Nginx Event模块
来源:互联网 发布:模糊控制算法matlab 编辑:程序博客网 时间:2024/05/19 03:46
Nginx Event模块
以下内容基于 Nginx 版本 1.8.0
主要结构说明
Nginx Event模块主要由三个模块构成:
- ngx_events_module- ngx_event_core_module- ngx_epoll_module
三个模块的配置加载可参见Nginx配置及配置加载,不再进行赘述
ngx_event_core_module 为 Nginx 核心模块,负责 Nginx 的事件处理
ngx_epoll_module 为IO多路复用模块,在 Linux 下默认使用该模块,在其它平台下会使用其他模块,如 freebsd 下使用 ngx_kqueue_module
ngx_module_t 的主要挂载函数
struct ngx_module_s { ... ngx_int_t (*init_master)(ngx_log_t *log); //目前未使用 ngx_int_t (*init_module)(ngx_cycle_t *cycle); //master进程在ngx_init_cycle中调用 ngx_int_t (*init_process)(ngx_cycle_t *cycle); //work进程在进入主循环前调用ngx_worker_process_init时调用 ngx_int_t (*init_thread)(ngx_cycle_t *cycle); //目前未使用 void (*exit_thread)(ngx_cycle_t *cycle); //目前未使用 void (*exit_process)(ngx_cycle_t *cycle); //在work进程收到信号退出时调用ngx_worker_process_exit时调用 void (*exit_master)(ngx_cycle_t *cycle); //在master进程收到退出或终止信号时调用ngx_master_process_exit时调用 ...};
Nginx三个主要模块中,只有 ngx_event_core_module 挂在了 init_module 和 init_process,其它两个模块均未挂载相应的处理函数。
ngx_event_core_module 模块挂载的处理函数:
init_module:ngx_event_module_initinit_process:ngx_event_process_init
ngx_events_module
Nginx 在配置加载阶段会加载 ngx_events_module 的配置 ngx_events_block,该配置函数会加载其它的 NGX_EVENT_MODULE 类型的模块(ngx_event_core_module 和 ngx_epoll_module)
ngx_events_block
该函数主要处理以下几个内容:
调用所有 NGX_EVENT_MODULE 类型模块的 ngx_modules[i]->ctx->create_conf
加载所有 NGX_EVENT_MODULE 类型模块的 配置
调用所有 NGX_EVENT_MODULE 类型模块的 ngx_modules[i]->ctx->init_conf
通过以上三步,达到对 NGX_EVENT_MODULE 模块的配置加载和初始化
ngx_event_core_module 模块
ngx_event_core_module 配置
ngx_event_core_module 保存了 Event 处理主要的配置
typedef struct { ngx_uint_t connections; //连接数,默认初始化为512,读取events下的worker_connections或connections配置项,如:worker_connections 1024 ngx_uint_t use; //使用的多路复用的event模块的ctx_indx,读取events下的use配置项,如:use epoll ngx_flag_t multi_accept; //默认为0,读取events下的multi_accept配置项,如:multi_accept off ngx_flag_t accept_mutex; //默认为1,读取events下的accept_mutex配置项,如:accept_mutex on ngx_msec_t accept_mutex_delay; //默认为500,读取events下的accept_mutex_delay配置项,如:accept_mutex_delay 500 u_char *name; //如ngx_epoll_module_ctx,其为epoll_name("epoll",ngx_epoll_module_ctx->ngx_epoll_module_ctx->name)#if (NGX_DEBUG) ngx_array_t debug_connection;#endif} ngx_event_conf_t;
- connections:这个配置会和 openfile 的限制数有关系,具体可以使用 ulimit -a 查看,如果该配置项大于限制数,会将其调整为系统中的最大限制数
- use:用于标记使用的是那个IO复用模块,如 epoll
- accept_mutex:表示使用 accept 锁(用于解决惊群问题)
ngx_event_module_init
该函数主要处理两件事:
- 对 ecf->connections 的配置进行检验,如果超过系统的限制上限,将其调整为系统上限,系统上限可使用 ulimit -a 查看 open files
创建一个共享内存,该内存包括一个进程共享的互斥锁和一些统计用的字段:
ngx_accept_mutexngx_connection_counterngx_temp_numberngx_random_numberngx_stat_acceptedngx_stat_handledngx_stat_requestsngx_stat_activengx_stat_readingngx_stat_writingngx_stat_waiting
以 ngx_stat_ 开始的几个变量是 ngx_http_stub_status_module 使用的,用于对 Nginx 的运行状态进行统计
ngx_event_process_init
该函数主要处理以下几个内容:
当为多进程模式(master+worker 进程),worker 进程数为2个以上,并且 accept_mutex 打开时,对以下变量进行赋值:
ngx_use_accept_mutex:1表示使用 accept_mutex(ngx_accept_mutex)ngx_accept_mutex_held:0表示 accept_mutex 未被占用ngx_accept_mutex_delay:ecf->accept_mutex_delay
当只有一个 worker 进程时,不启用 accept_mutex,ngx_use_accept_mutex 会被置为0
初始化两个队列
ngx_queue_init(&ngx_posted_accept_events):accept 事件处理队列ngx_queue_init(&ngx_posted_events):其它事件处理队列
调用NGX_EVENT_MODULE的actions.init
ctx_index 必须与 ecf->use 一致,如 epoll 模块直接调用 ngx_epoll_init 对 epoll 模块进行初始化
使用 epoll 会处理SIGALRM,该信号的处理函数为 ngx_timer_signal_handler
初始化 connections
Nginx 会根据初始配置初始化一个 connection 池,并放入到 cycle->free_connections 作为空闲连接,如果后续有连接进来会到该空闲池中取出一个连接使用,这样可以避免频繁的创建和删除 connection
建立监听事件
对每个监听的端口,从连接池获取一个空闲的连接:
ls = cycle->listening.elts;for (i = 0; i < cycle->listening.nelts; i++) { c = ngx_get_connection(ls[i].fd, cycle->log); ...
将连接事件的处理函数挂载到 ngx_event_accept 函数上
...ls[i].connection = c;rev = c->read;...if (ngx_event_flags & NGX_USE_IOCP_EVENT) { ngx_iocp_conf_t *iocpcf; ...} else { rev->handler = ngx_event_accept; if (ngx_use_accept_mutex) { continue; } if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; }}
注意此处如果只有一个 worker 进程,会在此处直接将 listen 的处理事件加入到 epoll 中,如果有两个或两个以上的 worker 进程,则会在后面获取 accept 锁时加入
ngx_epoll_module
在 Linux 下 ngx_epoll_module 为默认的IO多路复用模块,其主要处理函数为 ngx_epoll_module_ctx 下定义的 actions 挂载处理函数:
{ ngx_epoll_add_event, /* add an event */ ngx_epoll_del_event, /* delete an event */ ngx_epoll_add_event, /* enable an event */ ngx_epoll_del_event, /* disable an event */ ngx_epoll_add_connection, /* add an connection */ ngx_epoll_del_connection, /* delete an connection */#if (NGX_HAVE_EVENTFD) ngx_epoll_notify, /* trigger a notify */#else NULL, /* trigger a notify */#endif ngx_epoll_process_events, /* process the events */ ngx_epoll_init, /* init the events */ ngx_epoll_done, /* done the events */}
一旦确定后,其会与下面几个宏进行关联
#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#define ngx_notify ngx_event_actions.notify
对于 epoll 模块,主要挂载处理函数为:
ngx_epoll_init:为初始化函数,在 ngx_event_process_init 中调用ngx_epoll_process_events:为事件处理函数,在主循环中调用ngx_epoll_add_event:为向 epoll 中添加 IO 事件时使用
处理流程主线
ngx_process_events_and_timers
ngx_process_events_and_timers 为Nginx事件处理函数,该函数会处理所有定时器和 IO 事件
worker进程执行主循环 ngx_worker_process_cycle->ngx_process_events_and_timers,ngx_process_events_and_timers 处理如下:
由于单个进程不会启动 accept 锁,所以不会执行以下代码
if (ngx_use_accept_mutex) { if (ngx_accept_disabled > 0) { ... } else { if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) { return; } if (ngx_accept_mutex_held) { //第一个进入ngx_trylock_accept_mutex并持有accept锁的worker进程 flags |= NGX_POST_EVENTS; } else { //其它worker进程 if (timer == NGX_TIMER_INFINITE || timer > ngx_accept_mutex_delay) { timer = ngx_accept_mutex_delay; } } }}
前面提过,只有一个 worker 进程的情况下,连接事件在 ngx_event_process_init 中注册到epoll中,accept 锁不会启用,不会执行这段代码
多个 worker 进程的情况下,第一个调用 ngx_trylock_accept_mutex 会持有 accept 锁,并将连接事件注册到 epoll 中(ngx_trylock_accept_mutex->ngx_enable_accept_events),而其它进程则会进入延迟上锁。综上,多个 worker 进程的情况下,每次只有一个进程能处理外部的连接请求,当其处理外部连接事件时,其它进程会抢占并持有 accept 锁,这种机制保证内核在有连接进入时,只会选择一个worker进程进行处理,防止 accept 惊群
直接开始执行(注:此处传入的 flags 只有 NGX_UPDATE_TIME):
(void) ngx_process_events(cycle, timer, flags);
前面说过,在Linux下,该宏指向 ngx_epoll_process_events,具体处理见 ngx_epoll_process_events
处理所有 accept 事件
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
在一个 work 进程的情况下,该队列一般为空,多个 worker 进程的情况下,连接事件在该处进行处理,具体原因在 ngx_epoll_process_events 中说明
在执行完 accept 队列的事件后,会立即释放 accept 锁,已保证其它 worker 进程能迅速抢占 accept 锁处理后续的连接请求
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}处理定时器
if (delta) {
ngx_event_expire_timers();
}处理其他事件
ngx_event_process_posted(cycle, &ngx_posted_events);
在一个 work 进程的情况下,该队列一般为空,多个 worker 进程的情况下,连接事件在该处进行处理,具体原因在 ngx_epoll_process_events 中说明
ngx_epoll_process_events
ngx_epoll_process_events 为 IO 多路复用处理函数,其使用 epoll_wait 等待 IO 事件,并对 IO 事件进行处理。一般情况下 Nginx 的 worker 进程在空闲时都会 block 在这个位置
epoll 多路复用处理 IO 事件
events = epoll_wait(ep, event_list, (int) nevents, timer);
单个进程,timer 为 -1,epoll_wait 会一直等待,直到有事件产生
多个进程,其中一个 worker 进程会长期持有 accept 锁,并一直等待外部连接请求,其它 worker 进程在默认情况下会每 500ms (等待时间由 ngx_event_core_module 模块的 accept_mutex_delay 配置决定)从 epoll_wait 中退出,这样保证了一旦持有 accept 锁的进程处理连接请求,释放 accept 锁了,其它进程能顺利抢占 accept 锁
更新系统时间
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { ngx_time_update();}
epoll_wait 超时由此退出,多个 worker 进程,未持有 accept 锁,并且没有其它事件处理时,系统默认配置每 500ms 会从 epoll_wait 退出,并从此处返回
if (events == 0) { if (timer != NGX_TIMER_INFINITE) { return NGX_OK; } ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "epoll_wait() returned no events without timeout"); return NGX_ERROR;}
处理 IO 事件
for (i = 0; i < events; i++) { c = event_list[i].data.ptr; instance = (uintptr_t) c & 1; c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1); ...}
系统在 epoll 注册 IO 事件时,会将连接相关的 connection 注册到 epoll 的 data 中,当有 IO 事件发生时,可以获取产生 IO 事件的 connection,从而可获取其中的读写事件,并对读写事件进行处理
rev = c->read;...if ((revents & EPOLLIN) && rev->active) { ... if (flags & NGX_POST_EVENTS) { queue = rev->accept ? &ngx_posted_accept_events : &ngx_posted_events; ngx_post_event(rev, queue); } else { rev->handler(rev); }}
只有一个 worker 进程时,flags 只有 NGX_UPDATE_TIME,会直接调用 rev->handler 进行处理,如果此时为一个外部连接创建请求,会调用 ngx_event_accept 进行处理,如果为其它请求,会调用其它处理函数进行处理,如 HTTP 请求会调用 ngx_http_wait_request_handler,对于 HTTP 请求调用 ngx_http_wait_request_handler 会在 ngx_event_accept 的处理中进行说明
当有多个 worker 进程时,flags 为 NGX_UPDATE_TIME | NGX_POST_EVENTS,如果当前的 connection 为一个新建连接,会将事件推送到 ngx_posted_accept_events 队列中,否则将事件推送到 ngx_posted_events 队列中,后面在 ngx_process_events_and_timers 中处理 ngx_posted_accept_events 与 ngx_posted_events 时与单 worker 进程调用 rev->handler 的处理过程相同。这里需要注意,多 worker 进程时,虽然同一时间点只有一个 worker 进程可以处理外部连接请求,其它进程还是能处理其它请求的,当一个已建立的连接发生 IO 事件时,仍会到此处进行处理
这里多 worker 进程处理与单 worker 进程处理不同的原因为此处处理的事件除了外部连接请求外,还有其它的请求(如HTTP请求),此处代码持有 accept 锁,如果同时处理连接请求和其它请求,其它 worker 进程在此期间是不能处理外部连接请求的,会降低系统对外部连接处理的效率。因此此处只是按照事件类型将事件推送到 accept 队列和其它队列中,在 ngx_process_events_and_timers 中会在持有 accept 锁阶段处理 accept 队列事件,而在处理其它事件前会释放 accept 锁,这样其它 worker 进程就能在其处理其它事件时,抢占并持有 accept 锁,处理外部连接请求了
wev = c->write;if ((revents & EPOLLOUT) && wev->active) { ... if (flags & NGX_POST_EVENTS) { ngx_post_event(wev, &ngx_posted_events); } else { wev->handler(wev); }}
写事件的处理与读事件的处理类似,这里就不进行赘述
ngx_event_accept
ngx_event_accept 为外部连接请求处理函数,前面提过,其在 ngx_event_process_init 中对所有监听的句柄时挂载到监听句柄的 connection 上。对单个 worker 进程,在 ngx_event_process_init 就将事件放入到 epoll 中;对多个 worker 进程,在 ngx_process_events_and_timers 调用 ngx_trylock_accept_mutex 时将该事件放入到 epoll 中
accept外部的连接
do { socklen = NGX_SOCKADDRLEN;#if (NGX_HAVE_ACCEPT4) if (use_accept4) { s = accept4(lc->fd, (struct sockaddr *) sa, &socklen, SOCK_NONBLOCK); } else { s = accept(lc->fd, (struct sockaddr *) sa, &socklen); }#else s = accept(lc->fd, (struct sockaddr *) sa, &socklen);#endif ...} while (ev->available);
获取一个空闲的 connection 关联到新建立的连接上,并进行一些初始化工作
c = ngx_get_connection(s, ev->log);...c->recv = ngx_recv;c->send = ngx_send;c->recv_chain = ngx_recv_chain;c->send_chain = ngx_send_chain;c->log = log;c->pool->log = log;c->socklen = socklen;c->listening = ls;c->local_sockaddr = ls->sockaddr;c->local_socklen = ls->socklen;c->unexpected_eof = 1;...
调用连接处理句柄进行处理
log->data = NULL;log->handler = NULL;ls->handler(c);
此处的 handler 根据不同的模块挂载不同,挂载点一般为配置初始化时,对服务端口进行监听时挂载的:
如 HTTP 模块是在 ngx_http_block->ngx_http_optimize_servers->ngx_http_init_listening->ngx_http_add_listening 时挂载的,挂载处理函数为 ngx_http_init_connection
ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);if (ls == NULL) { return NULL;}ls->addr_ntop = 1;ls->handler = ngx_http_init_connection;
如 MAIL 模块是在 ngx_mail_block->ngx_mail_optimize_servers 时挂载的,挂载处理函数为 ngx_mail_init_connection
ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);if (ls == NULL) { return NGX_CONF_ERROR;}ls->addr_ntop = 1;ls->handler = ngx_mail_init_connection;
也就是说,如果需要开发一个新的服务协议,可以使用类似的方法,对服务端口监听处理函数挂载为自己需要的函数即可处理外部的连接请求
以 HTTP 模块为例,当有一个外部连接请求时,会调用 ngx_http_init_connection 对外部连接请求进行处理,其处理流程如下
ngx_http_init_connection
此处跳过连接相关的一些处理,首先是将connection关联到 HTTP 连接的应用上下文中
hc->conf_ctx = hc->addr_conf->default_server->ctx;ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));if (ctx == NULL) { ngx_http_close_connection(c); return;}ctx->connection = c;ctx->request = NULL;ctx->current_request = NULL;
挂载新连接的处理函数 ngx_http_wait_request_handler
rev = c->read;rev->handler = ngx_http_wait_request_handler;c->write->handler = ngx_http_empty_handler;
如果当前连接为一个ssl连接,则将读事件挂载为 ngx_http_ssl_handshake
#if (NGX_HTTP_SSL) { ngx_http_ssl_srv_conf_t *sscf; sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); if (sscf->enable || hc->addr_conf->ssl) { c->log->action = "SSL handshaking"; if (hc->addr_conf->ssl && sscf->ssl.ctx == NULL) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "no \"ssl_certificate\" is defined " "in server listening on SSL port"); ngx_http_close_connection(c); return; } hc->ssl = 1; rev->handler = ngx_http_ssl_handshake; } }#endif
在SSL握手完成后,会将读事件重新挂载回 ngx_http_wait_request_handler
此时 rev 是新建立的连接,而不是监听连接,在普通的 epoll 模式下,此时的 rev->ready 不为 1,下面这段代码不会执行
if (rev->ready) { /* the deferred accept(), rtsig, aio, iocp */ if (ngx_use_accept_mutex) { ngx_post_event(rev, &ngx_posted_events); return; } rev->handler(rev); return;}
在 ngx_handle_read_event 会将建立的连接加入到 epoll 中
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
综上,当外部 HTTP 实体向该连接发起 HTTP 请求后,会调用 ngx_http_wait_request_handler 进行处理,即 HTTP 请求的处理从此函数开始
单进程处理流
pid=23835 ngx_http_add_listeningpid=23836 ngx_event_process_initpid=23836 ccf->master:1, ccf->worker_processes:1, ecf->accept_mutex:1pid=23836 hook rev->handler = ngx_event_accept:0pid=23836 in ngx_event_process_init before ngx_add_event(rev, NGX_READ_EVENT, 0)pid=23836 ngx_epoll_add_eventpid=23836 in ngx_worker_process_init before ngx_add_channel_eventpid=23836 ngx_epoll_add_eventpid=23836 in ngx_worker_process_init after ngx_add_channel_eventpid=23836 ngx_process_events_and_timerspid=23836 ngx_epoll_process_eventspid=23836 epoll timer: -1pid=23836 read rev->handler(rev)pid=23836 ngx_event_acceptpid=23836 ngx_http_init_connectionpid=23836 ngx_epoll_add_eventpid=23836 after ngx_process_events(cycle, timer, flags); flags=1, timer=-1pid=23836 ngx_event_process_posted process 0 eventspid=23836 after ngx_event_process_posted(cycle, &ngx_posted_accept_events)pid=23836 ngx_event_process_posted process 0 eventspid=23836 after ngx_event_process_posted(cycle, &ngx_posted_events)#########################################################pid=23836 ngx_process_events_and_timerspid=23836 ngx_epoll_process_eventspid=23836 epoll timer: 60000pid=23836 read rev->handler(rev)pid=23836 ngx_http_wait_request_handlerpid=23836 after ngx_process_events(cycle, timer, flags); flags=1, timer=60000pid=23836 ngx_event_process_posted process 0 eventspid=23836 after ngx_event_process_posted(cycle, &ngx_posted_accept_events)pid=23836 ngx_event_process_posted process 1 eventspid=23836 after ngx_event_process_posted(cycle, &ngx_posted_events)#########################################################pid=23836 ngx_process_events_and_timerspid=23836 ngx_epoll_process_eventspid=23836 epoll timer: 65000pid=23836 read rev->handler(rev)pid=23836 after ngx_process_events(cycle, timer, flags); flags=1, timer=65000pid=23836 ngx_event_process_posted process 0 eventspid=23836 after ngx_event_process_posted(cycle, &ngx_posted_accept_events)pid=23836 ngx_event_process_posted process 0 eventspid=23836 after ngx_event_process_posted(cycle, &ngx_posted_events)#########################################################pid=23836 ngx_process_events_and_timerspid=23836 ngx_epoll_process_eventspid=23836 epoll timer: -1
多进程处理流
pid=23829 ngx_http_add_listeningpid=23830 ngx_event_process_initpid=23830 ccf->master:1, ccf->worker_processes:2, ecf->accept_mutex:1pid=23831 ngx_event_process_initpid=23831 ccf->master:1, ccf->worker_processes:2, ecf->accept_mutex:1pid=23830 hook rev->handler = ngx_event_accept:1pid=23830 in ngx_worker_process_init before ngx_add_channel_eventpid=23830 ngx_epoll_add_eventpid=23830 in ngx_worker_process_init after ngx_add_channel_eventpid=23830 ngx_process_events_and_timerspid=23830 !(ngx_accept_disabled > 0)pid=23830 ngx_epoll_add_eventpid=23830 ngx_epoll_process_eventspid=23830 epoll timer: -1pid=23830 read ngx_post_event(rev, queue)pid=23830 after ngx_process_events(cycle, timer, flags); flags=3, timer=-1pid=23830 ngx_event_process_posted process 0 eventspid=23830 after ngx_event_process_posted(cycle, &ngx_posted_accept_events)pid=23830 ngx_event_process_posted process 1 eventspid=23830 after ngx_event_process_posted(cycle, &ngx_posted_events)#########################################################pid=23830 ngx_process_events_and_timerspid=23830 !(ngx_accept_disabled > 0)pid=23830 ngx_epoll_process_eventspid=23830 epoll timer: -1pid=23831 hook rev->handler = ngx_event_accept:1pid=23831 in ngx_worker_process_init before ngx_add_channel_eventpid=23831 ngx_epoll_add_eventpid=23831 in ngx_worker_process_init after ngx_add_channel_eventpid=23831 ngx_process_events_and_timerspid=23831 !(ngx_accept_disabled > 0)pid=23831 ngx_epoll_process_eventspid=23831 epoll timer: 500pid=23831 after ngx_process_events(cycle, timer, flags); flags=1, timer=500pid=23831 ngx_event_process_posted process 0 eventspid=23831 after ngx_event_process_posted(cycle, &ngx_posted_accept_events)pid=23831 ngx_event_process_posted process 0 eventspid=23831 after ngx_event_process_posted(cycle, &ngx_posted_events)#########################################################
定时器
SIGALRM 信号
定时器这块有一个变量 ngx_timer_resolution,默认情况下为 0,如果在配置文件中配置了 timer_resolution (ngx_core_module 的配置),会启动一个实时 SIGALRM 信号处理。如此处配置为 1,即每 1s 产生一次 SIGALRM 信号,epoll_wait 则会被该信号打断。如无别的需要不要配置该项
Nginx 定时器红黑树
Nginx 的定时器使用一棵红黑树(ngx_event_timer_rbtree)进行管理,该红黑树是以定时器定时时间为key的,换言之,超时时间最短的定时器,在红黑树的最左节点上
Nginx 定时器提供给用户两个接口:
ngx_add_timer:用于添加定时器,与 ngx_event_add_timer 绑定ngx_del_timer:用于删除定时器,与 ngx_event_del_timer 绑定
设置定时器 ngx_event_add_timer
static ngx_inline voidngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
- ev 为需要加入定时器处理的事件
- timer 指定定时器超时时间离当前的毫秒数
而最终定时器的 key 值为 ngx_current_msec + timer,是一个绝对时间
删除定时器 ngx_event_del_timer
static ngx_inline voidngx_event_del_timer(ngx_event_t *ev)
- ev 为需要删除定时器处理的事件
初始化定时器红黑树 ngx_event_timer_init
在 ngx_event_process_init 中调用,对定时器红黑树进行初始化
返回即将超时的定时器时间 ngx_event_find_timer
在事件处理函数 ngx_process_events_and_timers 中调用,在定时器红黑树中寻找最快要超时的定时器,并返回其离超时还剩余的时间
定时器超时处理 ngx_event_expire_timers
在红黑树中寻找所有已超时定时器(每次总找 key 值最小的定时器,直到最小的定时器未超时为止),设置event的timeout标识,并调用其 handler 进行处理:
ev->timedout;ev->handler(ev);
在处理函数中可以通过判断 timedout 是否为 1 来判断当前是定时器处理超时调用的 handler 函数还是正常事件调用的 handler 函数,这样在一个 handler 函数中就可以同时处理超时和 IO 事件了。如 HTTP 请求处理函数 ngx_http_wait_request_handler
ngx_http_wait_request_handler(ngx_event_t *rev){ ... if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ngx_http_close_connection(c); return; } ...}
这里需要注意,对定时器是否超时判断方法是:
if ((ngx_msec_int_t) (node->key - ngx_current_msec) > 0) { return;}
而不是:
if ((ngx_msec_int_t) node->key > (ngx_msec_int_t) ngx_current_msec) { return;}
这种方式与 Linux 内核中对定时器的处理方式一致,可以有效避免比较的两个值发生上溢的情况
取消所有定时器 ngx_event_cancel_timers
在 worker 进程退出时执行,从离超时最近的定时器开始,把所有定时器的处理函数都执行一遍
Nginx 定时器执行
Nginx 定时器在系统执行机制和绝大多数事件处理程序相同,每次主循环检查一次定时器队列(红黑树),如果发生超时则执行定时器。因此,如果一次主循环时间过长,则有可能会导致定时器超时了,但是并没有执行,导致意想不到的结果产生
对于 Nginx,其会在 epoll_wait 处阻塞等待 IO 事件,如果 epoll_wait 一直阻塞,就会出现上述情况,那么如何解决这个问题,这就需要定时器需要与事件处理进行配合
Nginx 在主循环中,每次通过获取最小定时器超时剩余时间来决定 epoll_wait 的最大阻塞时长
timer = ngx_event_find_timer();flags = NGX_UPDATE_TIME;
对于单个 worker 进程或多个进程等待外部连接请求的 worker 进程,该时间即为 epoll_wait 的最长时间(注,如果没有定时器,timer 会被置为 -1,即 epoll_wait 会永久阻塞,直到 IO 事件发生为止)
对于多个 worker 进程的其它进程,如果 timer 大于 ngx_accept_mutex_delay (默认为 500ms,可通过 ngx_event_core_module 的 accept_mutex_delay 进行配置),则将 timer 置为 ngx_accept_mutex_delay,前面说过这样是保证其它进程能在持有 accept 锁的进程处理请求时抢占 accept 锁。这样可以保证这些进程 epoll_wait 阻塞事件不会超过超时最近的定时器的超时时间。这种处理方式在很多事件处理模型的软件中都是如此使用,如 Redis
在 epoll_wait 退出后,会立即更新 Nginx 的所有系统时间
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { ngx_time_update();}
在完成所有 accept 事件处理后,会调用 ngx_event_expire_timers 处理超时的定时器事件
if (delta) { ngx_event_expire_timers();}
- Nginx event 模块分析
- nginx中的event模块
- nginx event 模块解析
- Nginx Event模块
- nginx 重中之重 event 模块
- nginx event模块启动分析
- nginx学习(四) event模块
- nginx源码分析——event模块
- nginx源码分析——event模块
- Nginx源码剖析--event类型模块
- Nginx event核心模块之epoll模块详解(一)
- Nginx event核心模块之epoll模块详解(二)
- Nginx event核心模块之epoll模块详解(三)
- Nginx源码分析 - Event事件篇 - Nginx的Event事件模块概览
- Nginx event核心模块之process部分详解(四)
- Nginx源码分析 - Event事件篇 - epoll事件模块
- Nginx源码分析 - Event事件篇 - Event模块和配置的初始化
- Nginx源码分析 - Event事件篇 - Event模块的进程初始化ngx_event_process_init
- C++中堆、栈数据区别
- pat1006Sign In and Sign Out (25)
- 0.IDA-基本操作
- Spiral Matrix
- UGUI学习笔记4——UI Events,Event Triggers
- Nginx Event模块
- 关于Maven的一些见解
- C# 获取操作系统相关信息
- linux文件处理命令——目录处理命令
- 二、制作第一个安卓应用
- Word Search
- CodeForces 416B Art Union
- Gson 源码分析
- In-network PCA and anomaly detection阅读笔记