惊群效应
惊群效应
惊群效应(thundering herd)是指多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群效应
惊群效应造成了什么问题
Linux 内核对用户进程(线程)频繁地做无效的调度、上下文切换等使系统性能大打折扣
为了确保只有一个进程(线程)得到资源,需要对资源操作进行加锁保护,加大了系统的开销
针对Accept惊群现象解决
Linux 2.6 版本之前,监听同一个 socket 的进程会挂在同一个等待队列上,当请求到来时,会唤醒所有等待的进程
Linux 2.6 版本之后,引入一个标记位 WQ_FLAG_EXCLUSIVE。用户进程 task 对 listen socket 进行 accept 操作,如果这个时候如果没有新的 connect 请求过来,用户进程 task 会阻塞睡眠在 listent fd 的睡眠队列上,这个时候,用户进程 Task 会被设置 WQ_FLAG_EXCLUSIVE 标志位,并加入到 listen socket 的睡眠队列尾部。根据唤醒逻辑,一个新的 connect 到来,内核只会唤醒一个用户进程 task 就会退出唤醒过程,从而不存在了"惊群"现象
针对epoll惊群现象
分为两中情况,epoll_create 在 fork 之前创建,或epoll_create 在 fork 之后创建
epoll_create 在 fork 之前创建
处理方案与accept惊群的原因类似,解决思路与accept一致
epoll_create 在 fork 之后创建
1、多线程环境下惊群问题的解决方法:
只使用一个线程进行事件的监控,每当有就绪事件到来时,就将这些事件转交给其它线程去处理,这样就避免了因为多执行流同时使用epoll监控而带来的惊群问题
2、多进程环境下惊群问题的解决主要借鉴的是 Lighttpd和nginx的解决方法
① Lighttpd的解决思路简单粗暴,就是直接无视这个问题,事件到来后依旧能够唤醒多个进程来争抢,但是只有一个能成功,其它进程争抢失败后的报错EAGAIN会被捕获,捕获后不会处理这个错误,而是直接无视,就当做没有发生
② Nginx的解决思路其实就是加锁与负载均衡:
使用一个全局的互斥锁,每当有描述符就绪,就会让每个进程都去竞争这把锁(如果某个进程当前连接数达到了最大连接数的7/8,也就是负载均衡点,此时这个进程就不会再去争抢锁资源,而是将负载均衡到其它进程上),如果成功竞争到了锁,则将描述符加入进自己的wait集合中,而对于没有竞争到锁的进程,则将其从自己的wait集合中移除,这样就保证了不会让多个进程同一事件进行监控,而是让每个进程都通过竞争锁的方式轮流进行监控,这样保证了同一时间只会有一个进程进行监控,因此解决了惊群问题.
Nginx 中有个标志 ngx_use_accept_mutex,当 ngx_use_accept_mutex 为 1 的时候(当 nginx worker 进程数>1 时且配置文件中打开 accept_mutex 时,这个标志置为 1),表示要进行 listen fdt"惊群"避免
ngx_accept_mutex_held 表示当前是否已经持有锁。
ngx_accept_mutex_delay 表示当获得锁失败后,再次去请求锁的间隔时间,这个时间可以在配置文件中设置的
NGX_POST_EVENTS 标记,设置了这个标记就说明当 socket 有数据被唤醒时,我们并不会马上 accept 或者说读取,而是将这个事件保存起来,然后当我们释放锁之后,才会进行 accept 或者读取这个句柄,如果没有这个标记,Nginx 会立即 Accept 或者读取句柄