起因:
最近,一直在学习nginx。对于事件模块所解决的”惊群“现象完全不明白,遂产生了此文。
套接字:
可以将服务器端的套接字分为 监听套接字 与 连接套接字。 监听套接字负责等待用户连接,而通过accept,获得一个新的套接字,
该套接字为连接套接字,通过连接套接字与用户传送数据。
何谓惊群:
nginx 分为 master 进程 与 worker 进程,worker 进程其实就是一些子进程。当每个子进程都持有监听套接字时,假如某一时刻,
没有任何连接请求,所有子进程都处于休眠状态等待用户连接。突然,来了一个请求,此时,操作系统不知道该唤醒哪一个进程
来处理这个请求,索性全部唤醒。然后,只有一个进程获得处理权,取得连接套接字,其它进程都将失败,这导致资源的浪费。
现象重现:
测试版本
Linux controller 3.2.0-20-generic #33-Ubuntu SMP Tue Mar 27 16:42:26 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
测试结果
开启了8个工作进程,当到来一个请求时,惊醒了4个进程,最终,只有一个进程获得连接。
代码:
这里:http://download.csdn.net/detail/spch2008/6850183
centos: centos 环境下不是很明显。
[root@localhost alarmall]# uname -a
Linux localhost.localdomain 2.6.32-358.6.2.el6.x86_64 #1 SMP Thu May 16 20:59:36 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
可能需要连续运行几次client程序。
总结-》并不是将所有工作进程全部唤醒,而只是唤醒了一部分。
2016-02-01 更正:
进程没有全部唤醒,因为某个进程已经处理完这个accept,内核队列上已经没有这个事件,无需唤醒其他进程。(多谢 开心小熊)
修改代码,进程不要accept,这样,全部进程唤醒。
memset(&event, 0, sizeof(struct epoll_event));
event.events |= EPOLLIN;
event.events |= EPOLLET; // 边缘
event.data.fd = sfd;
/* add listening sfd */
res = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
if (res == -1) {
perror("ERROR : epoll_ctl EPOLL_CTL_ADD\n");
return -1;
}
for ( ; ; ) {
nfds = epoll_wait(efd, events, MAX_EVENTS, -1);
for ( i = 0; i < nfds; i++) {
if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)) {
close(events[i].data.fd);
printf("socket error, fd = %d\n", events[i].data.fd);
continue;
} else if (events[i].data.fd == sfd) {
printf("process wake up, pid = %d\n", getpid());
continue; //直接跳出
while (1) {
addrlen = sizeof(struct sockaddr);
nginx解决办法:
nginx每次只将监听套接字放于一个进程中,这就避免了”惊群“现象,同一时刻,只有一个进程处于等待用户连接的状态,
其它进程都处理已经建立的连接。监听套接字被几个工作进程轮流持有,采用负载均衡,当一个进程过载后,放弃监听
套接字,由其它进程持有,而放弃的进程只能处理已经建立的连接。这样,就很好的解决了”惊群“现象。
ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
ngx_shmtx_unlock(&ngx_accept_mutex);
return NGX_ERROR;
}
ngx_accept_mutex_held = 1;
return NGX_OK;
}
if (ngx_accept_mutex_held) {
if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
return NGX_ERROR;
}
ngx_accept_mutex_held = 0;
}
return NGX_OK;
}
首先获得互斥锁,即只有一个进程可以得到监听套接字,当获取成功后,进入ngx_enable_accept_events,将监听
套接字加入epoll(采用epoll)中。没有获得锁,而上一次获得过,还没有释放,则进行释放ngx_disable_accept_events。
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
}
}
只有在ngx_accept_disabled 小于0 的时候,才去获取互斥锁,也就是说,当进程处理连接的数量达到上限,不在去尝试
获取监听套接字,而是处理已经获取的连接。
ngx_event.c->ngx_event_core_module->ngx_event_process_init->ngx_event_accept:
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
阈值为连接总数的7/8.
参考:
惊群:http://blog.163.com/pandalove@126/blog/static/9800324520122633515612/
套接字:http://www.cnblogs.com/newlist/archive/2012/02/19/2358469.html
nginx:《深入理解nginx模块开发与架构解析》