目录
2.2.1fork之前创建epollfd(内核2.6已解决)
一:惊群效应
1.1惊群效应是什么?(以修勾为例)
惊群问题又名惊群效应。简单来说就是多个进程或者线程在等待同一个事件,当事件发生时,所有线程和进程都会被内核唤醒。唤醒后通常只有一个进程获得了该事件并进行处理,其他进程发现获取事件失败后又继续进入了等待状态,在一定程度上造成了资源浪费,降低了系统性能。
打个比方就是:当你往一群修勾中间扔一块食物,虽然最终只有一个修勾抢到食物,但所有修勾都会被惊动来争夺,没有抢到食物的修勾只好悻悻而归回去继续睡觉, 等待下一块食物到来。这样,每扔一块食物,都会惊动所有的修勾,即为惊群。
简单地说:就是扔一块食物,所有修勾来抢,但最终只一个修勾抢到了食物。
1.2惊群问题(thundering herd)的产生
在建立连接的时候,Nginx出于充分发挥多核CPU架构性能的考虑,使用了多个worker子进程监听相同端口的设计,这样多个子进程在accept建立新连接时会有争抢,这会带来著名的“惊群”问题,子进程数量越多越明显,这会造成系统性能的下降。
一般情况下,有多少CPU核心就有配置多少个worker子进程。假设现在没有用户连入服务器,某一时刻恰好所有的子进程都休眠且等待新连接的系统调用(如epoll_wait),这时有一个用户向服务器发起了连接,内核在收到TCP的SYN包时,会激活所有的休眠worker子进程。最终只有最先开始执行accept的子进程可以成功建立新连接,而其他worker子进程都将accept失败。这些accept失败的子进程被内核唤醒是不必要的,他们被唤醒会的执行很可能是多余的,那么这一时刻他们占用了本不需要占用的资源,引发了不必要的进程切换,增加了系统开销。
1.3惊群效应的影响
惊群效应会占用系统资源,降低系统性能。多进程/线程的唤醒,涉及到的一个问题是上下文切换问题。频繁的上下文切换带来的一个问题是数据将频繁的在寄存器与运行队列中流转。极端情况下,时间更多的消耗在进程/线程的调度上,而不是执行。
二:常见的惊群效应
在 Linux 下,我们常见的惊群效应发生于我们使用 accept
以及我们 select
、poll
或 epoll
等系统提供的 API 来处理我们的网络连接。
2.1accept 惊群
以多进程为例,在主进程创建监听描述符 listenfd 后,fork()多个子进程,多个进程共享listenfd,accept是在每个子进程中,当一个新连接来的时候,会发生惊群。
由上图所示:
- 主线程创建了监听描述符listenfd = 3
- 主线程fork 三个子进程共享listenfd=3
- 当有新连接进来时,内核进行处理
在内核2.6之前,所有进程accept都会惊醒,但只有一个可以accept成功,其他返回EGAIN。
在内核2.6及之后,解决了惊群,在内核中增加了一个互斥等待变量。一个互斥等待的行为与睡眠基本类似,主要的不同点在于:
- 当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项, 相反, 添加到开始.
- 当 wake_up 被在一个等待队列上调用时, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止。
- 对于互斥等待的行为,比如如对一个listen后的socket描述符,多线程阻塞 accept 时,系统内核只会唤醒所有正在等待此时间的队列 的第一个,队列中的其他人则继续等待下一次事件的发生,这样就避免的多个线程同时监听同一个socket描述符时的惊群问题。
2.2epoll惊群
epoll惊群分两种:
- 在fork之前创建epollfd,所有进程共用一个epoll;
- 在fork之后创建epollfd,每个进程独用一个epoll。