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

该函数主要处理以下几个内容:

  1. 调用所有 NGX_EVENT_MODULE 类型模块的 ngx_modules[i]->ctx->create_conf

  2. 加载所有 NGX_EVENT_MODULE 类型模块的 配置

  3. 调用所有 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

该函数主要处理两件事:

  1. 对 ecf->connections 的配置进行检验,如果超过系统的限制上限,将其调整为系统上限,系统上限可使用 ulimit -a 查看 open files
  2. 创建一个共享内存,该内存包括一个进程共享的互斥锁和一些统计用的字段:

    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

该函数主要处理以下几个内容:

  1. 当为多进程模式(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

  2. 初始化两个队列

    ngx_queue_init(&ngx_posted_accept_events):accept 事件处理队列ngx_queue_init(&ngx_posted_events):其它事件处理队列
  3. 调用NGX_EVENT_MODULE的actions.init

    ctx_index 必须与 ecf->use 一致,如 epoll 模块直接调用 ngx_epoll_init 对 epoll 模块进行初始化

  4. 使用 epoll 会处理SIGALRM,该信号的处理函数为 ngx_timer_signal_handler

  5. 初始化 connections

    Nginx 会根据初始配置初始化一个 connection 池,并放入到 cycle->free_connections 作为空闲连接,如果后续有连接进来会到该空闲池中取出一个连接使用,这样可以避免频繁的创建和删除 connection

  6. 建立监听事件

    对每个监听的端口,从连接池获取一个空闲的连接:

    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 处理如下:

  1. 由于单个进程不会启动 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 惊群

  2. 直接开始执行(注:此处传入的 flags 只有 NGX_UPDATE_TIME):

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

    前面说过,在Linux下,该宏指向 ngx_epoll_process_events,具体处理见 ngx_epoll_process_events

  3. 处理所有 accept 事件

    ngx_event_process_posted(cycle, &ngx_posted_accept_events);

    在一个 work 进程的情况下,该队列一般为空,多个 worker 进程的情况下,连接事件在该处进行处理,具体原因在 ngx_epoll_process_events 中说明

  4. 在执行完 accept 队列的事件后,会立即释放 accept 锁,已保证其它 worker 进程能迅速抢占 accept 锁处理后续的连接请求

    if (ngx_accept_mutex_held) {
    ngx_shmtx_unlock(&ngx_accept_mutex);
    }

  5. 处理定时器

    if (delta) {
    ngx_event_expire_timers();
    }

  6. 处理其他事件

    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 在这个位置

  1. 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 锁

  2. 更新系统时间

    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {    ngx_time_update();}
  3. 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;}
  4. 处理 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 中

  1. 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);
  2. 获取一个空闲的 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;...
  3. 调用连接处理句柄进行处理

    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

  1. 此处跳过连接相关的一些处理,首先是将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;
  2. 挂载新连接的处理函数 ngx_http_wait_request_handler

    rev = c->read;rev->handler = ngx_http_wait_request_handler;c->write->handler = ngx_http_empty_handler;
  3. 如果当前连接为一个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

  4. 此时 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;}
  5. 在 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();}
0 0
原创粉丝点击