Nginx 定时器事件
Nginx实现了自己的定时器触发机制,它与网络事件的触发机制不同,网络事件的触发是由内核触发完成的,内核如果支持epoll就使用ngx_epoll_module模块驱动事件,内核如果仅支持select那就得使用ngx_select_module模块驱动事件。
Nginx定时器事件则完全是由nginx自身实现的,它与内核完全无关, 而是通过红黑树来维护所有的timer节点,在worker进程的每一次循环中都会调用ngx_process_events_and_timers函数,在该函数中就会调用处理定时器的函数ngx_event_expire_timers,每次该函数都不断的从红黑树中取出时间值最小的,查看他们是否已经超时,然后执行他们的函数,直到取出的节点的时间没有超时为止。
那么,所有事件的定时器是如何组织起来的呢?在事件超时后,定时器是如何触发事件的呢?
定时器事件如何组织
Nginx 定时器管理
上面说了,Nginx 的定时器是通过红黑树管理的,首先我们来看Nginx的定时器的初始化,在介绍初始化之前,先看Nginx 驱动定时器的机制
Nginx阻塞于epoll_wait时可能被3类事件唤醒,分别是有
- 读写事件发生
- 等待时间超时
- 信号中断
等待超时和信号中断都是与定时器实现相关的,它们的初始化发生在ngx_event_core_module模块的进程初始化阶段,代码段如下, 在ngx_event_process_init函数中( src/event/ngx_event.c):
//初始化维护定时器的红黑树
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
return NGX_ERROR;
}.....
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");
}
}......
使用setitimer系统调用设置系统定时器,每当到达时间点后将发生SIGALRM信号,同时epoll_wait的阻塞将被信号中断从而被唤醒执行定时事件。其实,这段初始化并不是一定会被执行的,它的条件ngx_timer_resolution就是通过配置指令timer_resolution来设置的,如果没有配置此指令,就不会执行这段初始化代码了。也就是说,配置文件中使用了timer_resolution指令后,epoll_wait将使用信号中断的机制来驱动定时器,否则将使用定时器红黑树的最小时间作为epoll_wait超时时间来驱动定时器
接下来我们看ngx_event_timer_init这个函数是怎么实现的, 在 src/event/ngx_event_timer.c 文件里
/*
* the event timer rbtree may contain the duplicate keys, however,
* it should not be a problem, because we use the rbtree to find
* a minimum timer value only
*/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;key = ngx_current_msec + timer; //这个事件超时的时间点
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_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer); //加到红黑树里
ev->timer_set = 1; //更新加入定时器的标记
该函数用于将事件加入到红黑树中,首先设置超时时间,也就是当前的时间加上传进来的超时时间。然后再将timer域加入到红黑树中就可以了,这里timer域的定义说白了是一棵红黑树节点。然后还有一个函数ngx_event_del_timer,它用于将某个事件从红黑树当中移除。
处理定时事件
nginx 处理定时器事件在 ngx_process_events_and_timers函数中实现
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;if (ngx_timer_resolution) { //如果在配置文件中配置了 timer_resolution , 走这个分支,此处NGX_TIMER_INFINITE的值为-1, 这个timer 最终要传入epoll_wait, epoll_wait man 手册里这么讲:
/*Specifying a timeout of -1 causes epoll_wait() to block indefinitely,
while specifying a timeout equal to zero cause epoll_wait() to return immediately,even if no events are available
*/
timer = NGX_TIMER_INFINITE;
flags = 0;} else {
//找到当前红黑树当中的最小的事件,传递给epoll的wait,保证epoll可以该时间内可以超时,可以使得超时的事件得到处理
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;#if (NGX_WIN32)
/* handle signals from master in case of network inactivity */
if (timer == NGX_TIMER_INFINITE || timer > 500) {
timer = 500;
}#endif
}if (ngx_use_accept_mutex) {
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;} else {
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}delta = ngx_current_msec;
(void) ngx_process_events(cycle, timer, flags); //在这个函数里将timer 传入epoll_wait
delta = ngx_current_msec - delta;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"timer delta: %M", delta);ngx_event_process_posted(cycle, &ngx_posted_accept_events);
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}/*delta是上文对epoll wait事件的耗时统计,存在毫秒级的耗时 就对所有事件的timer进行检查,
如果time out就从timer rbtree中, 删除到期的timer,同时调用相应事件的handler函数完成处理。
*/
if (delta) {
ngx_event_expire_timers();
}ngx_event_process_posted(cycle, &ngx_posted_events);
}
在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;
//for循环,遍历找到所有的超时的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) <= 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);
}
该函数遍历红黑树,不断的从红黑树中获取key最小的元素,如果超时的话,就通过偏移量来获取其所在的event,然后执行handler,直到找到一个没有超时的timer为止,跳出循环