1.线程池介绍一下
线程池是一组提前创建的线程,去完成工作队列中的任务,用于短任务和高并发的场景。例如数据库查询、日志打印、异步处理等操作。它避免了频繁的创建和销毁线程带来的开销。但是如果线程池中的线程过多的话,也会有上下文切换带来的开销。
线程池中的任务积压怎么去处理?
可以直接拒绝服务,也可以使用消息队列把任务存储起来,也可以对任务区分优先级,丢弃低优先级的服务,也可以使用提交任务的线程去完成任务。
2.一个完整的 RPC 框架请求调用的过程。
首先,客户端像调用本地函数一样调用服务端的接口,把服务名称、参数序列化,TCP 协议把数据分成段,然后根据服务发现和附带均衡发送到目标服务器。然后目标服务器对数据进行反序列化,然后找到业务服务,完成相应的调用。然后业务服务把结果序列化返回给客户端,客户端反序列化得到最终结果。
数据从应用层到网络层到下发的过程中,各个层次做的工作是什么?
首先是客户端把数据进行序列化,序列化为字节流。然后由 TCP 对报文进行分段,然后增加 TCP 头,包括确认号、序号。之后由 IP 层进行路由,然后交由数据链路层封装成帧,由驱动网卡异步发送。
3.线程切换的开销。
线程的上下文切换就是保存和恢复程序的运行状态。具体内容有栈指针、程序计数器、寄存器等内容。对于不同进程主间的线程进行切换,还要进行地址空间的切换,具体就是页表寄存器、页表内容的切换。线程切换带来的另一个开销就是 CPU Cache 的命中率和 TLB 的命中率下降。
用户态和内核态切换上下文是做什么?
用户态和内核态的切换不是相对于不同进程,而是相对于同一个进程,所以不需要切换进程的地址空间,只需要切换栈指针、程序计数器和寄存器进行系统调用,系统调用完成之后,还要恢复用户态的执行上下文。
4.CPU 的使用率过高,怎么去排查?
首先通过 top ps 的命令去定位使用率过高的进程,然后通过命令去定位到线程。然后使用 GDB 调试,查看对战内容,结合性能分析工具去查看具体可能的情况,比如说包括死循环、锁竞争等情况。然后结合日志和对战信息去定位具体的代码问题。
5.怎么去设计一个分布式锁?
可以使用 Redis,利用 Redis 原子性的操作来实现。首先采用令牌桶的算法,Redis 中保存桶容量、当前的令牌数和令牌生成速率以及上次令牌更新的时间。每次申请令牌的时候,都使用 Lua 脚本,Lua 脚本进行具体的,首先判断当前的偷杆数量,如果可以的话,就进行扣减,然后返回。因为 Lua 脚本是保证了原子性的操作。
6.关于手撕问题,如何设计一个带有 TTL 的 LRU?
首先传统的 LRU 采用哈希表和双链表去设计,双链表是升序的,最近访问的放在链表头。然后哈希存储的是节点指针和它的 key。
增加一个堆,堆的内容分别是过期时间和 key,然后使用小顶堆,每次去读取的过程中,先去清理过期的数据。
7.如何设计一个使用滑动窗口的限流手撕?
可以使用带有容量的队列去实现,队列中存储的是时间。如果队列中的容量达到上限,并且最前的时间已经超出了时间窗口,就逐个的去除。