9.8.5 ngx_process_events_and_timers流程

本节将综合上文相关内容,探讨Nginx事件框架处理的流程。

在图8-7中,每个worker进程都在ngx_worker_process_cycle方法中循环处理事件。图8-7中的处理分发事件实际上就是调用的ngx_process_events_and_timers方法,下面先看一下它的定义:


void ngx_process_events_and_timers(ngx_cycle_t*cycle);


循环调用ngx_process_events_and_timers方法就是在处理所有的事件,这正是事件驱动机制的核心。顾名思义,ngx_process_events_and_timers方法既会处理普通的网络事件,也会处理定时器事件,在图9-7中,读者会看到在这个方法中到底做了哪些事情。

9.8.5 ngx_process_events_and_timers流程 - 图1

图 9-7 ngx_process_events_and_timers方法中的事件框架处理流程

ngx_process_events_and_timers方法中核心的操作主要有以下3个:

❑调用所使用的事件驱动模块实现的process_events方法,处理网络事件。

❑处理两个post事件队列中的事件,实际上就是分别调用ngx_event_process_posted(cycle,&ngx_posted_accept_events)和ngx_event_process_posted(cycle,&ngx_posted_events)方法(参见9.8.4节)。

❑处理定时器事件,实际上就是调用ngx_event_expire_timers()方法(参见9.7.3节)。

后两项操作很清晰,而调用事件驱动模块的process_events方法时则需要设置两个关键参数timer和flags。Nginx用一系列宏封装了ngx_event_actions接口中的方法,如下所示。


define ngx_process_changes ngx_event_actions.process_changes

define ngx_process_events ngx_event_actions.process_events

define ngx_done_events ngx_event_actions.done

define ngx_add_event ngx_event_actions.add

define ngx_del_event ngx_event_actions.del

define ngx_add_conn

ngx_event_actions.add_conn

define ngx_del_conn

ngx_event_actions.del_conn


在调用ngx_process_changes时,传入的timer和flags会影响时间精度以及事件是否会在post队列中处理。下面简要分析一下图9-7中的11个步骤,其中前6个步骤都与参数timer和flags的设置有关。

1)如果配置文件中使用了timer_resolution配置项,也就是ngx_timer_resolution值大于0,则说明用户希望服务器时间精确度为ngx_timer_resolution毫秒。这时,将ngx_process_changes的timer参数设为-1,告诉ngx_process_change方法在检测事件时不要等待,直接搜集所有已经就绪的事件然后返回;同时将flags参数初始化为0,它是在告诉ngx_process_changes没有任何附加动作。

2)如果没有使用timer_resolution,那么将调用ngx_event_find_timer()方法(参见表9-5)获取最近一个将要触发的事件距离现在有多少毫秒,然后把这个值赋予timer参数,告诉ngx_process_change方法在检测事件时如果没有任何事件,最多等待timer毫秒就返回;将flags参数设置为NGX_UPDATE_TIME,告诉ngx_process_change方法更新缓存的时间(参见9.6.3节中ngx_epoll_process_events方法的源代码)。

3)如果在配置文件中使用accept_mutex off关闭accept_mutex锁,就直接跳到第7步,否则检测负载均衡阈值变量ngx_accept_disabled。如果ngx_accept_disabled是正数,则将其值减去1,继续向下执行第7步。

4)如果ngx_accept_disabled是负数,表明还没有触发到负载均衡机制(参见9.8.3节),此时要调用ngx_trylock_accept_mutex方法试图去获取accept_mutex锁(也就是ngx_accept_mutex变量表示的锁)。

5)如果获取到accept_mutex锁,也就是说,ngx_accept_mutex_held标志位为1,那么将flags参数加上NGX_POST_EVENTS标志,告诉ngx_process_change方法搜集到的事件没有直接执行它的handler方法,而是分门别类地放到ngx_posted_accept_events队列和ngx_posted_events队列中。timer参数保持不变。

6)如果没有获取到accept_mutex锁,则意味着既不能让当前worker进程频繁地试图抢锁,也不能让它经过太长时间再去抢锁。这里有个简单的判断方法,如下所示。


if(timer==NGX_TIMER_INFINITE

||timer>ngx_accept_mutex_delay)

{

timer=ngx_accept_mutex_delay;

}


这意味着,即使开启了timer_resolution时间精度,也需要让ngx_process_change方法在没有新事件的时候至少等待ngx_accept_mutex_delay毫秒再去试图抢锁。而没有开启时间精度时,如果最近一个定时器事件的超时时间距离现在超过了ngx_accept_mutex_delay毫秒的话,也要把timer设置为ngx_accept_mutex_delay毫秒,这是因为当前进程虽然没有抢到accept_mutex锁,但也不能让ngx_process_change方法在没有新事件的时候等待的时间超过ngx_accept_mutex_delay毫秒,这会影响整个负载均衡机制。

注意 ngx_accept_mutex_delay变量与nginx.conf配置文件中的accept_mutex_delay配置项的参数相关内容可参见9.5节。

7)调用ngx_process_events方法,并计算ngx_process_events执行时消耗的时间,如下所示。


delta=ngx_current_msec;

(void)ngx_process_events(cycle,timer,flags);

delta=ngx_current_msec-delta;


其中,delta是ngx_process_events执行时消耗的毫秒数,它会影响第10步中触发定时器的执行。

8)如果ngx_posted_accept_events队列不为空,那么调用ngx_event_process_posted方法执行ngx_posted_accept_events队列中需要建立新连接的事件。

9)如果ngx_accept_mutex_held标志位为1,则表示当前进程获得了accept_mutex锁,而且在第8步中也已经处理完了新连接事件,这时需要调用ngx_shmtx_unlock释放accept_mutex锁。

10)如果ngx_process_events执行时消耗的时间delta大于0,而且这时可能有新的定时器事件被触发,那么需要调用ngx_event_expire_timers方法处理所有满足条件的定时器事件。

11)如果ngx_posted_events队列不为空,则调用ngx_event_process_posted方法执行ngx_posted_events队列中的普通读/写事件。

至此,ngx_process_events_and_timers方法执行完毕。注意,ngx_process_events_and_timers方法就是Nginx实际上处理Web服务的方法,所有业务的执行都是由它开始的。ngx_process_events_and_timers方法涉及Nginx完整的事件驱动机制,因此,它也把之前介绍的内容整合在一起了,读者需要格外注意。