Nginx下惊群现象的处理

来源:互联网 发布:正版凯立德导航软件 编辑:程序博客网 时间:2024/06/05 16:20

Nginx(“engine x”)是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器。它可以托管网站,进行Http服务处理,也可以作为反向代理服务器使用。

1.Nginx的优点:

1.处理静态文件,索引文件以及自动索引;打开文件描述符缓冲。2.无缓存的反向代理加速,简单的负载均衡和容错。    

Nginx具有很高的稳定性。其他HTTP服务器,当遇到访问的峰值,或者有人恶意发起慢速连接时,可能会导致服务器物理内存耗尽频繁交换,失去响应,只能重启服务器。例如当前apache一旦上到200个以上进程,web响应速度就明显非常缓慢。而Nginx采取了分阶段资源分配技术,使得它的CPU与内存占用率非常低。Nginx官方表示保持10000个没有活动的链接,只占2.5M内存。
Nginx支持热部署。启动特别容易,并且几乎可以做到7*24不间断运行,即使运行数个月也不需要重新启动。你还可以在不间断服务的情况下,对软件版本进行升级。
Nginx采用master-slave模型,能够充分利用SMP的优势,且能够减少工作进程在磁盘I/O的阻塞延迟。当采用select()/poll()调用时,还可以限制每个进程的连接数。

2.Nginx负载均衡
负载均衡是为了解决有可能一个进程处理了多个连接,因此就需要让多进程更平均的处理连接。
Nginx的upstream目前支持4种方式的分配
1) 轮询(默认)
每个请求按时间顺序逐一分配到不同的后端,如果后端服务器down掉,能自动剔除。
2) weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
3) ip_hash
每个请求按访问ip的hash结果分配,如果每个访客固定访问一个后端服务器,可以解决session的问题。
4) fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
5) url_hash(第三方)
按访问url的hash结果来分配请求,是每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

3.Nginx中惊群的处理
惊群现象:如果多个工作进程同时拥有多个监听套接口,那么一但该套接口出现客户端请求,此时就将引发所有拥有该套接口的工作进程去争抢这个请求(accept),能争抢到的肯定只有某一个工作进程,而其他工作进程注定要无功而返,这种现象称为“惊群”。

在看代码之前,先来看ngx_use_accept_mutex这个变量,如果有个变量,说明nginx有必要使用accept互斥体,这个变量的初始化在ngx_event_process_init中。

这里还有两个变量,一个是ngx_accept_mutex_held,一个是ngx_accept_mutex_delay,其中前一个表示当前是否以及持有锁,后一个表示,当获得锁失败后,再次去请求锁的间隔时间,这个时间可以看到,在配置文件中设置的。

//如果使用了master worker,并且worker个数大于1,并且配置文件里面有设置使用accept_mutex的话,设置ngx_use_accept_mutexif(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_accept_disabled,这个变量是一个阈值,如果大于0,说明当前的进程处理的连接过大。
下面是这个值的初始化,可以看到初始值是全部连接的7/8(注意是负值0)

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

然后来看ngx_process_events_and_timers中的处理。

//如果有使用mutex,则才会进行处理。if (ngx_use_accept_mutex) {//如果大于0,则跳过下面的锁的处理,并减一。        if (ngx_accept_disabled > 0) {            ngx_accept_disabled--;        } else {//试着获得锁,如果出错则返回。        if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {            return;        }//如果ngx_accept_mutex_held为1,则说明已经获得锁,此时设置flag,这个flag后面会解释。        if (ngx_accept_mutex_held) {            flags |= NGX_POST_EVENTS;        } else {//否则,设置timer,也就是定时器。接下来会解释这段。            if (timer == NGX_TIMER_INFINITE                || timer > ngx_accept_mutex_delay)            {                timer = ngx_accept_mutex_delay;            }        }    }}

然后先来看NGX_POST_EVENTS标记,设置了这个标记就说明当socket有数据被唤醒时,我们并不会马上accept或者说读取,而是将这个事件保存起来,然后当我们释放锁之后,才会进行accept或者读取这个句柄。

//如果ngx_posted_accept_events不为NULL,则说明有accept event需要nginx处理。if (ngx_posted_accept_events) {        ngx_event_process_posted(cycle, &ngx_posted_accept_events);    }

而如果没有设置NGX_POST_EVENTS 标记的话,nginx会立即accept或者读取句柄。

然后是定时器,这里如果nginx没有获得锁,并不会马上再去获得锁,而是设置定时器,然后再epoll休眠(如果没有其他的东西唤醒)。此时如果有链接到达,当前休眠进程会被提前唤醒,然后立即accept。否则,休眠ngx_accept_mutex_delay时间,然后继续try lock.

最后是个核心函数,那就是ngx_trylock_accept_mutex.这个函数尝试获得accept mutex.

ngx_int_tngx_trylock_accept_mutex(ngx_cycle_t *cycle){//尝试获得锁    if (ngx_shmtx_trylock(&ngx_accept_mutex)) {    //如果本来已经获得锁,则直接返回Ok        if (ngx_accept_mutex_held            && ngx_accept_events == 0            && !(ngx_event_flags & NGX_USE_RTSIG_EVENT))        {            return NGX_OK;        }        //到达这里,说明重新获得锁成功,因此需要打开被关闭的listening句柄。        if (ngx_enable_accept_events(cycle) == NGX_ERROR) {            ngx_shmtx_unlock(&ngx_accept_mutex);            return NGX_ERROR;        }        ngx_accept_events = 0;        //设置获得锁的标记。        ngx_accept_mutex_held = 1;        return NGX_OK;        }//如果我们前面已经获得了锁,然后这次获得锁失败,则说明当前的listen句柄已经被其他的进程锁监听,因此此时需要从    if (ngx_accept_mutex_held) {        if (ngx_disable_accept_events(cycle) == NGX_ERROR) {            return NGX_ERROR;        }    //设置锁的持有为0.    ngx_accept_mutex_held = 0;    }    return NGX_OK;}

这里可以看到大部分情况下,每次只会有一个进程在监听listen句柄,而只有当ngx_accept_disabled大于0的情况下,才会出现一定程度的惊群。

而nginx中,由于锁的控制(以及获得锁的定时器),每个进程都能相对公平的accept句柄,也就是比较好的解决了子进程负载均衡。

原创粉丝点击