nginx中到处充斥着定时器,那么nginx中的定时器是怎么来管理的?是不是每个定时都要检查是否超时?要是这样,那效率太低下了,细读源码来看看nginx中的定时器是怎么样的。
首先来一个大概的认识:nginx中的定时器是放在一个全局的叫做ngx_event_timer_rbtree的红黑树中的,通过ngx_add_timer来添加定时器,等超时定时事件执行完后就从红黑树中移除该定时器,所以想要达到循环定时的目的,那就需要每次在执行定时操作后再次通过ngx_add_timer添加到定时列表中。
#define ngx_add_timer ngx_event_add_timer
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; /*当前时间 + 定时时间,这个值是固定的,表示将来的某一时刻,作为红黑树中的key*/
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) { /*如果距离超时时间 < 300ms,此时无需添加新的定时器,用已有的定时器就可以了*/
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); /*新节点插入红黑树,注意的是我们并没有插入整个event,而是插入了ev->timer*/
ev->timer_set = 1;/*状态置为1*/
}
再来看超时检测
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_time */
if ((ngx_msec_int_t) (node->key - ngx_current_msec) > 0) { /*还没超时*/
return;
}
ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));/*由于在插入红黑树时插入的是ev->timer,所以这个地方需要偏移来得到完整的event*/
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;/*状态置为 0*/
ev->timedout = 1; /*设为超时*/
ev->handler(ev);/*执行定时操作*/
}
}
注意的地方:
由于在红黑树总保持的是ev->timer,而不是整个完整的event,所以想要获取完整的event就需要偏移。
我们知道,红黑树的优点是有良好的插入和查询性能,那么这里的红黑树是否能用小根堆来实现呢?