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.5 ngx_event_core_module事件模块 - 图1

图 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事件方式为例来介绍实际的事件驱动模块是如何处理事件的。