Nginx如何解决“惊群”现象

首先解释下什么是“惊群”现象:如果多个工作进程同时拥有某个监听套接口,那么一旦该套接口出现某客户端请求,此时就将引发所有拥有该套接口的工作进程去争抢这个请求,能争抢到的肯定只有某一个工作进程,而其他工作进程注定要无功而返,这种现象即为“惊群”

Nginx解决这种“惊群”现象使用的是负载均衡的策略,接下来先结合Nginx的源码详细介绍下Nginx的这种负载均衡策略。

首先是Nginx如何开启负载均衡策略:当然运行的Nginx要是多进程模型,并且工作进程数目大于1。这很好理解,只有在拥有多个工作进程争抢一个套接口时才会出现惊群现象,也才会需要负载均衡的策略。

if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
        ngx_use_accept_mutex = 1;
        ngx_accept_mutex_held = 0;
        ngx_accept_mutex_delay = ecf->accept_mutex_delay;

    } else {
        ngx_use_accept_mutex = 0;
    }

其中变量ngx_use_accept_mutex就是用于标识是否打开负载均衡策略。这里的负载均衡策略又叫做“前端负载均衡”,因为它用于将客户端请求合理地分配给工作进程的方法;而“后端负载均衡”是用于为处理客户端请求,来合理选择一个后端服务器的策略。

接下来介绍负载均衡策略如何“合理的”将请求分配给工作进程,ngx_process_events_and_timers()有如下代码:

 if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            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 = 1)后才生效。在该逻辑内,首先通过检测变量ngx_accept_diabled值是够大于0来判断当前进程是否已经过载,为什么可以这样判断需要理解变量ngx_accept_diabled值的含义,这在accept()接受新连接请求的处理函数ngx_event_accept()内可以看到。

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

其中ngx_cycle->connection_n表示一个工作进程中的最大可承受连接数,可以通过worker_connections指令配置,其默认值为512.另外一个变量ngx_cycle->free_connection_n则表示当前的可用连接数,假设当前活动连接数为X,那么该值为ngx_cycle->connection_n - X;故此ngx_accept_diabled的值为:

ngx_accept_diabled = X - ngx_cycle->connection_n * 7 / 8; 

也就是说如果当前活动连接数(X)超过最大可承受连接数的7/8,则表示发生过载,变量ngx_accept_diabled的值将大于0,并且该值越大表示过载越大,当前进程的负载越重。

回过去再看函数ngx_process_events_and_timers()内的代码,当进程处于过载状态时,所做的工作仅仅只是对变量ngx_accept_diabled自减1,这表示既然经过了一轮事件处理,那么负载肯定有所减小,所以也相应的调整变量ngx_accept_diabled的值。经过一段时间,ngx_accept_diabled又会降到0以下,便可争取锁获取新的请求连接。所以可以看出最大可承受连接数的7/8是一个负载均衡点,当某工作进程的负载达到这个临界点时,它就不会去尝试获取互斥锁,从而让新增负载均衡到其他工作进程上

如果进程并没有处于过载状态,那么就会去争用锁,当然,实际上是争用监听套接口的监控权,争锁成功就会把所有监听套接口加入到自身的事件监控机制里(如果原本不在);争锁失败就会把监听套接口从自身的事件监控机制里删除(如果原本在)。

变量ngx_accept_mutex_held的值用于标识当前是否拥有锁,注意这一点很重要,因为如果当前拥有锁,则个flags变量打个NGX_POST_EVENTS标记,这表示所有发生的事件都将延后处理。这是任何架构设计都必须遵守的一个约定,即持锁者必须尽量缩短自身持锁的时间。所以把大部分事件延迟到释放锁之后再去处理,把锁尽快释放,缩短自身持锁的时间让其他进程尽可能的有机会获取锁。如果当前进程没有拥有锁,那么就把事件监控机制阻塞点(比如epoll_wait())的超时时间限制在一个比较短的范围内,超时越快,那么也就更频繁地从阻塞中跳出来,也就有更多的机会去争抢到互斥锁。

前面说到当事件到来时,不会立即处理,而是会设置一个延迟处理的标识(NGX_POST_EVENTS)。延迟处理的原理就是当一个事件发生时,会将事件以链表的形式缓存起来。

讲了这么多好像和解决惊群现在没有关系一样,其实上面所讲的内容大体上已经解释了Nginx如何避免惊群现象的原理了。总结下面两点:

第一,如果在处理新建连接事件的过程中,在监听套接字接口上又来了新的请求会怎么样?这没有关系,当前进程只处理已缓存的事件,新的请求将被阻塞在监听套接字接口上,并且监听套接字接口是以水平方式加入到事件监控机制里的,所以等到下一轮被哪个进程争取到锁并加在事件监控机制里时才会触发而被抓取出来。

第二,在进程处理事件时,只是把锁释放了,而没有将监听套接字接口从事件监控机制里删除,所以当处理缓存事件过程中,互斥锁可能会被另外一个进程争抢到并把所有监听套接字接口加入到它的事件监控机制里。因此严格来说,在同一时刻,监听套接字可能被多个进程拥有,但是,在同一时刻,监听套接口只可能被一个进程监控,因此在进程处理完缓存事件之后去争抢锁,发现锁被其他进程占用而争抢失败,会把所有监听套接口从自身的事件监控机制删除,然后才进行事件监控。在同一时刻,监听套接口只可能被一个进程监控,这也就意味着Nginx根本不会受到惊群的影响。

时间: 2024-08-08 13:51:08

Nginx如何解决“惊群”现象的相关文章

Nginx中的惊群现象解决方法

*什么是惊群现象?Nginx中用了什么方法来避免这种问题的发生?本篇就解决这两个问题...→_→* 惊群现象的定义与危害 在Nginx中,每一个worker进程都是由master进程fork出来的.master进程创建socket后进行listen.bind操作,fork出来的worker继承了socket,调用accpet开始监听等待网络连接 如果这时有多个worker进程都在等待事件的发生.当事件发生时,这些worker进程被同时唤醒,但最终只有一个worker进程可以处理事件成功,其他的w

Nginx——事件驱动机制(惊群问题,负载均衡)

事件框架处理流程 每个worker子进程都在ngx_worker_process_cycle方法中循环处理事件,处理分发事件则在ngx_worker_process_cycle方法中调用ngx_process_events_and_timers方法,循环调用该方法就是 在处理所有事件,这正是事件驱动机制的核心.该方法既会处理普通的网络事件,也会处理定时器事件. ngx_process_events_and_timers方法中核心操作主要有以下3个: 1)  调用所使用事件驱动模块实现的proce

Redis 利用锁机制来防止缓存过期产生的惊群现象-转载自 http://my.oschina.net/u/1156660/blog/360552

首先,所谓的缓存过期引起的“惊群”现象是指,在大并发情况下,我们通常会用缓存来给数据库分压,但是会有这么一种情况发生,那就是在一定时间 内生成大量的缓存,然后当缓存到期之后又有大量的缓存失效,导致后端数据库的压力突然增大,这种现象就可以称为“缓存过期产生的惊群现象”! 以下代码的思路,就是利用“锁机制”来防止惊群现象.先看代码: class KomaRedis{ private $redis; //redis对象 private static $_instance = null; private

pthread_cond_signal惊群现象

1.如下代码所示: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> pthread_mutex_t count_lock; pthread_cond_t count_ready; int count; void *decrement_count(void *arg) { while(1) { pthread_mutex_lock(&coun

2、惊群现象

参考文献有 1:https://blog.csdn.net/russell_tao/article/details/7204260 2:https://www.jianshu.com/p/8f362e943e56 什么是“惊群”? 多线程/多进程(linux下线程进程也没多大区别)等待同一个socket事件,当这个事件发生时,这些线程/进程被同时唤醒,就是惊群.可以想见,效率很低下,许多进程被内核重新调度唤醒,同时去响应这一个事件,当然只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新

Accept 惊群现象测试perl脚本

$uname -a Linux debian-11-34 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt9-3~deb8u1 (2015-04-24) x86_64 GNU/Linux 经过测试Debina 8.0 已经解决了Aceept thundering herd https://gist.github.com/kazuho/10436253 # 1) run this script with either "accept" or "se

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

原文:http://blog.csdn.net/russell_tao/article/details/7204260 在说nginx前,先来看看什么是“惊群”?简单说来,多线程/多进程(linux下线程进程也没多大区别)等待同一个socket事件,当这个事件发生时,这些线程/进程被同时唤醒,就是惊群.可以想见,效率很低下,许多进程被内核重新调度唤醒,同时去响应这一个事件,当然只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新休眠(也有其他选择).这种性能浪费现象就是惊群. 惊群通常发

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

在说nginx前,先来看看什么是“惊群”?简单说来,多线程/多进程(linux下线程进程也没多大区别)等待同一个socket事件,当这个事件发生时,这些线程/进程被同时唤醒,就是惊群.可以想见,效率很低下,许多进程被内核重新调度唤醒,同时去响应这一个事件,当然只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新休眠(也有其他选择).这种性能浪费现象就是惊群. 惊群通常发生在server 上,当父进程绑定一个端口监听socket,然后fork出多个子进程,子进程们开始循环处理(比如acce

Nginx源码分析 - 主流程篇 - 多进程的惊群和进程负载均衡处理

Linux2.6版本之前还存在对于socket的accept的惊群现象.之后的版本已经解决掉了这个问题. 惊群是指多个进程/线程在等待同一资源时,每当资源可用,所有的进程/线程都来竞争资源的现象. Nginx采用的是多进程的模式.假设Linux系统是2.6版本以前,当有一个客户端要连到Nginx服务器上,Nginx的N个进程都会去监听socket的accept的,如果全部的N个进程都对这个客户端的socket连接进行了监听,就会造成资源的竞争甚至数据的错乱.我们要保证的是,一个链接在Nginx的