nginx中的定时器

服务器中定时器是重要的组成部分,nginx也不例外,nginx将定时器作为一种事件类型来进行处理。在nginx中利用红黑树来存储定时器,至于为什么选择红黑树,个人认为主要是为了兼顾查找、插入、删除的效率而选取的折中方案。

1 定时器的初始化、添加、删除

初始化定时器,即创建一颗红黑树

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;
	//超时的时间为当前时间加上timer
    key = ngx_current_msec + timer;
    ...
    //设置时间的定时器超时时间
    ev->timer.key = key;
    ...
    //将事件的超时时间插入到红黑树中
    ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
    ev->timer_set = 1;
}

删除定时事件

static ngx_inline void ngx_event_del_timer(ngx_event_t *ev)
{
    ...
    ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
	...
    ev->timer_set = 0;
}

2 定时器的触发

nginx中定时器的触发方式有两种,一种是利用信号,另一种是利用epoll的超时返回机制。

信号触发

利用系统函数设置定时事件的到期时间,若到达时间点后,将会产生一个SIGALRM信号,当前的epoll将会中断,转去处理定时事件。系统默认不会采用这种方式,若需要使用这种定时触发方式,在配置文件设置timer_resolution指令即可。

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");
        }
    }

利用epoll的超时机制

该机制是利用epoll超时返回的机制来实现的,当超时返回时,首先进入**ngx_event_expire_timers()**来检查是否有超时事件,若有超时,将其移出红黑树,设置超时标记,并调用回调函数进行处理即处理。在处理完定时器事件后再转去处理其他事件。利用超时机制最关键的一点是如何设置超时时间timer,nginx将定时器中最早到期时间减去当前时间的值作为timer。

void ngx_event_expire_timers(void)
{
    ngx_event_t        *ev;
    ngx_rbtree_node_t  *node, *root, *sentinel;
    sentinel = ngx_event_timer_rbtree.sentinel;
    for ( ;; ) 
	{
        root = ngx_event_timer_rbtree.root;
        if (root == sentinel) {
            return;
        }
		//从红黑树中取出最小的结点
        node = ngx_rbtree_min(root, sentinel);

        /* node->key > ngx_current_msec */
		//没有定时器到期,直接返回
        if ((ngx_msec_int_t) (node->key - ngx_current_msec) > 0) {
            return;
        }
		//将当前的结点地址减去定时器在结构体中的偏移量,获取事件的地址
        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);
		//删除到期的定时事件
        ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);

#if (NGX_DEBUG)
        ev->timer.left = NULL;
        ev->timer.right = NULL;
        ev->timer.parent = NULL;
#endif
		//是否加入到红黑树定时器中
        ev->timer_set = 0;
        //设置超时标记
        ev->timedout = 1;
		//调用定时器的回调函数
        ev->handler(ev);
    }
}


timer = ngx_event_find_timer();
ngx_msec_t ngx_event_find_timer(void)
{
    ngx_msec_int_t      timer;
    ngx_rbtree_node_t  *node, *root, *sentinel;
    if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
        return NGX_TIMER_INFINITE;
    }
    root = ngx_event_timer_rbtree.root;
    sentinel = ngx_event_timer_rbtree.sentinel;
    node = ngx_rbtree_min(root, sentinel);
    //最早到期时间减去当前时间
    timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
    return (ngx_msec_t) (timer > 0 ? timer : 0);
}