Nginx accept_mute 惊群问题

 

惊群的定义


首先,来看惊群的定义:

The thundering herd problem occurs when a large number of processes waiting for an event are awoken when that event occurs, but only one process is able to proceed at a time. After the processes wake up, they all demand the resource and a decision must be made as to which process can continue. After the decision is made, the remaining processes are put back to sleep, only to all wake up again to request access to the resource.

This occurs repeatedly, until there are no more processes to be woken up. Because all the processes use system resources upon waking, it is more efficient if only one process was woken up at a time.

This may render the computer unusable, but it can also be used as a technique if there is no other way to decide which process should continue (for example when programming with semaphores).

简而言之,惊群现象就是当多个进程/线程等待同一个事件,如果这个事件发生,会唤醒所有的进程/线程,但最终只可能有一个进程/线程能对该事件进行处理,其他进程/线程会在获取事件失败后重新休眠。

惊群现象非常像把食物丢进鸡群,引起所有的鸡一起哄抢食物。如果这个食物只是一粒米的话,唤醒所有的鸡一起来抢的话则非常没有必要。

惊群通常发生在网络服务器上。父进程首先绑定一个端口监听socket,然后fork出多个子进程,子进程们开始循环等待处理(比如accept)这个socket。每当用户发起一个TCP连接时,多个子进程同时被唤醒,然后其中一个子进程accept新连接成功,余者皆失败,重新休眠。

 

如何解决惊群问题呢?


另:高版本的Linux中,accept不存在惊群问题,不过epoll_wait等操作还有。

解决的惊群的方法也很简单,每次把进程(鸡)排个序,来了新的请求(食物)只唤醒排在第一位的就好。

其实早在linux2.6,accept系统调用的惊群问题已经被解决了:http://citi.umich.edu/projects/linux-scalability/reports/accept.html

但我们的网络程序不会单独阻塞在accept调用上,我们还有许多其他网络事件要处理。在accept之前,我们依然还得处理惊群现象。拿nginx来说,master进程监听端口号,所有的nginx worker进程开始用epoll_wait来处理新事件(使用epoll模型),如果不加任何保护,一个新连接来临时,会有多个worker进程在epoll_wait后被唤醒。也就是说,我们还要解决epoll的惊群问题。

 Nginx解决惊群问题的配置是accept_mute。开启 accept_mutex,只有一个子进程会将监听套接字添加到epoll中,这样当一个新的连接来到时,就只有一个 worker 子进程会被唤醒了。Nginx在1.11.3版本以前是默认开启accept_mutex的。

Linux4.5以后的版本中增加了EPOLLEXCLUSIVE支持以解决epoll的惊群问题(https://github.com/torvalds/linux/commit/df0108c5da561c66c333bb46bfe3c1fc65905898),Nginx从1.11.3版本之后也增加了对EPOLLEXCLUSIVE的支持,转而由操作系统自己解决epoll的惊群,从此之后accept_mutex从默认的on变成了默认off。惊群问题完美解决。

https://github.com/nginx/nginx/commit/5c2dd3913aad5c4bf7d9056e1336025c2703586b

https://github.com/alibaba/tengine/issues/768

https://nginx.org/en/CHANGES

 

总结


假设你养了一百只小鸡,现在你有一粒粮食,那么有两种喂食方法:

  • 你把这粒粮食直接扔到小鸡中间,一百只小鸡一起上来抢,最终只有一只小鸡能得手,其它九十九只小鸡只能铩羽而归。这就相当于关闭了accept_mutex。
  • 你主动抓一只小鸡过来,把这粒粮食塞到它嘴里,其它九十九只小鸡对此浑然不知,该睡觉睡觉。这就相当于激活了accept_mutex。

可以看到此场景下,激活accept_mutex相对更好一些,让我们修改一下问题的场景,我不再只有一粒粮食,而是一盆粮食,怎么办?

此时如果仍然采用主动抓小鸡过来塞粮食的做法就太低效了,一盆粮食不知何年何月才能喂完,大家可以设想一下几十只小鸡排队等着喂食时那种翘首以盼的情景。此时更好的方法是把这盆粮食直接撒到小鸡中间,让它们自己去抢,虽然这可能会造成一定程度的混乱,但是整体的效率无疑大大增强了。

简单点说:Apache动辄就会启动成百上千的进程,如果发生惊群问题的话,影响相对较大;但是对Nginx而言,一般来说,worker_processes会设置成CPU个数,所以最多也就几十个,即便发生惊群问题的话,影响相对也较小。

Nginx缺省激活了accept_mutex,是一种保守的选择。如果关闭了它,可能会引起一定程度的惊群问题,表现为上下文切换增多(sar -w)或者负载上升,但是如果你的网站访问量比较大,为了系统的吞吐量,我还是建议大家关闭它。

 

最后附上一张图: 章亦春,OpenResty Inc. 创始人兼 CEO,OpenResty 开源项目创建者

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章