Nginx定时器基于定制红黑树实现,节点按超时时间升序排列,根节点即最近超时项;过期检测仅检查根节点,O(1)获取、O(log n)增删;定时器通过ngx_add_timer/ngx_del_timer管理,节点须嵌入宿主结构体且不可重复插入。

在 Nginx 源码中,定时器(timer)的高效管理依赖于红黑树 ngx_rbtree_t,它并非通用红黑树,而是为超时事件高度定制的结构:所有节点按超时时间升序排列,最小时间总在树根或最左路径上,从而支持 O(log n) 插入/删除 + O(1) 获取最近超时节点。过期检测不靠轮询全树,而是每次只检查根节点是否到期——这是性能关键。
红黑树如何组织定时器节点
Nginx 定时器节点类型为 ngx_rbtree_node_t,但真正携带业务逻辑的是嵌入它的结构体(如 ngx_event_t),通过 rbtree_key 字段存储绝对超时时间(毫秒级,单位是 ngx_msec_t,本质是 int64_t)。红黑树本身不存回调或上下文,仅负责按时间排序;实际事件处理由外部驱动(如 ngx_event_expire_timers())。
关键点:
- 红黑树比较逻辑由
ngx_rbtree_insert_value实现,只比key值,不关心节点内容 - 所有定时器节点必须调用
ngx_rbtree_insert(&ngx_event_timer_rbtree, node)插入,不能直接操作树结构 - 节点插入前需预先设置
node->key = ngx_current_msec + delay,即绝对超时时刻
过期检测的核心流程:只看根,不遍历
Nginx 不在每次事件循环中遍历整棵树找过期项,而是反复检查红黑树根节点是否已超时。一旦根节点超时,就摘下它、执行回调、再继续检查新根——直到根未超时或树空。该逻辑封装在 ngx_event_expire_timers() 中,通常被 ngx_process_events_and_timers() 调用。
典型步骤如下:
- 进入函数后,先取
ngx_event_timer_rbtree.root - 若根非哨兵(
!= &ngx_event_timer_rbtree.sentinel)且root->key ,说明已过期 - 调用
ngx_rbtree_delete()移除该节点,并强制重平衡 - 从原节点反向推导出宿主结构(如
ngx_event_t *),调用其handler(如ngx_event_expire_timer) - 循环重复,直到根不再过期
手动添加/取消定时器的正确姿势
开发者极少直接操作红黑树,而是通过 Nginx 事件接口间接管理:
- 添加定时器:调用
ngx_add_timer(rev/ev, delay),内部会初始化ev->timer.key并插入树 - 取消定时器:调用
ngx_del_timer(rev/ev),内部检查ev->timer.left != NULL(即是否已在树中),再安全删除 - 修改定时器:Nginx 不提供原子更新接口,必须先
del再add,避免残留或重复插入 - 注意:
ev->timer是ngx_rbtree_node_t类型字段,必须作为结构体成员嵌入,不可单独 malloc
调试与常见陷阱
红黑树行为不易肉眼验证,调试时建议结合日志与断点:
- 在
ngx_event_expire_timers开头打印ngx_event_timer_rbtree.root->key和ngx_current_msec,观察是否及时触发 - 误将同一事件节点多次
add_timer会导致树结构损坏(重复插入相同 key 或指针),引发段错误或漏触发 - 未在事件 handler 中调用
ngx_del_timer就直接free事件结构,会造成悬垂指针——红黑树仍持有已释放内存地址 - 跨线程操作定时器(如多 worker 下共享 event)不安全,Nginx 定时器严格绑定当前 event loop,无锁设计不支持并发修改

















