惊群现象:所有的工作进程都在等待一个socket,当socket客户端连接时,所有工作线程都被唤醒,但最终有且仅有一个工作线程去处理该连接,其他进程又要进入睡眠状态。
Nginx通过控制争抢处理socket的进程数量和抢占ngx_accept_mutex锁解决惊群现象。只有一个ngx_accept_mutex锁,谁拿到锁,谁处理该socket的请求。
如果当前进程的连接数>最大连接数*7/8,则该进程不参与本轮竞争。
while(true)
{
//工作进程抢占锁,抢占成功的进程将ngx_accept_mutex_held变量置为1。拿到锁,意味着socket被放到本进程的epoll中了,如果没有拿到锁,则socket会被从epoll中取出。
if(pthread_mutex_trylock(&ngx_accept_mutex))
{
ngx_accept_mutex_held = 1;
}
else
{
//设置time时间,使得没有拿到锁的worker进程,去拿锁的频繁更高
timer = 500;
ngx_accept_mutex_held = 0;
}
//拿到锁的话,置flag为NGX_POST_EVENTS,这意味着ngx_process_events函数中,任何事件都将延后处理,会把accept事件都放到ngx_posted_accept_events链表中,epollin|epollout事件都放到ngx_posted_events链表中
if (ngx_accept_mutex_held)
{
flags |= NGX_POST_EVENTS;
}
//继续epoll_wait等待处理事件
int num = epoll_wait(epollfd, events, length, timer);
for(int i=0; i<num; ++i)
{
......
//如果是读事件
if (revents & EPOLLIN)
{
//有NGX_POST_EVENTS标志的话,就把accept事件放到ngx_posted_accept_events队列中,把正常的事件放到ngx_posted_events队列中延迟处理
if (flags & NGX_POST_EVENTS)
{
queue = rev->accept ?
&ngx_posted_accept_events:&ngx_posted_events;
ngx_post_event(rev, queue);
}
else//处理
{
rev->handler(rev);
}
}
//如果是写事件
if (revents & EPOLLOUT)
{
//同理,有NGX_POST_EVENTS标志的话,写事件延迟处理,放到ngx_posted_events队列中
if (flags & NGX_POST_EVENTS)
{
ngx_post_event(rev, &ngx_posted_events);
}
else//处理
{
rev->handler(rev);
}
}
//先处理新用户的连接事件
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
//释放处理新连接的锁
if(ngx_accept_mutex_held)
{
pthread_mutex_unlock(&ngx_accept_mutex);
}
//再处理已建立连接的用户读写事件
ngx_event_process_posted(cycle, &ngx_posted_events);
}