前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >nginx怎么避免惊群?

nginx怎么避免惊群?

作者头像
后端技术探索
发布2018-09-18 12:24:51
2.2K0
发布2018-09-18 12:24:51
举报
文章被收录于专栏:后端技术探索
什么是惊群?

在建立连接的时候,Nginx处于充分发挥多核CPU架构性能的考虑,使用了多个worker子进程监听相同端口的设计,这样多个子进程在accept建立新连接时会有争抢,这会带来著名的“惊群”问题,子进程数量越多越明显,这会造成系统性能的下降。

一般情况下,有多少CPU核心就有配置多少个worker子进程。假设现在没有用户连入服务器,某一时刻恰好所有的子进程都休眠且等待新连接的系统调用(如epoll_wait),这时有一个用户向服务器发起了连接,内核在收到TCP的SYN包时,会激活所有的休眠worker子进程。最终只有最先开始执行accept的子进程可以成功建立新连接,而其他worker子进程都将accept失败。这些accept失败的子进程被内核唤醒是不必要的,他们被唤醒会的执行很可能是多余的,那么这一时刻他们占用了本不需要占用的资源,引发了不必要的进程切换,增加了系统开销。

以上所述的浪费性能的现象就是惊群。

nginx怎么解决惊群?

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

那么,我们不能只用一个进程去accept新连接么?然后通过消息队列等同步方式使其他子进程处理这些新建的连接,这样惊群不就避免了?没错,惊群是避免了,但是效率低下,因为这个进程只能用来accept连接。对多核机器来说,仅有一个进程去accept,这也是程序员在自己创造accept瓶颈。所以,我仍然坚持需要多进程处理accept事件。

其实,在linux2.6内核上,accept系统调用已经不存在惊群了(至少我在2.6.18内核版本上已经不存在)。大家可以写个简单的程序试下,在父进程中bind,listen,然后fork出子进程,所有的子进程都accept这个监听句柄。这样,当新连接过来时,大家会发现,仅有一个子进程返回新建的连接,其他子进程继续休眠在accept调用上,没有被唤醒。

但是很不幸,通常我们的程序没那么简单,不会愿意阻塞在accept调用上,我们还有许多其他网络读写事件要处理,linux下我们爱用epoll解决非阻塞socket。所以,即使accept调用没有惊群了,我们也还得处理惊群这事,因为epoll有这问题。上面说的测试程序,如果我们在子进程内不是阻塞调用accept,而是用epoll_wait,就会发现,新连接过来时,多个子进程都会在epoll_wait后被唤醒!

nginx就是这样,master进程监听端口号(例如80),所有的nginx worker进程开始用epoll_wait来处理新事件(linux下),如果不加任何保护,一个新连接来临时,会有多个worker进程在epoll_wait后被唤醒,然后发现自己accept失败。现在,我们可以看看nginx是怎么处理这个惊群问题了。

其具体思路是:不让多个进程在同一时间监听接受连接的socket,而是让每个进程轮流监听,这样当有连接过来的时候,就只有一个进程在监听那肯定就没有惊群的问题。具体做法是:利用一把进程间锁,每个进程中都尝试获得这把锁,如果获取成功将监听socket加入wait集合中,并设置超时等待连接到来,没有获得所的进程则将监听socket从wait集合去除。这里只是简单讨论nginx在处理惊群问题基本做法,实际其代码还处理了很多细节问题,例如简单的连接的负载均衡、定时事件处理等等。

核心的代码如下

代码语言:javascript
复制
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    ...
    //这里面会对监听socket处理
    //1、获得锁则加入wait集合
    //2、没有获得则去除
    if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
        return;
    }

    ...

    //设置网络读写事件延迟处理标志,即在释放锁后处理
    if (ngx_accept_mutex_held) {
        flags |= NGX_POST_EVENTS;
    } 

    ...

    //这里面epollwait等待网络事件
    //网络连接事件,放入ngx_posted_accept_events队列
    //网络读写事件,放入ngx_posted_events队列
    (void) ngx_process_events(cycle, timer, flags);

    ...

    //先处理网络连接事件,只有获取到锁,这里才会有连接事件
    ngx_event_process_posted(cycle, &ngx_posted_accept_events);

    //释放锁,让其他进程也能够拿到
    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

    //处理网络读写事件
    ngx_event_process_posted(cycle, &ngx_posted_events);
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 nginx 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是惊群?
  • nginx怎么解决惊群?
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档