Nginx中对事件处理的定时器是利用红黑树实现的,下来逐步分析一下nginx如何对定时器实现的。
首先,Nginx的工作进程是一个无限for循环,主要代码如下:
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
……
// 工作进程初始化调用
ngx_worker_process_init(cycle, worker);
……
// 无限循环
for ( ;; ) {
……
ngx_process_events_and_timers(cycle);
……
}
}
无限循环中调用的 ngx_process_events_and_timers 就是处理事件的函数。再这之前我们需要了解 ngx_event_timer_init 函数,该函数在工作进程初始化 ngx_worker_process_init 在初始化事件模块时调用的,具体函数如下:
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;
}
添加事件至事件树上其函数为: ngx_event_add_timer,主要代码如下:
static ngx_inline void ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
{
// 当前时间 + 定时时间,计算出将来的某一超时时刻
key = ngx_current_msec + timer;
……
ev->timer.key = key;
// 将事件添加至事件的红黑树上
ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
……
}
查询事件树上timer最小的函数为:ngx_event_find_timer,该函数主要查询是不是有事件已经到期或者过期,当过期或者到期都返回 0。具体代码如下:
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;
}
root = ngx_event_timer_rbtree.root;
sentinel = ngx_event_timer_rbtree.sentinel;
node = ngx_rbtree_min(root, sentinel);
timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
return (ngx_msec_t) (timer > 0 ? timer : 0);
}
了解了上述三个函数,下来分析一下工作进程循环里的ngx_process_events_and_timers函数,主要代码如下:
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
……
// 为epoll的wait提供一个超时时间,以防止epoll的wait阻塞时间太长
timer = ngx_event_find_timer();
……
// 调用 epoll_wait 去监听并处理读写事件
(void) ngx_process_events(cycle, timer, flags);
……
if (delta) {
ngx_event_expire_timers();
}
……
}
上述代码中每次for循环开始都会查找事件树中最小超时事件的时间,将该时间作为epoll_wait函数的超时时间,以防止epoll_wait函数阻塞时间过长。后续的 delta 变量为执行ngx_event_expire_timers函数的时间,如果该时间超过1ms则事件树上有可能有事件到期,则遍历事件树执行到期时间的handler并移除该过期事件,函数为ngx_event_expire_timers,主要代码如下:
void ngx_event_expire_timers(void)
{
……
for ( ;; ) {
……
// 找出最小key值的
node = ngx_rbtree_min(root, sentinel);
// 如果最小的key对于的事件没过期则返回
if ((ngx_msec_int_t) (node->key - ngx_current_msec) > 0) {
return;
}
ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
……
// 删除刚才找出的事件节点
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
……
// 执行事件的回调函数
ev->handler(ev);
}
}
执行完ngx_event_expire_timers后一次for循环就算是执行完了,后续一直循环执行就可以。至此,nginx的工作进程定时器处理事件的也算是整个完了。
结合上一章Nginx事件模块学习之连接_向往天空的鱼,简单梳理一下nginx处理事件的主要流程图如下: