Nginx源代码分析之群惊问题(十七)

    接上一节,由于nginx的I/O模型框架是从listen开始处理异步通知,因此存在当同一个connect到达os的内核并完成三次握手,会同时唤醒多个work process来处理这个异步通知。


    实际上,根据相关资料,Linux内核本身已经解决了群惊的问题,当多个线程在阻塞模式同时调用一个socket的accept并阻塞,当有请求到来的时候,只有一个进程的accept调用成功并返回,其他进程继续阻塞在accept上面并保持睡眠状态。


     但由于nginx使用的是异步I/O的统一框架,并提供多个I/O模型,像select,epoll,queue这样的unix平台下的模型在异步通知这方面仍然存在群惊的情况,因此nginx必须处理这些情况,另外需要注意的是,win平台的overlap,iocp并不存在群惊的情况,因为这2个模型可以投递异步accept,每个connect仅会唤醒一个异步accept。但为了统一网络框架,也只好按unix的方式统一处理。

这里有个全局变量ngx_use_accept_mutex用于控制是否开启“防群惊”模式,同时有一个

在ngx_event_process_init中,会首先判断是否开启 防群惊模式,如果未开启,则直接注册ngx_event_accept。代码如下

#if (NGX_WIN32)
        if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
            ngx_iocp_conf_t  *iocpcf;
            rev->handler = ngx_event_acceptex;
            if (ngx_use_accept_mutex) {
                continue;
            }
            if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) {
                return NGX_ERROR;
            }

            ls[i].log.handler = ngx_acceptex_log_error;

            iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module);
            if (ngx_event_post_acceptex(&ls[i], iocpcf->post_acceptex)
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }

        } else {
            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;
            }
        }
#else
        rev->handler = ngx_event_accept;
        if (ngx_use_accept_mutex
#if (NGX_HAVE_REUSEPORT)
            && !ls[i].reuseport
#endif
           )
        {
            continue;
        }
        if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }
#endif

     如果开启,这里不会直接注册accept的回调:ngx_event_accept,而是在work process的进程函数的ngx_process_events_and_timers里面有一个获取互斥锁的操作,关键点是获取互斥锁的函数ngx_trylock_accept_mutex,代码如下

ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
    if (ngx_shmtx_trylock(&ngx_accept_mutex)) {


        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "accept mutex locked");


        if (ngx_accept_mutex_held && ngx_accept_events == 0) {
            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;
    }


    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "accept mutex lock failed: %ui", ngx_accept_mutex_held);


    if (ngx_accept_mutex_held) {
        if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }


        ngx_accept_mutex_held = 0;
    }


    return NGX_OK;
}


   此函数 首先调用ngx_shmtx_trylock获取互斥锁,ngx_shmtx_trylock是异步版的,不管是否成功都会立即返回,不会造成进程/线程的阻塞,它的阻塞版是ngx_shmtx_lock,后来章节再详细介绍区别。如果成功拿到互斥锁,当前进程就获得了调用accept的权限,接着,判断ngx_accept_mutex_held为1和ngx_accept_events 为0,这2个条件都为真说明当前进程之前就是互斥锁的持有者,不需要其他操作直接返回NGX_OK,否则调用ngx_enable_accept_events,遍历所有处于listen状态的socket,为每个连接设置异步读通知,然后直接返回。

    如果获取互斥锁失败,而且ngx_accept_mutex_held的值为1,则调用ngx_disable_accept_events取消当前进程对每个socket的异步通知状态。

    ngx_trylock_accept_mutex返回之后,如果调用成功并且持有互斥锁,给flags添加标志NGX_POST_EVENTS,随后调用ngx_process_events,前面章节已经介绍过这是真正执行异步调用的函数,如果flags包含NGX_POST_EVENTS,ngx_process_events会获取的所有已经就绪的I/O请求放到等待队列中。代码如下:

        if ((revents & EPOLLIN) && rev->active) {


#if (NGX_HAVE_EPOLLRDHUP)
            if (revents & EPOLLRDHUP) {
                rev->pending_eof = 1;
            }
#endif


            rev->ready = 1;


            if (flags & NGX_POST_EVENTS) {
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;


                ngx_post_event(rev, queue);


            } else {
                rev->handler(rev);
            }
        }

其中accept单独放到ngx_posted_accept_events队列,其他读写请求放到ngx_posted_events队列。

然后立即处理调用ngx_event_process_posted处理异步accept请求,完成后释放accept的互斥锁。

    ngx_event_process_posted(cycle, &ngx_posted_accept_events);


    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }


这样做的原因是当前进程在获取accept锁之后应该尽快调用accept()并释放锁,以便让其他进程尽快accept互斥锁,避免降低整个服务器接受连接的效率。释放之后再调用ngx_event_process_posted处理缓存在等待队列中的其他读写请求。


最后说说ngx_accept_disabled,这个全局变量用来实现work process的简单负载均衡的功能。每次成功调用accept之后会重置这个值


        ngx_accept_disabled = ngx_cycle->connection_n / 8
                              - ngx_cycle->free_connection_n;


        c = ngx_get_connection(s, ev->log);


        if (c == NULL) {
            if (ngx_close_socket(s) == -1) {
                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
                              ngx_close_socket_n " failed");
            }


            return;
        }

    ngx_cycle->free_connection_n表示当前进程剩余可用连接,当ngx_cycle->free_connection_n小于1/8最大连接的时候,即ngx_accept_disabled>0的时候,说明进程比较繁忙,当前进程会放弃获取accept互斥锁,并将ngx_accept_disabled减少1,直到减少到<=0,进程最终会得到获取accept锁的机会,避免繁忙进程陷入一直无法获取accept锁的状态。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值