首页 > 代码库 > Nginx源码分析—过期事件和惊群事件的处理
Nginx源码分析—过期事件和惊群事件的处理
Nginx源码分析—过期事件和惊群事件的处理
过期事件:每个事件的date域都是一个结构体ngx_connection_t结构体,表示对应的连接。对于一个结构体struct epoll_event 中的data.ptr成员存储的是ngx_connection_t连接,这里使用Instance标志位来标识,下面就配合ngx_epoll_process_events方法说明他的用法。
Data.ptr (void* )((uintptr_t) c | ev->instance); 这事添加事件的使用对ptr进行的初始化。
那么在检查的时候怎么使用它呢?
遍历epoll_wait返回的所有事件,对照上面提到的ngx_epoll_add_event方法,可以看到ptr成员就是ngx_connection_t连接的地址,但最后一位有特殊含义,需要把他屏蔽到。
C= event_list[i].data.ptr;
//将地址的最后一位取出来,用Instace变量标识
Instance= (uintptr_t) c &1;
/*五路是32位还是64位机器,其地址的最后一位肯定是0.可以用下面的这行语句把ngx_connection_t的地址还原到真正的地址值*/
C = (ngx_connection_t*) ((uintptr_t) c& (uintptr_t) ~1);
//取出事件
Rev= c->read;
//判断这个度事件是否为过期事件
If(c->fd== -1 || wev->instance != instance)
{
//当fd套接字描述符为-1 或者Instance标志位不相等是,表示这个事件已经过期了,不用处理
Instance标志位为什么可以判断事件是否过期?从上面的代码可以看出,instance标志位的使用其实很简单,它利用了指针的最后一位是0这个特性,既然最后一位始终为0,那么不用不如用来表示Instance,这样,在使用ngx_epoll_add_event方法向epoll中添加事件时,就把epoll_event中联合成员data的ptr成员指向ngx_connection_t连接的地址,同时把最后一位置为这个事件的Instance标志。而在ngx_epoll_process_events方法中取出指向连接的ptr地址时,先把最后一位instance取出来,再把ptr还原成正常的地址赋给ngx_connection_t连接,这样,Instance究竟放在何处的问题也就解决了
那么,过期事件又是怎么回事呢?举个例子,epoll_wait一次放回3个事件,在第一个事件的处理过程总,由于业务的需要,所以关闭了一个连接,而这个连接恰好对应第三个事件,这样的话,在处理到第三个事件时,这个时间就已经是过期事件了,一旦处理必然出错。既然如此,把关闭的这个连接的fd套接字置为-1能解决问题么?答案是不能处理所有出错。
假设第三个事件对应的Ngx_connection_t连接中的fd套接字原先是50,处理第一个事件时把这个连接的套接字关闭了,同时置为-1,并且调用ngx_free_connection将该连接归还给连接池,在Ngx_epoll_process_events方法的循环中开始处理第二个事件,恰好第二个事件时建立新连接诶事件,调用ngx_get_connection从连接池中取出的连接非常可能及时刚才释放的第三个事件对应的连接,由于套接字50刚刚被释放,Linux内核非常有可能把刚刚释放的套接字50又分配给新建立的连接。因此,在循环中处理第三个事件时,这个事件就是过期了,它对应的事件时关闭的连接,而不是新建立的连接。
如何解决这个问题?依靠Instance标志位,当调用ngx_get_connection从连接池中获取一个新连接时,Instance标志位就会置反,
Ngx_get_connection(s,log)
{
Rev = c->read;
Wev = c->write;
Instane = rev->instance;
//将instance 标志位置为原来的相反值
Rev->instance = !instance;
Wev->instance = !instance;
}
这样,当这个ngx_connection_t连接重复使用时,他的instance标志位一定是不同的。因此,在ngx_epoll_process_events方法中一旦判断instance发生了变化,就认为这是过期事件而不处理。
Nginx源码分析—过期事件和惊群事件的处理