写在前面

写NGINX系列的随笔,一来总结学到的东西,二来记录下疑惑的地方,在接下来的学习过程中去解决疑惑。

也希望同样对NGINX感兴趣的朋友能够解答我的疑惑,或者共同探讨研究。

整个NGINX系列的文章中,我会将我的疑惑用红色标出,希望能遇到前辈在评论中给我解答迷津。

----基于nginx 1.4.4

(虽然类似的文章博客已经很多了,但学了东西不整理记录,始终变不成自己的东西。)

所谓的惊群

简单举例来说

TCP服务端socket的建立,一般经过socket、bind、listen初始化后,调用accept等待客户端的连接。服务器一般做法会在listen后,fork多个子进程同时accept客户端的连接。

子进程在调用accept后会堵塞睡眠,当第一个客户端连接到来后,所有子进程都会唤醒,但只会有一个子进程accept调用成功,其他子进程返回失败,代码中的处理往往在accept返回失败后,继续调用accept。

虽然这在功能上没有什么问题,但在性能上很是浪费。有一种处理方式,是父进程调用accept之后,然后通过某种方式传递给子进程处理连接(怎么传递?我没想到,fork时,子进程会复制父进程的资源,因此子进程accept就是父进程初始化好的fd,但如果子进程fork后,父进程给子进程传递accept到的资源描述符fd,怎么传递?),但需要一个单独的进程处理accept,其实也是对CPU的一种浪费。

(问题补充:可以在accept后,fork子进程处理新连接)

惊群的解决

Linux内核2.6已经解决了accept时的惊群问题,多个子进程accept堵塞睡眠时,连接到来,只有一个进程的accept会被唤醒返回。但现在子进程的实现方式不是直接accept,而是将初始化好的fd加入到epoll 的事件队列中,epoll返回后再调用accept。Linux无法解决多个子进程epoll返回的情况。这需要子进程自己处理。

Nginx的处理

Nginx中处理epoll时惊群问题的思路很简单,多个子进程有一个锁,谁拿到锁,谁才将accept的fd加入到epoll队列中,其他的子进程拿不到锁,也就不会将fd加入到epoll中,连接到来也就不会导致所有子进程的epoll被唤醒返回。

。。。明天分析代码。

代码分析

Nginx执行路径

 nginx.c:main()
ngx_master_process_cycle()
ngx_start_worker_processes()
ngx_spawn_process()
ngx_worker_process_cycle()
{
for(;;) {
ngx_process_events_and_timers()
}
}
           

惊群处理的代码

ngx_process_events_and_timers()处理事件,这个函数中的以下代码,处理惊群问题,顺带实现了负载均衡,因为处理惊群问题和负载均衡问题的代码在一起,下面一起分析一下。

     if (ngx_use_accept_mutex) {
if (ngx_accept_disabled > ) {
ngx_accept_disabled--; } else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
} if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS; } else {
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}

ngx_use_accept_mutex配置nginx是否利用ngx_accept_mutex锁的方式解决惊群问题(nginx只有这种方式,如果不使用,惊群问题就会存在),默认配置是打开的。

ngx_accept_disable用于处理负载均衡。

这里可以看出使用ngx_accept_mutex锁的情况下,一个进程要处理accept事件,必须满足两个条件:

  1. 满足负载均衡条件(负载压力低,稍后介绍nginx怎么判断压力高低)
  2. 获取ngx_accept_mutex锁

负载均衡条件

Ngx_accept_disable会在每次accept事件正确处理后,更新其值

ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;

connection_n是配置文件中配置的该进程处理的最大连接数,free_connection_n初始值为connection_n,新建一个连接++,关闭一个连接--。

(我没有看到ngx_accept_disabled初始值是在哪赋值的,有时间调试一下,看看初始值是多少)

当free_connection_n 大于connection_n的1 / 8(连接数少于总数的7 / 8)时,ngx_accept_disable是负的,此时进程会参与锁的争夺,如果获得锁,该进程处理accept事件,其他进程,不处理accept事件。

当该进程处理accept事件增多,处理的连接也就增多,当free_connection_n小于connection_n的1 / 8(连接数大于总数的7 / 8)时,ngx_accept_disabled是正的,此时该进程会简单的将ngx_accept_disabled--,并退出锁的争夺,把机会让给其他进程。就这样平衡了负载,防止一个进程负载过高。

进程的连接越多,放弃争夺的次数也就越多,而ngx_accept_disabled--,避免了进程一直退出锁的争夺,防止不再接受新连接。

当每个进程的连接数都比较少时,谁抢到了ngx_accept_disable,谁处理连接;当一个进程抢的比较多,连接数率先达到了 7/ 8,那么就会退出锁的争夺,把机会让给其他进程,并不时ngx_accept_disabled--,保证一段时间后继续参加锁的争夺;当所有进程的连接数都大于 7 / 8时,这时再有新连接到来,处理延迟应该会比较大,因为所有进程都放弃了争夺,直到ngx_accept_disable—到小于0,再次争夺。

获取ngx_accept_mutex锁

当满足了负载均衡条件,进程就会参与ngx_accept_mutex锁的争夺 ngx_trylock_accept_mutex(cycle)

Ngx_trylock_accept_mutex出现错误,直接return了

没有错误的情况

ngx_accept_mutex_held=1表示拿到了锁

拿到锁会设置flags |= NGX_POST_EVENTS; 设置了这个标志,epoll返回的所有事件不会立即处理,而是将事件放到post队列中,然后释放锁后在把队列中的事件一一拿出处理,这样做的原因是防止处理事件导致锁长时间得不到释放,新的accept连接事件得不到(其他进程的)及时处理。

ngx_accept_mutex_held=0表示没有拿到锁

拿不到锁的进程会修改epoll超时时间,让epoll尽快返回,早早的参与到下一次锁的争夺上来,也就能快速的处理新的accept连接事件。

(写到这里不得不佩服作者真是各种情况都考虑到了,牛啊)

接下来看看关于锁的战争

 ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
if (ngx_accept_mutex_held
&& ngx_accept_events ==
&& !(ngx_event_flags & NGX_USE_RTSIG_EVENT))
{
return NGX_OK;
}
if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
ngx_shmtx_unlock(&ngx_accept_mutex);
return NGX_ERROR;
}
ngx_accept_events = ;
ngx_accept_mutex_held = ; return NGX_OK;
}
if (ngx_accept_mutex_held) {
if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
return NGX_ERROR;
}
ngx_accept_mutex_held = ;
}
return NGX_OK;
}

line4       试图获取锁

line20     没有获取到锁,但ngx_accept_mutex_held却是1,是个异常,禁止当前进程处理accept事件(将accept fd将epoll队列中移除),重置ngx_accept_mutex_held=0

line5-10   成功获取锁,发现自己早就获得了该锁,只是没有accept事件发生,又循环了一次,继续获得了该锁。(第一次没有事件发生,应该释放锁,再进入第二次的争夺呀??)

line11-16 成功获得锁后,将accept fd加入到epoll队列中,准备监听accept事件,并置ngx_accept_mutex_head = 1表示获取到了锁(ngx_accept_events具体什么时候会用到)。

NGINX怎样处理惊群的的更多相关文章

  1. Nginx中的惊群现象解决方法

    *什么是惊群现象?Nginx中用了什么方法来避免这种问题的发生?本篇就解决这两个问题...→_→* 惊群现象的定义与危害 在Nginx中,每一个worker进程都是由master进程fork出来的.m ...

  2. Nginx学习之一-惊群现象

    惊群问题(thundering herd)的产生 在建立连接的时候,Nginx处于充分发挥多核CPU架构性能的考虑,使用了多个worker子进程监听相同端口的设计,这样多个子进程在accept建立新连 ...

  3. accept与epoll惊群 转载

    今天打开 OneNote,发现里面躺着一篇很久以前写的笔记,现在将它贴出来. 1. 什么叫惊群现象 首先,我们看看维基百科对惊群的定义: The thundering herd problem occ ...

  4. 源码剖析Linux epoll实现机制及Linux上惊群

    转载:https://blog.csdn.net/tgxallen/article/details/78086360 看源码是对一个技术认识最直接且最有效的方式了,之前用Linux Epoll做过一个 ...

  5. “惊群”,看看nginx是怎么解决它的

    在说nginx前,先来看看什么是“惊群”?简单说来,多线程/多进程(linux下线程进程也没多大区别)等待同一个socket事件,当这个事件发生时,这些线程/进程被同时唤醒,就是惊群.可以想见,效率很 ...

  6. Nginx惊群处理

    惊群:是指在多线程/多进程中,当有一个客户端发生链接请求时,多线程/多进程都被唤醒,然后只仅仅有一个进程/线程处理成功,其他进程/线程还是回到睡眠状态,这种现象就是惊群. 惊群是经常发生现在serve ...

  7. 【转载】“惊群”,看看nginx是怎么解决它的

    原文:http://blog.csdn.net/russell_tao/article/details/7204260 在说nginx前,先来看看什么是“惊群”?简单说来,多线程/多进程(linux下 ...

  8. 【Nginx】惊群问题

    转自:江南烟雨 惊群问题的产生 在建立连接的时候,Nginx处于充分发挥多核CPU架构性能的考虑,使用了多个worker子进程监听相同端口的设计,这样多个子进程在accept建立新连接时会有争抢,这会 ...

  9. Nginx模型 & 惊群问题

    这篇写的不错 http://www.cnblogs.com/linguoguo/p/5511293.html Nginx为啥性能高-多进程异步IO模型 1. 对于每个worker进程来说,独立的进程, ...

随机推荐

  1. Hadoop+Spark:集群环境搭建

    环境准备: 在虚拟机下,大家三台Linux ubuntu 14.04 server x64 系统(下载地址:http://releases.ubuntu.com/14.04.2/ubuntu-14.0 ...

  2. [翻译] java NIO Channel

    原文地址:http://tutorials.jenkov.com/java-nio/channels.html JAVA NIO channels和流的概念很像,下面是他们的一些区别: 你可以对cha ...

  3. Python 字符串方法详解

    Python 字符串方法详解 本文最初发表于赖勇浩(恋花蝶)的博客(http://blog.csdn.net/lanphaday),如蒙转载,敬请保留全文完整,切勿去除本声明和作者信息.        ...

  4. css字体样式(Font Style),属性

    css字体样式(Font Style),属性   css字体样式(Font Style)是网页中不可或缺的样式属性之一,有了字体样式,我们的网页才能变得更加美观,因此字体样式属性也就成为了每一位设计者 ...

  5. android webview 底层实现的逻辑

    其实在不同版本上,webview底层是有所不同的. 先提供个地址给大家查:http://grepcode.com/file/repository.grepcode.com/java/ext/com.g ...

  6. Together

  7. Cutterman 切图神器,虽然还没用过,先 mark 一下好了

    在官网上找了半天也没发现下载链接,注册账号用无法激活,还是等等再试吧.

  8. tomcat普通用户运行

    网站绑定域名后直接通过域名访问使用的是80端口,因此tomcat须监听80端口,而为了安全起见tomcat一般不用root身份运行,综上,需要以普通用户来运行监听80端口的tomcat.此时就会启动失 ...

  9. Tiny Rss简明安装与配置笔记

    对于某些自己喜欢的却没有RSS订阅功能的网站,可以通过Feed43定制抓取规则来获取所需信息.但由于其服务器在国外,抓取时常失败,且免费账户抓取频率太低,遂仿造他自行实现了一个并挂到服务器上,然后通过 ...

  10. CSS3响应式布局之弹性盒子

    CSS3弹性盒模型可以轻松的创建自适应浏览器流动窗口的布局或自适应字体大小的布局.同时该盒子决定了一个盒子在其他盒子的分布方式,及如何处理可用的空间. 自己写了一个弹性盒子的demo: 主要HTML代 ...