9.5 ngx_event_core_module事件模块
ngx_event_core_module模块是一个事件类型的模块,它在所有事件模块中的顺序是第一位(configure执行时必须把它放在其他事件模块之前)。这就保证了它会先于其他事件模块执行,由此它选择事件驱动机制的任务才可以完成。
ngx_event_core_module模块要完成哪些任务呢?它会创建9.3节中介绍的连接池(包括读/写事件),同时会决定究竟使用哪些事件驱动机制,以及初始化将要使用的事件模块。
下面先来看一下ngx_event_core_module模块对哪些配置项感兴趣。该模块定义了ngx_event_core_commands数组处理其感兴趣的7个配置项,以下进行简要说明。
static ngx_command_t ngx_event_core_commands[]={
/连接池的大小,也就是每个worker进程中支持的TCP最大连接数,它与下面的connections配置项的意义是重复的,可参照9.3.3节理解连接池的概念/
{ngx_string("worker_connections"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_connections,
0,
0,
NULL},
//连接池的大小,与worker_connections配置项意义相同
{ngx_string("connections"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_connections,
0,
0,
NULL},
//确定选择哪一个事件模块作为事件驱动机制
{ngx_string("use"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_use,
0,
0,
NULL},
/对应于9.2节中提到的事件定义的available字段。对于epoll事件驱动模式来说,意味着在接收到一个新连接事件时,调用accept以尽可能多地接收连接/
{ngx_string("multi_accept"),
NGX_EVENT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_event_conf_t,multi_accept),
NULL},
//确定是否使用accept_mutex负载均衡锁,默认为开启
{ngx_string("accept_mutex"),
NGX_EVENT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_event_conf_t,accept_mutex),
NULL},
/启用accept_mutex负载均衡锁后,延迟accept_mutex_delay毫秒后再试图处理新连接事件/
{ngx_string("accept_mutex_delay"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
0,
offsetof(ngx_event_conf_t,accept_mutex_delay),
NULL},
//需要对来自指定IP的TCP连接打印debug级别的调试日志
{ngx_string("debug_connection"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_debug_connection,
0,
0,
NULL},
ngx_null_command
};
值得注意的是,上面对于配置项参数的解析使用了在第4章中介绍过的Nginx预设的配置项解析方法,如ngx_conf_set_flag_slot和ngx_conf_set_msec_slot。这种自动解析配置项的方式是根据指定结构体中的位置决定的。下面看一下该模块定义的用于存储配置项参数的结构体ngx_event_conf_t。
typedef struct{
//连接池的大小
ngx_uint_t connections;
/选用的事件模块在所有事件模块中的序号,也就是9.4.2节中介绍过的ctx_index成员/
ngx_uint_t use;
//标志位,如果为1,则表示在接收到一个新连接事件时,一次性建立尽可能多的连接
ngx_flag_t multi_accept;
//标志位,为1时表示启用负载均衡锁
ngx_flag_t accept_mutex;
/负载均衡锁会使有些worker进程在拿不到锁时延迟建立新连接,accept_mutex_delay就是这段延迟时间的长度。关于它如何影响负载均衡的内容,可参见9.8.5节/
ngx_msec_t accept_mutex_delay;
//所选用事件模块的名字,它与use成员是匹配的
u_char*name;
if(NGX_DEBUG)
/在——with-debug编译模式下,可以仅针对某些客户端建立的连接输出调试级别的日志,而debug_connection数组用于保存这些客户端的地址信息/
ngx_array_t debug_connection;
endif
}ngx_event_conf_t;
ngx_event_conf_t结构体中有两个成员与负载均衡锁相关,读者可以在9.8节中了解负载均衡锁的原理。
对于每个事件模块都需要实现的ngx_event_module_t接口,ngx_event_core_module模块则仅实现了create_conf方法和init_conf方法,这是因为它并不真正负责TCP网络事件的驱动,所以不会实现ngx_event_actions_t中的方法,如下所示。
static ngx_str_t event_core_name=ngx_string("event_core");
ngx_event_module_t ngx_event_core_module_ctx={
&event_core_name,
ngx_event_create_conf,
/create configuration/
ngx_event_init_conf,
/init configuration/
{NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}
};
最后看一下ngx_event_core_module模块的定义。
ngx_module_t ngx_event_core_module={
NGX_MODULE_V1,
&ngx_event_core_module_ctx,
/module context/
ngx_event_core_commands,
/module directives/
NGX_EVENT_MODULE,
/module type/
NULL,
/init master/
ngx_event_module_init,
/init module/
ngx_event_process_init,
/init process/
NULL,
/init thread/
NULL,
/exit thread/
NULL,
/exit process/
NULL,
/exit master/
NGX_MODULE_V1_PADDING
};
它实现了ngx_event_module_init方法和ngx_event_process_init方法。在Nginx启动过程中还没有fork出worker子进程时,会首先调用ngx_event_core_module模块的ngx_event_module_init方法(参见图8-6),而在fork出worker子进程后,每一个worker进程会在调用ngx_event_core_module模块的ngx_event_process_init方法后才会进入正式的工作循环。弄清楚这两个方法何时调用后,下面来看一下它们究竟做了什么。
ngx_event_module_init方法其实很简单,它主要初始化了一些变量,尤其是ngx_http_stub_status_module统计模块使用的一些原子性的统计变量,这里不再详述。
而ngx_event_process_init方法就做了许多事情,下面开始详细介绍它的流程。
ngx_event_core_module模块在启动过程中的主要工作都是在ngx_event_process_init方法中进行的,如图9-4所示。
图 9-4 ngx_event_core_module事件模块启动时的工作流程
下面对以上13个步骤进行简要说明。
1)当打开accept_mutex负载均衡锁,同时使用了master模式并且worker进程数量大于1时,才正式确定了进程将使用accept_mutex负载均衡锁。因此,即使我们在配置文件中指定打开accept_mutex锁,如果没有使用master模式或者worker进程数量等于1,进程在运行时还是不会使用负载均衡锁(既然不存在多个进程去抢一个监听端口上的连接的情况,那么自然不需要均衡多个worker进程的负载)。
这时会将ngx_use_accept_mutex全局变量置为1,ngx_accept_mutex_held标志设为0,ngx_accept_mutex_delay则设为在配置文件中指定的最大延迟时间。这3个变量的意义可参见9.8节中关于负载均衡锁的说明。
2)如果没有满足第1步中的3个条件,那么会把ngx_use_accept_mutex置为0,也就是关闭负载均衡锁。
3)初始化红黑树实现的定时器。关于定时器的实现细节可参见9.6节。
4)在调用use配置项指定的事件模块中,在ngx_event_module_t接口下,ngx_event_actions_t中的init方法进行这个事件模块的初始化工作。
5)如果nginx.conf配置文件中设置了timer_resolution配置项,即表明需要控制时间精度,这时会调用setitimer方法,设置时间间隔为timer_resolution毫秒来回调ngx_timer_signal_handler方法。下面简单地介绍一下Nginx是如何控制时间精度的。
ngx_timer_signal_handler方法又做了些什么呢?其实非常简单,如下所示。
void ngx_timer_signal_handler(int signo)
{
ngx_event_timer_alarm=1;
}
ngx_event_timer_alarm只是个全局变量,当它设为1时,表示需要更新时间。
在ngx_event_actions_t的process_events方法中,每一个事件驱动模块都需要在ngx_event_timer_alarm为1时调用ngx_time_update方法(参见9.7.1节)更新系统时间,在更新系统结束后需要将ngx_event_timer_alarm设为0。
6)如果使用了epoll事件驱动模式,那么会为ngx_cycle_t结构体中的files成员预分配句柄。本章仅针对epoll事件驱动模式,具体内容不再详述。
7)预分配ngx_connection_t数组作为连接池,同时将ngx_cycle_t结构体中的connections成员指向该数组。数组的个数为nginx.conf配置文件中connections或worker_connections中配置的连接数。
8)预分配ngx_event_t事件数组作为读事件池,同时将ngx_cycle_t结构体中的read_events成员指向该数组。数组的个数为nginx.conf配置文件中connections或worker_connections里配置的连接数。
9)预分配ngx_event_t事件数组作为写事件池,同时将ngx_cycle_t结构体中的write_events成员指向该数组。数组的个数为nginx.conf配置文件中connections或worker_connections里配置的连接数。
10)按照序号,将上述3个数组相应的读/写事件设置到每一个ngx_connection_t连接对象中,同时把这些连接以ngx_connection_t中的data成员作为next指针串联成链表,为下一步设置空闲连接链表做好准备,参见图9-1。
11)将ngx_cycle_t结构体中的空闲连接链表free_connections指向connections数组的最后1个元素,也就是第10步所有ngx_connection_t连接通过data成员组成的单链表的首部。
12)在刚刚建立好的连接池中,为所有ngx_listening_t监听对象中的connection成员分配连接,同时对监听端口的读事件设置处理方法为ngx_event_accept,也就是说,有新连接事件时将调用ngx_event_accept方法建立新连接(详见9.8节中关于如何建立新连接的内容)。
13)将监听对象连接的读事件添加到事件驱动模块中,这样,epoll等事件模块就开始检测监听服务,并开始向用户提供服务了。
至此,ngx_event_core_module模块的启动工作就全部结束了。下面将以epoll事件方式为例来介绍实际的事件驱动模块是如何处理事件的。