Nginx 定时器

Nginx 定时器事件

Nginx实现了自己的定时器触发机制,它与网络事件的触发机制不同,网络事件的触发是由内核触发完成的,内核如果支持epoll就使用ngx_epoll_module模块驱动事件,内核如果仅支持select那就得使用ngx_select_module模块驱动事件。

Nginx定时器事件则完全是由nginx自身实现的,它与内核完全无关,  而是通过红黑树来维护所有的timer节点,在worker进程的每一次循环中都会调用ngx_process_events_and_timers函数,在该函数中就会调用处理定时器的函数ngx_event_expire_timers,每次该函数都不断的从红黑树中取出时间值最小的,查看他们是否已经超时,然后执行他们的函数,直到取出的节点的时间没有超时为止。

那么,所有事件的定时器是如何组织起来的呢?在事件超时后,定时器是如何触发事件的呢?

定时器事件如何组织

Nginx 定时器管理

上面说了,Nginx 的定时器是通过红黑树管理的,首先我们来看Nginx的定时器的初始化,在介绍初始化之前,先看Nginx 驱动定时器的机制

 Nginx阻塞于epoll_wait时可能被3类事件唤醒,分别是有

  1. 读写事件发生
  2. 等待时间超时
  3. 信号中断

等待超时和信号中断都是与定时器实现相关的,它们的初始化发生在ngx_event_core_module模块的进程初始化阶段,代码段如下, 在ngx_event_process_init函数中( src/event/ngx_event.c):

//初始化维护定时器的红黑树

if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
        return NGX_ERROR;
 }

.....

 

if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
        struct sigaction  sa;
        struct itimerval  itv;

        ngx_memzero(&sa, sizeof(struct sigaction));
        sa.sa_handler = ngx_timer_signal_handler;
        sigemptyset(&sa.sa_mask);

        if (sigaction(SIGALRM, &sa, NULL) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "sigaction(SIGALRM) failed");
            return NGX_ERROR;
        }

        itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
        itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
        itv.it_value.tv_sec = ngx_timer_resolution / 1000;
        itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;

        if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "setitimer() failed");
        }
    }

......

使用setitimer系统调用设置系统定时器,每当到达时间点后将发生SIGALRM信号,同时epoll_wait的阻塞将被信号中断从而被唤醒执行定时事件。其实,这段初始化并不是一定会被执行的,它的条件ngx_timer_resolution就是通过配置指令timer_resolution来设置的,如果没有配置此指令,就不会执行这段初始化代码了。也就是说,配置文件中使用了timer_resolution指令后,epoll_wait将使用信号中断的机制来驱动定时器,否则将使用定时器红黑树的最小时间作为epoll_wait超时时间来驱动定时器

接下来我们看ngx_event_timer_init这个函数是怎么实现的, 在 src/event/ngx_event_timer.c 文件里

/*
 * the event timer rbtree may contain the duplicate keys, however,
 * it should not be a problem, because we use the rbtree to find
 * a minimum timer value only
 */

ngx_int_t
ngx_event_timer_init(ngx_log_t *log)
{
    ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
                    ngx_rbtree_insert_timer_value);

    return NGX_OK;
}

就是初始化了一个红黑树

接下来我们看怎么将定时器加入红黑树

 

static ngx_inline void
ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
{
    ngx_msec_t      key;
    ngx_msec_int_t  diff;

    key = ngx_current_msec + timer;  //这个事件超时的时间点

    if (ev->timer_set) {  //如果这个事件已经设置了定时器事件

        /*
         * Use a previous timer value if difference between it and a new
         * value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows
         * to minimize the rbtree operations for fast connections.
         */

        diff = (ngx_msec_int_t) (key - ev->timer.key);

        if (ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) {
            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
                           "event timer: %d, old: %M, new: %M",
                            ngx_event_ident(ev->data), ev->timer.key, key);
            return;
        }

        ngx_del_timer(ev);  //先删除了
    }

    ev->timer.key = key;

    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
                   "event timer add: %d: %M:%M",
                    ngx_event_ident(ev->data), timer, ev->timer.key);

    ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer); //加到红黑树里

    ev->timer_set = 1;  //更新加入定时器的标记

该函数用于将事件加入到红黑树中,首先设置超时时间,也就是当前的时间加上传进来的超时时间。然后再将timer域加入到红黑树中就可以了,这里timer域的定义说白了是一棵红黑树节点。然后还有一个函数ngx_event_del_timer,它用于将某个事件从红黑树当中移除。

处理定时事件

nginx 处理定时器事件在 ngx_process_events_and_timers函数中实现

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    ngx_uint_t  flags;
    ngx_msec_t  timer, delta;

    if (ngx_timer_resolution) {   //如果在配置文件中配置了 timer_resolution , 走这个分支,此处NGX_TIMER_INFINITE的值为-1, 这个timer 最终要传入epoll_wait, epoll_wait man 手册里这么讲:

      /*Specifying a timeout of -1 causes epoll_wait() to block indefinitely,
       while specifying a timeout equal to zero cause epoll_wait() to return immediately,

        even if no events are available

       */
        timer = NGX_TIMER_INFINITE;
        flags = 0;

    } else {

        //找到当前红黑树当中的最小的事件,传递给epoll的wait,保证epoll可以该时间内可以超时,可以使得超时的事件得到处理

        timer = ngx_event_find_timer();  
        flags = NGX_UPDATE_TIME;

#if (NGX_WIN32)

        /* handle signals from master in case of network inactivity */

        if (timer == NGX_TIMER_INFINITE || timer > 500) {
            timer = 500;
        }

#endif
    }

    if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }

            if (ngx_accept_mutex_held) {
                flags |= NGX_POST_EVENTS;

            } else {
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }

    delta = ngx_current_msec;

    (void) ngx_process_events(cycle, timer, flags); //在这个函数里将timer 传入epoll_wait 

    delta = ngx_current_msec - delta;  

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "timer delta: %M", delta);

    ngx_event_process_posted(cycle, &ngx_posted_accept_events);

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

     /*delta是上文对epoll wait事件的耗时统计,存在毫秒级的耗时 就对所有事件的timer进行检查,

       如果time out就从timer rbtree中, 删除到期的timer,同时调用相应事件的handler函数完成处理。

     */

     if (delta) {
        ngx_event_expire_timers();
     }

     ngx_event_process_posted(cycle, &ngx_posted_events);

}

在ngx_process_events_and_timers函数中调用ngx_event_expire_timers函数来处理所有的定时事件。嗯,这里可以看到一个比较奇怪的现象,干嘛要判断delta的值呢,嗯,其实这个值是统计处理其余事件的用时,如果用时超过了毫秒,那么才会真正的调用ngx_event_expire_timers函数来处理所有的定时,否则不会,因为距离上次循环间隔太小,完全没有必要。接下来来看ngx_event_expire_timers函数:

//处理红黑树中所有超时的事件
void
ngx_event_expire_timers(void)
{
    ngx_event_t        *ev;
    ngx_rbtree_node_t  *node, *root, *sentinel;

    sentinel = ngx_event_timer_rbtree.sentinel;

    //for循环,遍历找到所有的超时的timer,然后处理他们
    for ( ;; ) {

        ngx_mutex_lock(ngx_event_timer_mutex);

        root = ngx_event_timer_rbtree.root;

        if (root == sentinel) {
            return;
        }

        node = ngx_rbtree_min(root, sentinel);   //获取key最小的节点

        /* node->key <= ngx_current_time */

        if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) {   //判断该节点是否超时,如果超时的话,就执行处理函数,否则就可以跳出循环了
            //通过偏移来获取当前timer所在的event
            ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));

            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
                           "event timer del: %d: %M",
                           ngx_event_ident(ev->data), ev->timer.key);
            //将当前timer移除
            ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);

            ngx_mutex_unlock(ngx_event_timer_mutex);

            ev->timer_set = 0;

            ev->timedout = 1;

            ev->handler(ev);   //调用event的handler来处理这个事件

            continue;
        }

        break;
    }

    ngx_mutex_unlock(ngx_event_timer_mutex);
}

该函数遍历红黑树,不断的从红黑树中获取key最小的元素,如果超时的话,就通过偏移量来获取其所在的event,然后执行handler,直到找到一个没有超时的timer为止,跳出循环

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值