首页 > nginx, 源码分析 > nginx的超时处理

nginx的超时处理

2011年9月9日 发表评论 阅读评论 9,285 次浏览

请关注最新修正合订:http://lenky.info/ebook/
这一系列的文章还是在09年写的,存在电脑里很久了,现在贴出来。顺序也不记得了,看到那个就发那个吧,最近都会发上来。欢迎转载,但请保留链接:http://lenky.info/,谢谢。
nginx对于是否存在有超时事件的处理很巧妙。首先,nginx利用红黑树来组织那些等待处理的并且需要关注其是否超时的事件对象(以下称该红黑树为事件计时红黑树:event_timer_rbtree);其次,nginx提供了两种方案来检测那些等待处理的事件对象是否已经超时,一种为常规的定时检测机制,也就是设置定时器,每过一定的时间就对红黑树管理的所有事件对象进行一次超时检测;另一种是过距离当前最快发生超时的事件对象的时间就进行一次超时检测,这样叙述可能不是很清楚,看完下面的介绍,读者就会理解。

我们已经知道nginx把事件封装在一个名为ngx_event_s的结构体内,而该结构体有几个字段与本节讲的nginx超时处理相关。
unsigned         timedout:1;
unsigned         timer_set:1;
ngx_rbtree_node_t   timer;
timedout域字段,用于标识该当前事件是否已经超时,0为没有超时。
timer_set域字段,用于标识该当前事件是否已经加入到红黑树,需要对其是否超时做监控,0为没有加入。
tmer字段,很容易看出属于红黑树节点类型变量,红黑树就是通过该字段来组织事件对象。

nginx设置了两个全局变量以便在程序的任何地方都能快速的访问到事件计时红黑树:
ngx_thread_volatile ngx_rbtree_t  ngx_event_timer_rbtree;
static ngx_rbtree_node_t  ngx_event_timer_sentinel;
它们都定义在源文件ngx_event_timer.c内,结构体ngx_rbtree_s类型的全局变量ngx_event_timer_rbtree封装了事件计时红黑树树结构,而ngx_event_timer_sentinel属于红黑树节点类型变量,在红黑树的操作过程中当作哨兵使用,同时它是static的,所以作用域仅限于模块内。

在nginx事件模块ngx_event_core_module的进程初始化回调函数ngx_event_process_init内将通过ngx_event_timer_init函数进行事件计时红黑树的初始化工作,初始化之后的事件计时红黑树图示描述如下所示:

当nginx需要对某些事件进行超时监控时,就将该事件加入到该事件计时红黑树。那在哪些地方需要进行这种超时监控处理呢?这样的地方很多,举个具体例子来说,当客户端对nginx发起请求连接时,nginx就会建立对应的连接对象connection并要读取客户端请求的头部信息,而读取这个头部信息并不是不限时的,如果在一个有限的时间内没有读取到头部信息或头部信息不完整,那么nginx就应该认为这是一个非法的请求,返回错误信息(”Request time out” (408)),从而释放相应的资源。很显然,这儿就是一个需要进行超时处理的地方。因此,在nginx源码的请求连接初始化函数ngx_http_init_connection内,可以看到这么一行代码:
ngx_add_timer(rev, c->listening->post_accept_timeout);
这就是将rev事件(用于触发读取头部信息事件)对象加入到nginx的事件计时红黑树内进行超时监控管理,同时给它指定的超时时限为c->listening->post_accept_timeout[1]
类似的需要超时处理的地方还有很多,比如请求POST信息读取超时、连接keepalive超时等等。

ngx_add_timer函数[2]完成将一个事件对象加入到事件计时红黑树的功能,这种加入是间接性的,根据前面的介绍可知,每个事件对象都有一个tmer字段,并且其为ngx_rbtree_node_t类型变量,加入到事件计时红黑树的就是这个字段,而非事件对象本身。当然,利用offsetof宏也可以通过该tmer字段快速方便的找到其所在的对应事件对象,所以并不用为这种设计而担心。

具有四个节点的红黑树图示描述如下所示,从该图中可以看到两点:第一,可以通过全局变量ngx_event_timer_rbtree.root访问到该事件计时红黑树;第二,从该红黑树树根从左或从右遍历下去,最后都将到达全局变量ngx_event_timer_sentinel指定的末端树节点,这也是前面提到的将ngx_event_timer_sentinel当作哨兵节点。

通过事件计时红黑树,nginx对那些需要关注其是否超时的事件对象就有了统一的管理,nginx可以选择在合适的时机对事件计时红黑树管理的事件进行一次超时检测[3],对于超时了的事件对象就进行相应的处理。前面曾提到过nginx选择合适时机的方案有两种,下面就来分别介绍,不过在此之前,我们需要了解nginx工作进程执行的大体流程,如下图所示,该图中给出了与本节内容相关较为紧密的相关执行步骤,省掉了本节不关心的内容,以便理解。

简化的nginx工作进程执行流程就是一个大的for循环,该循环的执行路径如上图中的②③④所示,其中的函数ngx_process_events_and_timers调用是我们关注的重点,nginx的主要工作也是在该函数内完成的。

在函数ngx_process_events_and_timers开始,将首先判断ngx_timer_resolution是否为非零值[4],这个判断就决定了nginx的超时检测方案,反应到代码里就是根据判断结果设置对应的变量timer和flags。
变量flags的作用很明显,用作标识;而变量timer表示下面的IO复用模型进行监听发生事件的最长等待时间:1,如果变量ngx_timer_resolution非零,那么变量timer将被设置为NGX_TIMER_INFINITE[5],即IO复用模型将一直监听等待,直到有事件发生为止。如果有IO事件发生,那么nginx将走路径⑻⑼⒃,注意到前面将变量flags设置为0就很容易理解这点。当然,就算没有IO事件发生,nginx也不会让工作进程无限制的阻塞在这个事件监听等待处,IO复用模型会在最多等待ngx_timer_resolution时间后收到定时器中断事件而停止等待,从而继续下面的处理,即经过⑻⑽⒀⒁进行一次超时检测。这也就是超时检测方案一,即大概每过ngx_timer_resolution时间就进行一次超时检测。2,如果变量ngx_timer_resolution值为零,那么变量timer将被设置为最快发生超时的事件对象的超时时刻与当前时刻的时间差,举个例子来说,比如事件计时红黑树管理着三个事件a、b、c,它们分别将在5、6、7秒后超时,那么距离当前最快发生超时的就是事件a,而事件a的超时时刻与当前时刻的时间差为5,因此变量timer就将被设置5。timer值的具体计算实现在函数ngx_event_find_timer内,该函数从事件计时红黑树内找到key值最小的节点[6],然后用该节点的key值减去当前时刻(ngx_current_msec[7])即得到预期的timer值。预期的timer值可能为负数,这表示已经有事件超时了,因此直接将timer值设置为0,那么接下来的IO复用模型查询IO事件时会立即返回,以便能马上处理这些超时事件。路径⑻⑼⑿⒁和⑾⒁分别表示了超时检测方案二的可能执行过程,因此不管是哪一条路径都会进行超时检测。

比较两种超时检测方案可以看到,方案一简单直观、容易理解,但有可能导致一些超时事件得不到及时的处理;方案二避免了方案一的缺点,它能对超时事件做出及时的处理,然而,如果合理设置方案一中的ngx_timer_resolution值,那么超时事件的延迟处理并不会造成多大问题。另外,相对于方案一来说,方案二频繁的调用ngx_time_update函数[8]也将会有一定的性能损耗。

由于nginx利用红黑树来组织管理超时事件,因此检测是否有事件超时并不需要遍历所有的事件对象,而直接找到最近的即将超时的事件对象,判断其是否超时,如果超时则进行相应的超时处理,处理完了再判断第二近的即将超时的事件对象,如此反复,直到遇到某个事件还未超时或所有事件都超时并已处理就结束检测。这就是超时检测与处理函数ngx_event_expire_timers的大致逻辑,当然还有一些其它额外的动作,比如将超时了的事件对象的相关字段置位、超时处理、事件对象移出事件计时红黑树等等。


[1] 该变量对应client_header_timeout配置指令,即可由用户设定,默认情况下是60秒
[2] 事实上它是个宏:#define ngx_add_timer ngx_event_add_timer
[3] 函数ngx_event_expire_timers实现这个功能
[4] 用户可通过timer_resolution指令设置该变量的值,如果用户未设置那么其默认就是初始值0,因为它是一个全局变量,具体请参考其它章节
[5] #define NGX_TIMER_INFINITE  (ngx_msec_t) -1
[6] key值记录的就是事件的超时时刻,key值最小的节点表示的也就是距离当前最快发生超时的事件
[7] 该值并不是实时的,因此和精确的当前时刻会有一些偏差
[8] 该函数具体实现请参考其它章节

转载请保留地址:http://www.lenky.info/archives/2011/09/13http://lenky.info/?p=13


备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。

法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以Email或书面等方式告知,本站将及时删除相关内容或链接。

分类: nginx, 源码分析 标签: ,
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.