Nginx模块中应使用ngx_event_add_timer而非ngx_add_timer来为ngx_event_t事件添加毫秒级相对超时;需独立分配并长期持有事件结构,设置handler与log,在handler中根据ev->timedout判断触发状态,周期任务需手动重设,注意worker级生命周期管理。

在 Nginx 模块开发中,ngx_add_timer 并不是一个直接暴露给模块开发者的公共 API。它属于 Nginx 内部事件调度机制的一部分,通常由核心(如 ngx_event_timer_init)初始化后,通过 ngx_event_add_timer(注意:不是 ngx_add_timer)供模块使用。你真正能调用的是 ngx_event_add_timer,用于为一个已注册的事件(ngx_event_t *)添加超时时间。
1. 准备一个可重用的定时器事件结构
每个定时任务需绑定一个独立的 ngx_event_t 实例。不能复用连接或请求的事件,应自行分配并长期持有(例如挂到模块配置或周期性任务上下文中)。
- 在模块配置结构体中定义:
ngx_event_t *my_timer; - 在
create_conf或初始化阶段用ngx_pcalloc(cf->pool, sizeof(ngx_event_t))分配 - 设置
ev->handler = my_timer_handler;,指向你的回调函数 - 确保
ev->log已正确赋值(如cf->log),否则日志可能崩溃
2. 正确注册并启动定时器
调用 ngx_event_add_timer(ev, delay) 启动定时器。注意:
-
delay单位是毫秒,且是**相对当前时间的偏移量**(非绝对时间) - 该函数仅设置超时值,不触发 handler;Nginx 主循环会在到期时自动调用
ev->handler - 若定时器已处于待触发/已触发状态,再次调用会更新超时时间(即“重设”)
- 必须保证
ev在整个生命周期内有效(不能分配在栈上或短生命周期内存中)
3. 在 handler 中处理逻辑并决定是否重复
你的 my_timer_handler 是唯一执行业务的地方。典型模式如下:
- 先检查
ev->timedout—— 正常到期时为 1,被显式取消则为 0 - 执行实际任务(如清理缓存、上报指标、重试失败请求等)
- 如需周期执行,再次调用
ngx_event_add_timer(ev, interval) - 若不再需要,可调用
ngx_del_timer(ev)显式移除(非必须,但推荐)
示例 handler 片段:
static void
my_timer_handler(ngx_event_t *ev)
{
my_ctx_t *ctx = ev->data;
if (!ev->timedout) {
return; // 被主动删除,跳过
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "my timer fired");
// 执行你的任务...
do_my_background_work(ctx);
// 重新设定下一次(比如每 5 秒一次)
ngx_event_add_timer(ev, 5000);
}
4. 注意生命周期与线程安全
Nginx 是单进程多线程(worker 进程模型),但每个 worker 是单线程事件循环。因此:
- 定时器事件和 handler 始终运行在所属 worker 的主循环线程中,无需额外加锁(除非访问跨 worker 共享资源)
- 模块配置中的 timer 指针需按 worker 独立初始化(例如在
ngx_http_my_module_init_worker钩子中) - worker 进程退出前,应在
exit_process阶段调用ngx_del_timer并释放相关内存 - 避免在 handler 中执行阻塞操作(如同步文件 I/O、sleep、长循环),否则会卡住整个 event loop
不复杂但容易忽略:定时器不是“开箱即用”的 cron,而是基于事件驱动的轻量级延迟调度。关键是把 ngx_event_t 当作任务句柄来管理,而不是试图封装成高级定时器库。

















