nginx的定時器源碼分析

編寫服務器常常會需要實現定時器功能。windows下有微軟封得好好的控件,拖之即用,Linux下面就算了,還是自己動手吧。

雖說Linux提供了基於信號的定時功能(alarm,settimer),但是,考慮到信號是如此的粗暴,還是算了,在寫高性能服務器的時候,還是別用了。免得被虐。

既然放棄了系統的定時功能,那麼只能在用戶空間自己實現了,思路也很簡單。維護一個時間和一堆定時器事件,每次時間更新便在那一堆定時器事件中找跟當前時間匹配的事件,觸發之。這裏兩個因數決定了定時器的效率,一個就是更新時間的時機和頻率,第二就是事件的插入和查找。

來看看經典的服務器是怎麼做的吧,nginx是將事件存放在了一個rbtree中,memcached的定時器是用的libevent的,libevent同樣將事件存放在了rbtree中(1.4版以後改成最小堆了),而redis則是將其放在了一個鏈表中(而且未排序),不過由於redis中的定時器不多,所以作者說:“可以用skiplist來優化,但是意義不大”。至於時間的管理就看場景了,太頻繁了浪費cpu,太偶爾了定時器又不準。這裏就看開發者對系統的把握了。

下面簡單看看nginx中的定時器是如何實現的。

nginx中定時器的實現主要在nginx_event_timer.c和.h中。先來看看事件管理:

初始化:

1
2
3
4
5
6
7
8
9
ngx_thread_volatile ngx_rbtree_t  ngx_event_timer_rbtree
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;
}

很直接,初始化一個全局的紅黑樹。

下面是插入事件(在.h中,內聯函數):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
    //如果這個事件上上一個定時器的事件跟要設的新定時器相差很小的話
    //就複用以前的定時器,畢竟O(logN)的數操作也是要時間的嘛,能省就省了
    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) {
            return;
        }
        //刪除以前的定時器
        ngx_del_timer(ev);
    }
 
    ev->timer.key = key;
    //鎖一下,添加新的定時器
    ngx_mutex_lock(ngx_event_timer_mutex);
 
    ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
 
    ngx_mutex_unlock(ngx_event_timer_mutex);
 
    ev->timer_set = 1;
}

下面是刪除事件(同樣在.h中):

1
2
3
4
5
6
7
8
9
10
11
12
static ngx_inline void
ngx_event_del_timer(ngx_event_t *ev)
{
    ngx_mutex_lock(ngx_event_timer_mutex);
 
    ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
 
    ngx_mutex_unlock(ngx_event_timer_mutex);
 
 
    ev->timer_set = 0;
}

同樣是直接暴力。鎖一下,刪除節點。

查找事件同樣很直接(在.c中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
 
    ngx_mutex_unlock(ngx_event_timer_mutex);
    //如果有將來要觸發的,便將時間差算出來,返回
    //這個值將影響到事件循環中等待函數(比如epoll_wait)的超時
    timer = (ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec;
 
    return (ngx_msec_t) (timer > 0 ? timer : 0);
}

最後是超時事件的處理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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);
 
        /* node->key <= ngx_current_time */
 
        if ((ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec <= 0)
        {
            ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, 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);
 
            continue;
        }
 
        break;
    }
 
    ngx_mutex_unlock(ngx_event_timer_mutex);
}

這個函數將所有該觸發的事件一一觸發掉。

好了,nginx中定時器事件的管理就這樣了,那麼nginx是在什麼時候更新時間觸發事件的呢?很簡單,主要在主事件處理函數中,如果發現定時器事件有事件在pending,就會在wait之後更新一下時間。

好了,就是這樣。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章