首页 > 代码库 > Nginx处理stale事件机制分析

Nginx处理stale事件机制分析

Nginx为提高效率采用描述符缓冲池(连接池)来处理tcp连接,一个连接对应一个读事件和一个写事件,nginx在启动的时候会创建好所用连接和事件,当事件来的时候不用再创建,然而连接池的使用却存在stale事件的问题,以下将详细分析Nginx是如何处理stale事件的,该问题涉及到epollNginx连接与事件的相关知识。

1      Epoll的实现原理

epoll相关的系统调用有:epoll_create, epoll_ctlepoll_waitLinux-2.6.19又引入了可以屏蔽指定信号的epoll_wait: epoll_pwait。至此epoll家族已全。其中:

aepoll_create用来创建一个epoll文件描述符,

bepoll_ctl用来添加/修改/删除需要侦听的文件描述符及其事件,在ngx_epoll_add_event/ngx_epoll_del_event/ngx_epoll_add_connection/ngx_epoll_del_connection中使用

cepoll_wait/epoll_pwait接收发生在被侦听的描述符上的,用户感兴趣的IO事件,在ngx_epoll_process_event

depoll文件描述符用完后,直接用close关闭即可。事实上,任何被侦听的文件符只要其被关闭,那么它也会自动从被侦听的文件描述符集合中删除。

1epollselect相比,最大不同在于:

1epoll返回时已经明确的知道哪个sokcet fd发生了事件,不用再一个个比对。这样就提高了效率。

2selectFD_SETSIZE是有限止的,而epoll是没有限止的只与系统资源有关。

 

2EpollNginx中的使用:

     1ngx_epoll_init通过epoll_create函数创建了epfd句柄,这是epoll的接口,之后所有的函数调用都要使用该epfd句柄。

     2)之后在ngx_event_process_init中,通过epoll_ctl将监听套接字和其对应的事件类型添加到epfd句柄中。

     通过这两步,就完成了epoll事件处理的三部曲中的前两部,接下来就是由epoll_wait等待事件的发生。

 

2      Nginx的连接与事件

Nginx中事件不需要创建,因为在Nginx启动时初始化ngx_cycle_t的过程中就分配了所有读、写事件的空间。这样,每个连接就对应了一个写事件和一个读事件。在Nginx中定义了两种连接:ngx_connection_t:被动连接,由客户端主动发起。ngx_peer_connection_t:主动连接。用于主动和上游服务器进行通信。当Nginx服务器接和客户端建立连接后,就会获得一个ngx_connection_t实体。和事件一样,连接不需要额外创建,它是从ngx_connection_t连接池中获得的,连接池在Nginx启动阶段就已经分配好了,由ngx_cycle_t结构体的connections成员和free_connections成员共同管理,free_connections相当于一个栈,每次从链表头插入,从链表头取(先进后出)。

3 如何处理stale事件

Nginx连接的文件描述符状态转移如下图所示

 

stale event(过期事件)有两种情况:

第一:在处理状态2的描述符集合前面的描述符时,将此组中后面的描述符也关闭了,那么后面的那个描述符就应该进入到了状态1,也就是空闲的描述符组,但是它仍然还存在于状态2集合中,因为它是由一次的epoll_wait返回的,我们没有办法在下次系统调用epoll_wait之前将它从此组中剔除。

在处理状态2的描述符的后续循环中仍然会处理它。此时就需要一个标志来标识此时的描述符状态:虽然仍在状态2的集合里面,但实际上已经进入状态1的状态了,通过将状态2集合中的fd置为-1(也就是starvation中提到的解决方法),来标识状态2集合内的描述符实际上已经回到了状态1的集合,在顺序处理的过程中,检查此标识,如果fd-1那么就跳过不进行处理了。

第二、在状态2的描述符中有:#1, #2, ….. #40, ….,在处理#2的时候将#40关闭了,此时继续向下处理,但是还没到#40,此时又有新情况发生了,又来了新的连接,accept函数为他分配了新的描述符,恰好是#40,那么我们刚刚已经将#40的描述符的fd置为-1,现在它又被accept置为有效的值了,实际上已经进入了状态3。实际上此时的#40应该在下次epoll_wait的时候才返回,但是它现在还在我们的二组中,而还没处理到呢,一旦开始处理的时候,是不应该进行处理的,但是现在无法判断。

此种情况需要另一个标志instance来标识状态2和状态3的区别。在ngx_get_connection函数中获得一个新的connection的时候,将instance用上次值的反来初始化,假如此次初始化后的值为一bit位的x

instance = rev->instance;

 

    ngx_memzero(rev, sizeof(ngx_event_t));

    ngx_memzero(wev, sizeof(ngx_event_t));

 

    rev->instance = !instance;

    wev->instance = !instance;

 epoll中添加(add_event)和删除(del_event),对应着描述符从状态1到状态3和从状态2或者状态3到状态1转换中,用event.data.ptr的最后一比特记录这个instance的值,这时这个值还是x

ee.events = events | (uint32_t) flags;

    ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);

 

ngx_epoll_process_events中,c->fd == -1是第一种情况, rev->instance != instance是指第二种情况,instance代表是ngx_epoll_add_event将该事件添加到epoll中时所记录的值,rev->instance代表该连接connection自己的值,正常情况下,获得连接时rev->instance是刚释放连接取反,它先经过epoll_ctl添加进epoll时被记录,然后在处理事件的epoll_wait的返回后,取出instance,此时它与rev->instance是相等的,然而在第二种情况下发生了变化,重新获得了文件描述符#40的连接,rev->instance立刻取反,而此时添加动作还没来得及进行,即epoll_ctl的添加动作未发生,即这个新获得连接的标识没有被记录,此时的instance仍然是#40的第一次连接保持的值,当然两者是不相等,只有等到新连接被添加后,再次进行epoll_wait时instance才会被更新为rev->instance,即两者相等,才可以被处理,而本次的epoll_wait将跳过该事件。

for (i = 0; i < events; i++) {

        c = event_list[i].data.ptr;

 

        instance = (uintptr_t) c & 1;

        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);

 

        rev = c->read;

 

        if (c->fd == -1 || rev->instance != instance) {

 

            /*

             * the stale event from a file descriptor

             * that was just closed in this iteration

             */

 

            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,

                           "epoll: stale event %p", c);

            continue;

        }

参考:http://blog.csdn.net/nestler/article/details/37570401

          http://wap.blog.163.com/w2/blogDetail.do?u=http://blog.163.com/lnwdl/blog/static/3883041220132721710725