Nginx的定时事件的实现(timer)
阅读原文时间:2023年07月10日阅读:1

在前面的文章里面就说到了在事件循环中除了要处理所有的从epoll中获取的事件之外,还要处理一些timer事件,这篇文章就讲讲Nginx的timer是如何实现的。

在讲Nginx的实现之前,我们可以先回顾一下linux的定时器的实现。在linux中通过每次系统定时器时钟的中断的中断处理程序来设置相应的软中断位,该软中断的中断处理程序目的就是为了扫描系统中所有挂起的定时器,如果他们已经超时的话,那么就调用他们相应的函数,这样说白了就是利用中断来实现定时器。

而在Nginx中,timer是自己实现的,而且实现的方法完全不同,而是通过红黑树来维护所有的timer节点,在worker进程的每一次循环中都会调用ngx_process_events_and_timers函数,在该函数中就会调用处理定时器的函数ngx_event_expire_timers,每次该函数都不断的从红黑树中取出时间值最小的,查看他们是否已经超时,然后执行他们的函数,直到取出的节点的时间没有超时为止。其实在Nginx的运行环境中,这种实现方式可能比linux本身的实现方式更为高效。

首先我们来看Nginx的定时器的初始化,在ngx_event_process_init函数中( 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, );  
if (ngx\_event\_timer\_mutex == NULL) {  
    return NGX\_ERROR;  
}

#endif
return NGX_OK;
}

其实该函数非常的简单,说白了就是直接调用ngx_rbtree_init函数来初始化一颗红黑树而已(红黑树是Nginx自己实现的)。

接下来来看如何定义一个timer事件:

//将一个事件加入到红黑树当中,它的超时未timer时间
static ngx_inline void ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer) //timer说白了就是一个int的值,表示超时的事件,用于表示红黑树节点的key
{
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, ,  
                       "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, ,  
               "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 = ;  

}

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

接下来我们来看看Nginx到底是如何处理定时事件的。在ngx_process_events_and_timers函数中首先会有如下的代码

if (ngx\_timer\_resolution) {  
    timer = NGX\_TIMER\_INFINITE;  
    flags = ;

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

#if (NGX_THREADS)

    if (timer == NGX\_TIMER\_INFINITE || timer > ) {  
        timer = ;  
    }

#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 >  ? timer : );  

}

该函数其实还是很简单的,说白了就是找到红黑树中key的值最小的节点,然后返回它还剩下的超时时间就可以了。在ngx_process_events_and_timers函数中之所以要获取这个值,是为了为epoll的wait提供一个超时时间,以防止epoll的wait阻塞时间太长,影响了timer的处理。接着或有如下的代码:

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

在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;

//死循环,找到所有的超时的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) <= ) {   //判断该节点是否超时,如果超时的话,就执行处理函数,否则就可以跳出循环了  
        //通过偏移来获取当前timer所在的event  
        ev = (ngx\_event\_t \*) ((char \*) node - offsetof(ngx\_event\_t, timer));

        ngx\_log\_debug2(NGX\_LOG\_DEBUG\_EVENT, ev->log, ,  
                       "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 = ;

        ev->timedout = ;

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

        continue;  
    }

    break;  
}

ngx\_mutex\_unlock(ngx\_event\_timer\_mutex);  

}

该函数其实很简单的,一看就看明白了,说白了就是一个死循环,不断的从红黑树中获取key最小的元素,如果超时的话,就通过偏移量来获取其所在的event,然后执行handler,直到找到一个没有超时的timer为止,跳出循环。

嗯,到这里timer就讲完了。

转自:http://www.xuebuyuan.com/2041520.html