一、简介

当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到食物的鸽子只好回去继续睡觉, 等待下一块食物到来。这样,每扔一块食物,都会惊动所有的鸽子,即为惊群。

二、OS惊群简介

在多进程/多线程等待同一资源时,也会出现惊群。即当某一资源可用时,多个进程/线程会惊醒,竞争资源。这就是操作系统中的惊群。

三、坏处

  1. 惊醒所有进程/线程,导致n-1个进程/线程做了无效的调度,上下文切换,cpu瞬时增高
  2. 多个进程/线程争抢资源,所以涉及到同步问题,需对资源进行加锁保护,加解锁加大系统CPU开销

四、常见场景

在高并发(多线程/多进程/多连接)中,会产生惊群的情况有:

  1. accept 惊群
  2. epoll 惊群
  3. nginx 惊群
  4. 线程池惊群

4.1 accept 惊群

以多进程为例,在主进程创建监听描述符 listenfd 后,fork() 多个子进程,多个进程共享 listenfd,accept 是在每个子进程中,当一个新连接来的时候,会发生惊群。

在内核2.6之前,所有进程accept都会惊醒,但只有一个可以accept成功,其他返回EGAIN。

在内核2.6及之后,解决了惊群问题,办法是在内核中增加了一个互斥等待变量。一个互斥等待的行为与睡眠基本类似,主要的不同点在于:
        1)当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部。若没有这个标志的入口项,则添加到队首。
        2)当 wake up 被在一个等待队列上调用时, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止。
        对于互斥等待的行为,比如对一个 listen 后的socket描述符,多线程阻塞 accept 时,系统内核只会唤醒所有正在等待此时间的队列的第一个,队列中的其他人则继续等待下一次事件的发生。这样就避免的多个线程同时监听同一个socket描述符时的惊群问题。

4.2 epoll 惊群

epoll惊群分两种:

1、是在fork之前创建 epollfd,所有进程共用一个epoll。

2、是在fork之后创建 epollfd,每个进程独用一个epoll。

4.2.1 fork之前创建epollfd(新版内核已解决)

  1. 主进程创建 listenfd,创建 epollfd。
  2. 主进程 fork 多个子进程
  3. 每个子进程把 listenfd,加到 epollfd 中。
  4. 当一个连接进来时,会触发 epoll 惊群,多个子进程的 epoll 同时会触发。

这里的epoll惊群跟 accept 惊群是类似的,共享一个 epollfd,加锁或标记解决,在新版本的epoll中已解决,但在内核2.6及之前是存在的。

4.2.2 fork之后创建epollfd(内核未解决)

  1. 主进程创建 listendfd 。
  2. 主进程创建多个子进程
  3. 每个子进程创建自已的 epollfd。
  4. 每个子进程把 listenfd 加入到 epollfd 中。
  5. 当一个连接进来时,会触发 epoll 惊群,多个子进程 epoll 同时会触发。

因为每个子进程的 epoll 是不同的epoll, 虽然 listenfd 是同一个,但新连接过来时, accept 会触发惊群。因为内核不知道该发给哪个监听进程,因为不是同一个 epoll 。所以这种惊群内核并没有处理,惊群还是会出现。

4.3 nginx惊群

这里说的nginx惊群,其实就是上面的问题(fork之后创建epollfd),下面看看 nginx 是怎么处理惊群的。

在nginx中使用的epoll,是在创建进程后创建的 epollfd 。因些会出现上面的惊群问题。即每个子进程worker都会惊醒。

在nginx中,流程。

  1. 主线程创建 listenfd 。
  2. 主线程fork多个子进程(根据配置)。
  3. 子进程创建 epollfd 。
  4. 获得 accept 锁,只有一个子进程把 listenfd 加到epollfd中。(同一时间只有一个进程会把监听描述符加到 epoll 中)
  5. 循环监听。
void ngx_process_events_and_timers(ngx_cycle_t *cycle){// 忽略....//ngx_use_accept_mutex表示是否需要通过对accept加锁来解决惊群问题。//当nginx worker进程数>1时且配置文件中打开accept_mutex时,这个标志置为1if (ngx_use_accept_mutex) {//ngx_accept_disabled表示此时满负荷,没必要再处理新连接了,//我们在nginx.conf曾经配置了每一个nginx worker进程能够处理的最大连接数,//当达到最大数的7/8时,ngx_accept_disabled为正,说明本nginx worker进程非常繁忙,//将不再去处理新连接,这也是个简单的负载均衡if (ngx_accept_disabled > 0) {ngx_accept_disabled--;} else {//获得accept锁,多个worker仅有一个可以得到这把锁。//获得锁不是阻塞过程,都是立刻返回,获取成功的话ngx_accept_mutex_held被置为1。//拿到锁,意味着监听句柄被放到本进程的epoll中了,//如果没有拿到锁,则监听句柄会被从epoll中取出。if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {return;}//拿到锁的话,置flag为NGX_POST_EVENTS,这意味着ngx_process_events函数中,//任何事件都将延后处理,会把accept事件都放到ngx_posted_accept_events链表中,// epollin|epollout事件都放到ngx_posted_events链表中if (ngx_accept_mutex_held) {flags |= NGX_POST_EVENTS;} else {//拿不到锁,也就不会处理监听的句柄,//这个timer实际是传给epoll_wait的超时时间,//修改为最大ngx_accept_mutex_delay意味着epoll_wait更短的超时返回,//以免新连接长时间没有得到处理if (timer == NGX_TIMER_INFINITE|| timer > ngx_accept_mutex_delay){timer = ngx_accept_mutex_delay;}}}}// 忽略....//linux下,调用ngx_epoll_process_events函数开始处理(void) ngx_process_events(cycle, timer, flags);// 忽略....//如果ngx_posted_accept_events链表有数据,就开始accept建立新连接if (ngx_posted_accept_events) {ngx_event_process_posted(cycle, &ngx_posted_accept_events);}//释放锁后再处理下面的EPOLLIN EPOLLOUT请求if (ngx_accept_mutex_held) {ngx_shmtx_unlock(&ngx_accept_mutex);}if (delta) {ngx_event_expire_timers();}ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,"posted events %p", ngx_posted_events);//然后再处理正常的数据读写请求。因为这些请求耗时久,//所以在ngx_process_events里NGX_POST_EVENTS标志将事件//都放入ngx_posted_events链表中,延迟到锁释放了再处理。if (ngx_posted_events) {if (ngx_threaded) {ngx_wakeup_worker_thread(cycle);} else {ngx_event_process_posted(cycle, &ngx_posted_events);}}}

分析:

  1. nginx里采用了主动的方法去把监听描述符放到epoll中或从epoll移出(这个是nginx的精髓所在,因为大部份的并发架构都是被动的)
  2. nginx中用采互斥锁去解决谁来accept问题,保证了同一时刻,只有一个worker接收新连接(所以nginx并没有惊群问题)
  3. nginx根据自已的载负(最大连接的7/8)情况,决定去不去抢锁,简单方便地解决负载,防止进程因业务太多而导致所有业务都不及时处理

nginx采用互斥锁和主动的方法,避免了惊群,使得nginx中并无惊群。

4.4 线程池惊群

在多线程设计中,经常会用到互斥和条件变量的问题。当一个线程解锁并通知其他线程的时候,就会出现惊群的现象。

pthread_mutex_lock / pthread_mutex_unlock

线程互斥锁的加锁及解锁函数。

pthread_cond_wait

线程池中的消费者线程等待线程条件变量被通知;

pthread_cond_signal / pthread_cond_broadcast

生产者线程通知线程池中的某个或一些消费者线程池,接收处理任务;

这里的惊群现象出现在 3 里,pthread_cond_signal,语义上看是通知一个线程。调用此函数后,系统会唤醒在相同条件变量上等待的一个或多个线程(可参看手册)。如果通知了多个线程,则发生了惊群。

正常的用法:

  1. 所有线程共用一个锁,共用一个条件变量。
  2. 当 pthread_cond_signal 通知时,就可能会出现惊群。

解决惊群的方法:

  1. 所有线程共用一个锁,每个线程有自已的条件变量。
  2. pthread_cond_signal 通知时,定向通知某个线程的条件变量,不会出现惊群

五、高并发设计

以多线程为例,进程同理

栗子

主线程

子线程 epoll

是否有惊群

新版本是否已经解决

参考

1

listenfd

epollfd

共用 listenfd 和 epollfd

子线程accept

epoll 惊群

已解决

被动

2

listenfd

共用 listenfd,

每个线程创建 epollfd

listenfd 加入 epoll

epoll 惊群

未解决

被动

3

listenfd

主线程accept并分发connfd

每个线程创建 epollfd

接收主线程分发的 connfd

无惊群

accept 瓶颈

(无用)

被动

4

listenfd

共用 listenfd,

每个线程创建 epollfd

互斥锁决定加入 / 移出 epoll

无惊群

nginx

5.1 栗1
分析

主线程创建 listenfd 和 epollfd,子线程共享并把 listenfd 加入到epoll中,旧版中会出现惊群,新版中已解决了惊群。

缺点

应用层并不知道内核会把新连接分给哪个线程,可能平均,也可能不平均如果某个线程已经最大负载了,还分过来,会增加此线程压力甚至崩溃。
总结

因为例1并不是最好的方法,因为没有解决负载和分配问题。

5.2 栗2
分析

主线程创建 listenfd,子线程创建 epollfd,,把 listenfd 加入到 epoll 中, 这种方法是无法避免惊群的问题。每次有新连接时,都会唤醒所有的accept线程,但只有一个 accept 成功,其他的线程 accept 失败 EAGAIN 。

总结

栗 2 解决不了惊群的问题,如果线程超多,惊群越明显。如果真正开发中,可忽略惊群,或者需要用惊群,那么使用此种设计也是可行的。

5.3 栗3
分析

主线程创建 listenfd,每个子线程创建 epollfd,主线程负责accept,并发分新connfd给负载最低的一个线程,然后线程再把connfd 加入到 epoll 中。无惊群现象。

总结:

主线程只用 accept 用,可能会主线程没干,或连接太多处理不过来,accept 瓶颈(一般情况不会产生)。主线程可以很好地根据子线程的连接来分配新连接,有比较好的负载并发量也比较大,自测(单进程十万并发连接QPS十万,四核四G内存,很稳定)
5.4 栗4

这是 nginx 的设计,无疑是目前最优的一种高并发设计,无惊群。

nginx本质

同一时刻只允许一个 nginx worker 在自己的 epoll 中处理监听句柄。它的负载均衡也很简单,当达到最大 connection 的 7/8时,本 worker 不会去试图拿 accept 锁,也不会去处理新连接。这样其他 nginx worker 进程就更有机会去处理监听句柄,建立新连接了。而且,由于 timeout 的设定,使得没有拿到锁的worker进程,去拿锁的频繁更高。

总结

nginx的设计非常巧妙,很好的解决了惊群的产生,所以没有惊群。同时也根据各进程的负载主动去决定要不要接受新连接,负载比较优。

六、总结

研究高并发有一段时间了,总结下我自已的理解,怎么样才算是高并发呢?单进程百万连接,单进程百万 QPS ?

先说说基本概念

6.1 高并发连接

指的是连接的数量,对服务端来说,一个套接字对就是一个连接,连接和本地文件描述符无关,不受本地文件描述符限制,只跟内存有关,假设一个套接字对占用服 务器 8k 内存,那么1G内存=1024*1024/8 = 131072。因此连接数跟内存有关。1G = 10万左右连接,当然这是理论,实际要去除内核占用,其他进程占用,和本进程其他占用。假哪一个机器 32G 内存,那个撑个100万个连接是没有问题的。如果是单个进程100万连,那就更牛B了,但一般都不会这么做,因为如果此进程宕了,那么,所有业务都影响了。所以一般都会分布到不同进程,不同机器,一个进程出问题了,不会影响其他进程的处理。(这也是nginx原理)

6.2 PV

每天的总访问量 pave view, PV = QPS * (24*0.2) * 3600 (二八原则)

6.3 QPS

每秒请求量。假如每秒请求量10万,假如机器为16核,那么启16个线程同时工作, 那么每个线程同时的请求量 = 10万/ 16核 =  6250QPS。

按照二八原则,一天24小时,忙时=24*0.2 = 4.8小时。

则平均一天总请求量 = 4.8 * 3600 *10万QPS = 172亿8千万。

那么每秒请求10万并发量,每天就能达到172亿的PV。这算高并发吗?

6.4 丢包率

如果客端端发10万请求,服务端只处理了8万,那么就丢了2万。丢包率=2/10 = 20%。丢包率是越小越好,最好是没有。去除网络丢包,那么就要考虑内核里的丢包问题,因此要考虑网卡的吞吐量,同一时间发大多请求过来,内核会不会处理不过来, 导致丢包。

6.5 稳定性

一个高并发服务,除了高并发外,最重要的就是稳定了,这是所有服务都必须的。 一千 QPS 能处理,一万QPS 也能处理,十万 QPS 也能处理,当然越多越好。不要因为业务骤增导致业务瘫痪,那失败是不可估量的。因为,要有个度,当业务增加到一定程 度,为了保证现有业务的处理,不处理新请求业务,延时处理等,同时保证代码的可靠。

因此,说到高并发,其实跟机器有并,内存,网卡,CPU核数等有关,一个强大的服务器,比如:32核,64G内存,网卡吞吐很大,那么单个进程,开32个线程,做一个百万连接,百万QPS的服务,是可行的。

本身按栗 3 去做了个高并发的设计,做到了四核4G内存的虚拟机里,十万连接,十万QPS,很稳定,没加业务,每核CPU %sys 15左右 %usr 5%左右。如果加了业务,应该也是比较稳定的,有待测试。当然例3是有自已的缺点的。

同进,也希望研究高并发的同学,一起来讨论高并发服务设计思想。(加微:luoying140131)

(SAW:Game Over!)

Linux / 惊群效应相关推荐

  1. Linux惊群效应详解(最详细的了吧)

    https://blog.csdn.net/lyztyycode/article/details/78648798?locationNum=6&fps=1 linux惊群效应 详细的介绍什么是 ...

  2. Linux惊群效应之Nginx解决方案

    结论 不管还是多进程还是多线程,都存在惊群效应,本篇文章使用多进程分析. 在Linux2.6版本之后,已经解决了系统调用Accept的惊群效应(前提是没有使用select.poll.epoll等事件机 ...

  3. Linux惊群效应详解

    inux惊群效应 详细的介绍什么是惊群,惊群在线程和进程中的具体表现,惊群的系统消耗和惊群的处理方法. 1.惊群效应是什么?        惊群效应也有人叫做雷鸣群体效应,不过叫什么,简言之,惊群现象 ...

  4. Nginx源码实现的细枝末节 11个阶段的实现丨Nginx Filter|中间件开发|惊群效应|负载均衡丨组件丨c/c++linux服务器开发

    Nginx源码实现的细枝末节 11个阶段的实现 视频讲解如下,点击观看: Nginx源码实现的细枝末节 11个阶段的实现丨Nginx Filter|中间件开发|惊群效应|负载均衡丨组件丨c/c++li ...

  5. Linux环境,手把手带你实现一个Nginx模块,深入了解Nginx丨惊群效应|error|负载均衡|Openresty丨C/C++Linux服务器开发丨中间件

    Linux环境,手把手带你实现一个Nginx模块,深入了解Nginx 视频讲解如下,点击观看: Linux环境,手把手带你实现一个Nginx模块,深入了解Nginx丨惊群效应|error|负载均衡|O ...

  6. 系统通知并发问题_玩转Java高并发?请先说明下并发下的惊群效应

    实际项目中,我们有很多高并发的场景需要考虑.设计,在高并发领域有个很有特点的名词叫惊群效应,你了解吗? 一.啥是惊群效应 啥叫惊群效应,有个例子说明的很透彻.当你往一群鸽子中间扔一块食物,虽然最终只有 ...

  7. 解决多进程模式下引起的“惊群”效应

    导语: 对不起,我是标题党,本文解决的不是我们理解的"惊群"效应,先为我们操作系统组的正下名,因为腾讯服务器的内核版本,已经解决epoll模式下的惊群现象(本文描述的现象跟惊群其实 ...

  8. 一周技术思考(第36期)-缓存踩踏与惊群效应

    图自网络 "10年前的那一天Facebook发生了什么",本想用这个题目,但不符合本系列的气质,那,那天到底发生了什么呢. Facebook的事故介绍 2010年9月23日,Fac ...

  9. Nginx解决惊群效应

    1. 惊群效应 1.1 简介 惊群问题又名惊群效应.简单来说就是多个进程或者线程在等待同一个事件,当事件发生时,所有线程和进程都会被内核唤醒.唤醒后通常只有一个进程获得了该事件并进行处理,其他进程发现 ...

最新文章

  1. linux deploy ENV 目录,手机安装linux deploy 安装和配置
  2. Java中常见的几种类型转换
  3. 浙江理工大学2019年4月赛
  4. rabitMQ-centos7安装
  5. 计算机网络基础:网络分类和拓扑结构知识笔记
  6. Zoom Host可以真正看到您的所有私人消息吗?
  7. [見好就收]NET 2.0 - WinForm Control - DataGridView 编程36计
  8. 车联网服务non-RESTful架构改造实践
  9. 【2017年第2期】专题:大数据管理与分析
  10. 嘉年华专访 | 国际上智能运维研究
  11. 使用Photoshop+960 Grid System模板进行网页设计
  12. 【C语言】16-预处理指令2-条件编译
  13. 威纶触摸屏485轮询通讯_【威纶】触摸屏 界面制作软件 EBpro使用手册.pdf
  14. 台式计算机未识别网络,台式机显示未识别网络怎么办
  15. sourcetree的日常使用
  16. 【Unity3D脚本】Transform类
  17. 剪辑视频怎么学?手把手教你自学视频剪辑
  18. 用c语言编写打猎小游戏,使用c语言编写简单小游戏.docx
  19. 解决 plt.savefig() 生成空白图片的问题
  20. gmtime ()函数

热门文章

  1. [转载]如何编写无法维护的代码(3)
  2. win11 WSL centos7安装docker命令整理
  3. Harbor API整理:获取项目下的所有镜像
  4. kafka2.5.0创建主题topic命令
  5. git放弃本地文件修改
  6. Spark弹性式数据集RDDs
  7. 解决ubuntu16.E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用) E: 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?
  8. Scala泛型:协变和逆变
  9. scala整型的类型及取值范围
  10. Spring Data ElasticSearch示例--查询索引库