leevis.com
leevis.com copied to clipboard
nginx 网络事件和epoll
概述
本篇只分析epoll相关的网络事件。涉及到epoll的EPOLLIN|EPOLLOUT|EPOLLET这几个事件。 epoll事件触发分为水平触发和边沿触发,水平触发是缓存区有内容时,或缓存区可写时会一直触发。而边沿触发是缓存区从无内容变成有内容,或缓存区从不可写变成可写时触发一次。
- 相关常数
EPOLLIN 可读,EPOLLOUT可写,EPOLLET边沿触发。
代码分析
-
首先会调用
ngx_add_event(rev, NGX_READ_EVENT, 0)
把监听fd的可读事件添加到epoll中,flags为0,epoll默认是水平触发。ngx_add_event 是个函数指针,指向ngx_epoll_add_event函数。 -
当有连接建立时,nginx调用epoll_wait监听到上述添加的事件可读,最终调用该可读事件的回调函数ngx_event_accept。
-
ngx_event_accept函数会调用accept接受连接,调用 ls->handler(c); 该函数为ngx_http_init_connection,在ngx_http_block中赋的值。该函数对刚建立连接fd的可读可写事件的回调函数进行赋值,分别是ngx_http_wait_request_handler和ngx_http_empty_handler,然后调用ngx_add_timer添加超时定时,调用ngx_handle_read_event把可读事件添加到epoll中。
-
处理可读事件时,会一直调用ngx_http_read_request_header,该函数又会调用ngx_unix_recv函数,该函数有可读事件会一直读,直到读到没有缓存区为空直到返回eagain才把可读事件的ready赋0。
nginx 处理惊群
- 描述
自从支持了reuseport后,nginx采用锁方式解决惊群已经不再使用了。支持了reuseport后,Linux已经在内核层面解决了惊群问题。请参考《nginx reuseport 实现》。
但是nginx为了解决跨平台问题,和Linux老版本问题并没有删除该功能。相信其他类unix系统会跟进Linux在内核层面解决惊群问题,而到时候nginx的惊群就可能会删除了(win系统下nginx自身也不支持解决惊群)。
- 代码
在每个工作进程中调用ngx_worker_process_init初始化函数时,会调用所有模块的init_process函数。而监听的fd就是在事件模块的ngx_event_process_init函数被添加进去的(没有开启accept_mutex或开启了reuseport)。
在ngx_event_process_init函数中, worker进程大于1,且开启了accept_mutex。则ngx_use_accept_mutex=1; 否则ngx_use_accept_mutex=0;
如果是在win系统下,ngx_use_accept_mutex=0; 在win下,无论开启不开启accept_mutex,都没用。而开启reuseport后,就不会把监听的fd添加到epoll中了。疑问:不添加到epoll中该fd就永远也不会被处理。。。那什么时候添加到epoll中的呢???
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg;
#if (NGX_HAVE_REUSEPORT)
if (ls[i].reuseport) {
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
continue;
}
#endif
if (ngx_use_accept_mutex) {
continue;
}
#if (NGX_HAVE_EPOLLEXCLUSIVE)
if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
&& ccf->worker_processes > 1)
{
if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)
== NGX_ERROR)
{
return NGX_ERROR;
}
continue;
}
#endif
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
#endif
工作进程循环调用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_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; 如果开启了accept_mutex,则ngx_accept_disabled变量大于0(空闲链接不足总链接的1/8)则该进程不进行尝试accept,否则会调用ngx_trylock_accept_mutex函数。该函数会获取一个全局的锁,获取到锁则调用ngx_enable_accept_events函数把监听fd添加到epoll中。否则没获取到锁则把监听的fd从epoll中移除后返回。
然后调用epoll_wait(ngx_process_events)处理一次。后调用ngx_shmtx_unlock释放trylock加上的锁。
自从支持了reuseport后,nginx采用锁方式解决惊群已经不再使用了。支持了reuseport后,Linux已经在内核层面解决了惊群问题。请参考《nginx reuseport 实现》。
reuseport 怎么能解决惊群呢?求nginx官方文档出处; ps:已亲测reuserport之后,epoll_wait依旧惊群;