Nginx 使用进程级锁和延迟 accept() 等逻辑,以解决 epoll/kqueue 存在的
thundering herd 问题,并在各 worker 进程间尽量平衡负载 (正常连接)。
directives
-
accept_mutex- nginx uses accept mutex to serializeaccept()syscalls. -
accept_mutex_delay- If a worker process does not have accept mutex it will try to acquire it at least after this delay. By default delay is 500ms. -
multi_accept- tries toaccept()as many connections as possible after nginx gets notification about a new connection.
variables and functions
-
ngx_event_accept()- 实际完成accept()工作的函数。--------event/ngx_event.c:822-------------- rev->handler = ngx_event_accept; if (ngx_use_accept_mutex) { continue; } ... if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } -
ngx_use_accept_mutex- 基本上和配置项accept_mutex相对应。-------event/ngx_event.c:593--------------- 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_accept_disabled-worker进程是否可以开始竞争accept_mutex锁。--------event/ngx_event_accept.c:81--------- ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; --------event/ngx_event.c:200--------------- void ngx_process_events_and_timers(ngx_cycle_t *cycle) { ... 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_process_events(cycle, timer, flags); ... if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); } ... } ---------event/ngx_event_accept.c:268----------- ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) { if (ngx_shmtx_trylock(&ngx_accept_mutex)) { ... if (ngx_accept_mutex_held && ngx_accept_events == 0 && !(ngx_events & NGX_USE_RTSIG_EVENT)) { return NGX_OK; } 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; } ... if (ngx_accept_mutex_held) { if (ngx_disable_accept_events(cycle) == NGX_ERROR) { return NGX_ERROR; } ngx_accept_mutex_held = 0; } return NGX_OK; }
conclusion
-
worker进程竞争到accept_mutex后,方可开始监听listening sockets上的读 事件 (通过调用函数ngx_enable_accept_events完成)。在任何一个时刻,只有一个worker进程允许处理listening sockets上的读事件,接收新的连接。 -
worker进程在空闲连接数free_connection_n大于1/8 * connection_n时,或 者多次主循环 (ngx_process_events_timers) 使ngx_accept_diabled自减为非正整 数时,可以开始竞争accept_mutex。竞锁结果由ngx_accept_mutex_held表示。 -
worker进程竞锁失败后,需马上放弃监听listening sockets上的读事件。并且在ngx_accept_mutex_delay时长后,可再次尝试竞锁,并且竞锁的频度增加。 -
worker进程竞锁成功后,开始监听listening sockets上的读事件,接入新的连接。 直到某次竞锁失败时,才放弃获取listening sockets上的读事件 (ngx_disable_accept_events)。 -
accept_mutex打开后,在某个时刻,可能会有两个worker进程的listening sockets正处于epoll set中:刚刚处理完事件并释放accept_mutex的worker进程 和 刚刚竞锁成功的worker进程。但是刚刚释放accept_mutex的worker进程在下一次竞锁如果失败的话,就会将其listening sockets从epoll set中去除,所以,它其它并不会再次获取到listening sockets上的读事件。 -
所有
worker进程的ngx_accept_disabled都大于 0 时,都需等待其自减到非正整 数后,方可再次竞锁。此期间,所有worker进程 (除了最后那个拥有accept_mutex的worker进程外) 均不再接入新连接。也就是说,在总连接数超过所有worker进程连接数上限之和的7/8后 (同时需要最后那个拥有accept_mutex的worker进程连接数已达上限。因为在任一时刻,Nginx 保证有且只有一个worker进程正在监听listening sockets的读事件),Nginx 接入新连接速度将变缓,新连接失败率增加,响应 时间增加。 RIGHT? -
启用
epoll和multi_accept机制后,ngx_event_accept会一直接收新连接 (如 果有的话),直到本进程连接数到达上限 (connection_n为止)。 -
各连接在
worker进程中,使用上述逻辑无法达到均分 (设想新连接到达速度不均衡的 情况下,某个worker可以始终拥有大部分连接)。但是,这种情况比较极端。
Comments
不要轻轻地离开我,请留下点什么...