nginx系列第七篇:结合nginx讨论“惊群”问题

news/2024/10/21 11:27:07/

目录

1.什么是惊群

2.linux下socket通信之accept"惊群"现象

3.select/poll/epoll"惊群"现象

4.nginx中的惊群处理


1.什么是惊群

"惊群"是多个进程(线程)阻塞在某个系统调用上等待事件触发,当事件触发后,这些睡眠的进程(线程)会被同时唤醒,多个进程(线程)从阻塞的系统调用返回。"惊群"效率低下,
大量的CPU时间浪费在被唤醒发现无事可做,然后又继续睡眠的反复切换上。

2.linux下socket通信之accept"惊群"现象

        socket网络通信中,网络数据包的接收是异步进行的,数据包到来的处理分为两部分:
一是网卡通知数据包到来,中断协议栈收包。
二是协议栈将数据包填充socket的接收队列,通知应用程序有数据可读。
应用程序是通过socket和协议栈交互的,socket是应用程序和协议栈之间联系的桥梁,socket是两者之间的接口,
当数据包到达协议栈的时候,发生下面两个过程:
(1)协议栈将数据包放入socket的接收缓冲区队列,并通知持有该socket的应用程序;
(2)持有该socket的应用程序响应通知事件,将数据包从socket的接收缓冲区队列中取出

此处引用别出的一个图进行说明:

    对于高性能的服务器而言,为了利用多 CPU 核的优势,基本上都采用多个进程(线程)的方式同时accept在一个listen socket上。多个进程(线程)阻塞在accept调用上,在协议栈将client的请求socket放入listen socket的accept队列的时候,是要唤醒一个进程还是全部进程来处理呢?
linux内核通过睡眠队列来组织所有等待某个事件的task,而 wakeup 机制则可以异步唤醒整个睡眠队列上的 task,wakeup 逻辑在唤醒睡眠队列时,会遍历该队列链表上的每一个节点,调用每一个节点的 callback,从而唤醒睡眠队列上的每个 task。这样,在一个 connect 到达这个 lisent socket 的时候,内核会唤醒所有睡眠在 accept 队列上的 task。N 个 task 进程(线程)同时从 accept 返回,但是,只有一个 task 返回这个 connect 的 fd,其他 task 都返回-1(EAGAIN)。这是典型的 accept"惊群"现象。这个是 linux 上困扰了大家很长时间的一个经典问题,在 linux2.6(又说是2.4.1)以后的内核中得到彻底的解决,通过添加了一个 WQ_FLAG_EXCLUSIVE 标记告诉内核进行排他性的唤醒,即唤醒一个进程后即退出唤醒的过。用户进程 task 对 listen socket 进行 accept 操作,如果这个时候如果没有新的 connect 请求过来,用户进程 task 会阻塞睡眠在 listent fd 的睡眠队列上。这个时候,用户进程 Task 会被设置 WQ_FLAG_EXCLUSIVE 标志位,并加入到 listen socket 的睡眠队列尾部(这里要确保所有不带 WQ_FLAG_EXCLUSIVE 标志位的 non-exclusive waiters 排在带 WQ_FLAG_EXCLUSIVE 标志位的 exclusive waiters 前面)。根据前面的唤醒逻辑,一个新的 connect 到来,内核只会唤醒一个用户进程 task 就会退出唤醒过程,从而不存在了"惊群"现象。

3.select/poll/epoll"惊群"现象

        linux系统内核优化以后,accept系统调用已经不存在"惊群"现象。但是在实际服务端开发中,我们会存在另外一种惊群问题,通常一个服务端有很多网络IO需要处理,为了提高server的并发能力,不会让server阻塞在accept调用上,一般会使用 select/poll/epoll I/O 多路复用技术,同时为了充分利用多核CPU的有害,服务器上会起多个进程(线程)同时提供服务。因此在某一时刻多个进程(线程)阻塞在 select/poll/epoll_wait 系统调用上,当一个请求上来的时候,多个进程都会被 select/poll/epoll_wait 唤醒去 accept,然而只有一个进程(线程)accept 成功,其他进程(线程accept失败,然后重新阻塞在select/poll/epoll_wait系统调用上。因此尽管 accept 不存在"惊群"了,但是我们出现了另外的"惊群"。针对这个问题(只让一个进程(线程)去监听 listen socket 的可读事件),我们可以看一些nginx是如何处理惊群的。

4.nginx中的惊群处理

       nginx master不会接受任何请求,所有的连接都在worker中处理。nginx所有worker并不能同时accept多个新连接,nginx实现了一个叫accept锁的东西,同一时刻只有一个worker能够获取到这个accept锁,只有获取到锁的这个worker才能够accept新连接。代码实现如下:

      当 ngx_use_accept_mutex 为 1 的时候(当 nginx worker 进程数>1 时且配置文件中打开 accept_mutex 时,这个标志置为 1),表示要规避 listen fd"惊群"。nginx 的 worker 进程在进行 event 模块的初始化的时候, core event 模块的 process_init 函数中(ngx_event_process_init)将 listen fd 加入到 epoll 中并监听其 READ 事件。

ngx_use_accept_mutex 为 1 的流程如下:

(1)ngx_trylock_accept_mutex尝试加锁抢夺accept权限(ngx_shmtx_trylock(&ngx_accept_mutex)),加锁成功则调用ngx_enable_accept_events(cycle) 将一个或多个listen fd加入epoll监听READ事件(设置事件的回调函数ngx_event_accept),并设置ngx_accept_mutex_held = 1来标识自己持有锁。

(2)如果ngx_shmtx_trylock(&ngx_accept_mutex)失败,则调用ngx_disable_accept_events(cycle, 0)来将listen fd从epoll中delete掉。

(3)如果ngx_accept_mutex_held = 1(也就是抢到accept权),则设置延迟处理事件标志位flags |= NGX_POST_EVENTS; 如果ngx_accept_mutex_held = 0(没抢到accept权),则调整一下自己的epoll_wait超时,让自己下次能早点去抢夺accept权。

(4)进入ngx_process_events(ngx_epoll_process_events),在ngx_epoll_process_events将调用epoll_wait等待相关事件到来或超时。

(5)epoll_wait返回,循环遍历返回的事件,如果标志位flags被设置了NGX_POST_EVENTS,则将事件挂载到相应的队列中(Nginx有两个延迟处理队列,(1)ngx_posted_accept_events:listen fd返回的事件被挂载到的队列。(2)ngx_posted_events:其他socket fd返回的事件挂载到的队列),延迟处理事件,否则直接调用事件的回调函数。

(6)ngx_epoll_process_events返回后,则开始处理ngx_posted_accept_events队列上的事件,于是进入的回调函数是ngx_event_accept,在ngx_event_accept中accept客户端的请求,进行一些初始化工作,将accept到的socket fd放入epoll中。

(7)ngx_epoll_process_events处理完成后,如果本进程持有accept锁ngx_accept_mutex_held = 1,那么就将锁释放。

(8)接着开始处理ngx_posted_events队列上的事件。

       nginx 通过一次仅允许一个进程将 listen fd 放入自己的 epoll 来监听其 READ 事件的方式来达到 listen fd"惊群"避免。然而做好这一点并不容易,作为一个高性能 web 服务器,需要尽量避免阻塞,并且要很好平衡各个工作 worker 的请求,避免饿死情况。

(1)避免新请求不能及时得到处理的饿死现象。worker进程在抢夺到accept权限,加锁成功的时候,要将事件的处理delay到释放锁后在处理(为什么ngx_posted_accept_events队列上的事件处理不需要延迟呢? 因为ngx_posted_accept_events上的事件就是listen fd的可读事件,本来就是我抢到的accept权限,还没accept就释放锁,这个时候被别人抢走了怎么办呢?)。否则,获得锁的工作worker由于在处理一个耗时事件,这个时候大量请求过来,其他worker进程空闲却没有处理权限在干着急的等着。

(2)避免总是某个worker进程抢到锁的现象。大量请求被同一个进程抢到,而其他worker进程却很清闲。 nginx有个简单的负载均衡,ngx_accept_disabled表示此时满负荷程度,没必要再处理新连接了,nginx.conf配置了每个worker进程能够处理的最大连接数,当达到最大数的7/8时,ngx_accept_disabled为正,说明本nginx worker进程非常繁忙,将不再去处理新连接。每次要进行抢夺accept权限的时候,如果ngx_accept_disabled大于0,则递减1,不进行抢夺逻辑。

参考文章:

深入浅出 Linux 惊群:现象、原因和解决方案_socket


http://www.ppmy.cn/news/258767.html

相关文章

时尚大气Mac高清动态壁纸

一组时尚大气高清动态壁纸,该壁纸图片清晰,里面图片经典大气,让桌面看起来了干净整洁~ 时尚大气高清动态壁纸

韩国明星

615. 韩国明星 ★☆ 输入文件: star.in 输出文件: star.out 简单对比 时间限制:2 s 内存限制:128 MB 【问题描述】 在LazyCat同学的影响下,Roby同学开始听韩国的音乐,并且越来越喜欢H.o.T&#xf…

自动化10年+经验给你10条建议,让你在自动化界占据一片地!

目录 前言: 1、哪一刻,让你想起了自动化 1.1 执行回归测试 1.2 压测场景执行并发 1.3 UI稳定,接口不断升级 2、七问:是否了解自动化风险 2.1 团队成员的资历 2.2 自动化成本投入产出比 2.3 慎重对待UI级自动化 2.4 自动化…

E8-整理一些关于表单的表

起因 今天想给一个工作流对应的表写个触发器,本想抄以前写过的,但发现我想不起来之前是为哪个工作流做过类似的开发。看来有必要把哪些表,对应的哪个流程,做过哪些开发这些内容记录下来。 经过 那么,首先想办法把表…

618有什么数码好物值得购买?2023值得入手的数码好物推荐

在618期间,有哪些值得入手的数码好物?很多人还不知道有哪些数码好物值得买的,本文推荐几款质量不错数码好物,助您尽情享受618购物买买买。 一、南卡OE不入耳蓝牙耳机 推荐理由: 南卡OE耳机是一款性价比超高的不入耳蓝…

关于HTML面试题汇总

一、doctype的作用&#xff0c;严格与混杂模式的区别&#xff0c;有何意义 1、语法格式&#xff1a;<!DOCTYPE html> 2、<!DOCTYPE>不是Html标签&#xff0c;而是告知浏览器此页面使用哪个HTML版本进行编写的指令 3、html 4.01中有如下三种模式 1、html 4.01 St…

谷歌宣布:今年将Android 12L系统交付于三星、联想和微软

作者 | Sam Byford 编译 | 张洁 责编 | 屠敏 为了对平板电脑和可折叠设备进行定制化优化&#xff0c;三星、联想和微软的设备将搭载Android 12L。谷歌的Android工程副总裁Andrei Popescu发博文宣布&#xff0c;正式版本会于2022年的晚些时候交付给这三家公司。 图片下载自IC p…

缩略图加密学习总结

一、缩略图加密概述 完全加密为噪声图像后&#xff0c;密文图像的文件扩展&#xff0c;传输存储消耗更多的资源。完全加密的噪声图像的可用性建立在对密文进行解密的基础上&#xff0c;耗费大量的计算代价。原始图像中精细的视觉信息被抹去以保护隐私,而粗略的视觉信息被保留以…