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
不要轻轻地离开我,请留下点什么...