NginxTimer的实现

在说主题之前先来说一下Linux中Timer的实现:在Linux中,设置定时器,是通过每次系统定时器时钟的中断处理程序来设置相应的软中断位,然后通过这个中断处理程序扫描系统中所有挂起的定时器,如果发现哪个定时器超时了就调用相应的处理函数,也就说Linux定时器是通过系统中断实现的。

在Nginx中,Timer是自己实现的,而且实现的方法完全不同,它是通过一个红黑树去维护所有的time节点。然后在工作进程中每一循环都会调用ngx_process_events_and_timers函数,这个函数中又会调用处理定时器的函数ngx_event_expire_timers,这个函数每一次都会从红黑树中取最小的时间值,判断是否超时,超时就执行他们的函数,直到取出的不超时为止。类似的做法还有libevent中的小根堆维护time的思想,这两种思想是类似的,而且也能把定时器的超时时间和一些I/O事件统一到了工作进程之中。

来看看定时器的初始化Ngx_event.c中:

/*初始化计时器,此处将会创建起一颗红黑色,来维护计时器。*/    
    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
        return NGX_ERROR;
    }
//timer的初始化
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);

#if (NGX_THREADS)

    if (ngx_event_timer_mutex) {
        ngx_event_timer_mutex->log = log;
        return NGX_OK;
    }

    ngx_event_timer_mutex = ngx_mutex_init(log, 0);
    if (ngx_event_timer_mutex == NULL) {
        return NGX_ERROR;
    }

#endif
    return NGX_OK;
}

这个函数就是调用 ngx_rbtree_init来初始化一颗nginx自己实现的红黑树。

接下来就是如何去定义一个timer事件了:

就是利用下面这个函数,将一个定时事件的超时时间值加入到红黑树中即可,与之对应的还有一个从红黑树中删除的操作函数:ngx_event_del_timer

//将一个定时时间加入到红黑树中,交给红黑树维护
static ngx_inline void
ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)   //timer说白了就是一个int类型的值,表示超时的时间值,红黑树节点的key就是这个timer
{
    ngx_msec_t      key;
    ngx_msec_int_t  diff;

    key = ngx_current_msec + timer;  //表示该event的超时时间,为当前时间的值加上超时变量,这个时候可以就代表超时的时间点了

    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_mutex_lock(ngx_event_timer_mutex);

    ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);    //将定时事件的timer域插入到红黑树当中

    ngx_mutex_unlock(ngx_event_timer_mutex);

    ev->timer_set = 1;
}

那么Nginx是如何处理定时事件的

在ngx_process_events_and_timers中对定时事件处理,先去把当前红黑树中最小的事件找到,然后传递给epoll的wait,这一点保证epoll可以处理超时事件,因为如果epoll收到超时事件却没有收到这个那么超时事件就不会得到处理了:

if (ngx_timer_resolution) {
        timer = NGX_TIMER_INFINITE;
        flags = 0;

    } else {
        timer = ngx_event_find_timer();  //找到当前红黑树当中的最小的事件,传递给epoll的wait,保证epoll可以该时间内可以超时,可以使得超时的事件得到处理
        flags = NGX_UPDATE_TIME;

#if (NGX_THREADS)

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

#endif
    }

寻找最小的节点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;
    }

    ngx_mutex_lock(ngx_event_timer_mutex);

    root = ngx_event_timer_rbtree.root;
    sentinel = ngx_event_timer_rbtree.sentinel;

    node = ngx_rbtree_min(root, sentinel);  //找到红黑树中key最小的节点

    ngx_mutex_unlock(ngx_event_timer_mutex);

    timer = (ngx_msec_int_t) (node->key - ngx_current_msec);//返回的是还剩下的超时时间

    return (ngx_msec_t) (timer > 0 ? timer : 0);
}

在ngx_process_events_and_timers函数中获取这个值,因为这个值是要用来设置epoll的wait时间的,保证epoll在处理的时候,最小的超时事件既然能及时得到处理,其他就都能都得到处理了,否则,已经超时了,epoll还未处理,那也就不能达到定时的目的了.

接下来:

就是说epoll wait事件是耗时的,在耗时的时间中,已经超时的就应该被删除,同时调用相应的处理函数处理.

/*delta是上文对epoll wait事件的耗时统计,存在毫秒级的耗时 
        就对所有事件的timer进行检查,如果time out就从timer rbtree中, 
        删除到期的timer,同时调用相应事件的handler函数完成处理。 
      */  
    if (delta) {
        ngx_event_expire_timers();
    }

在ngx_process_events_and_timers中调用gx_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 ( ;; ) {

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

总结:

在Nginx中Timer的实现是通过这种从红黑树中选取最小定时事件的方式,利用了红黑树查找的高效,再结合自己对性能的优化,将定时器融入到Worker进程之中,在Nginx的运行环境之中,这种方式可能比Linux中断那一套更为高效,真的是太有智慧了!