nginx缓存过期判定与红黑树管理深度耦合:缓存条目按绝对过期时间升序构建红黑树,根节点即最快过期项;过期检测仅o(1)检查根节点,触发后逐个删除并重平衡;cache manager进程作为定时器应答员,依此机制执行磁盘清理。

要真正理解 Nginx 缓存过期判定和红黑树内存索引的高效管理,关键不是看表层配置,而是抓住两个协同运转的核心:**缓存条目如何在内存中组织(红黑树索引)**,以及**过期动作如何被触发与执行(定时器驱动)**。两者不独立,而是通过统一的时间轴(毫秒级绝对时间)和共享的数据结构(ngx_rbtree_node_t嵌入)深度耦合。
缓存条目怎么用红黑树组织?不是哈希,是按时间排序
Nginx 的缓存索引不是靠哈希表快速定位某一个 key,而是用红黑树把所有缓存条目按“过期时间”升序排列。每个缓存项(如 ngx_http_file_cache_node_t)内部都嵌入一个 timer 字段 —— 类型是 ngx_rbtree_node_t,其 key 字段存的是该缓存的绝对过期时刻(单位毫秒,值为 ngx_current_msec + expire_time)。
这意味着:
- 树根节点永远代表“最快将过期”的缓存项,无需遍历全树就能知道谁该先处理
- 插入新缓存时调用
ngx_rbtree_insert(&cache->rbtree, &node->queue),比较逻辑只看key,不关心内容是什么 - 查找某个 URL 对应的缓存,走的是另一套路径(基于 MD5 cache key 的哈希桶 + 单链表),红黑树不参与“查 key”,只负责“管时间”
过期判定不是轮询,而是“只盯根节点”的 O(1) 检查
缓存过期检测的高性能,来自一个极简却关键的设计:Nginx 从不遍历整棵红黑树去找过期项。它只在每次事件循环末尾调用 ngx_event_expire_timers(),反复检查红黑树根节点是否已超时:
- 若
root != sentinel && root->key ,说明这个缓存已过期 - 立刻调用
ngx_rbtree_delete()移除该节点,并触发其宿主结构体(比如ngx_http_file_cache_node_t)的清理逻辑 - 删除后树自动重平衡,新根再次暴露出来 —— 继续判断,直到根未过期或树为空
整个过程是“剥洋葱式”的单向推进,一次循环最多处理多个连续过期项,但每次判断都是 O(1),增删是 O(log n),没有隐式遍历开销。
cache manager 进程干啥?它不主动扫描,只响应定时器
很多人误以为 cache manager 是个后台线程定期扫磁盘找过期文件。其实它本质是一个**专用事件驱动进程**,只做一件事:维护并响应一个全局定时器事件。
它的核心逻辑是:
- 启动时注册 handler:
ngx_cache_manager_process_handler - 该 handler 被绑定到一个长期存在的
ngx_event_t上,其ev->timer.key设为下次清理检查时间(比如 5 秒后) - 每当该定时器触发,handler 就运行 —— 它会遍历内存中的红黑树索引,对已过期的节点执行磁盘文件删除、元数据清理等操作
- 清理完立即重设下一次定时器,形成稳定节奏
换句话说:cache manager 不是“巡检员”,而是“应答员”;真正决定“何时清理”的,是红黑树根节点的时间戳和事件循环的定时器机制。
为什么不用标准定时器?因为缓存过期必须和请求生命周期解耦
HTTP 请求相关的定时器(如 ngx_add_timer(ev, 60000))属于事件模块,服务于单次连接或 upstream 超时;而缓存过期是系统级资源管理任务,必须跨请求、跨连接、持久存在。
所以 Nginx 为 cache manager 单独开辟进程空间,并使用独立的红黑树(ngx_cache_manager_ctx 关联的 timer 树),避免和 HTTP 事件的 ngx_event_timer_rbtree 混淆。两棵树共用同一套红黑树实现,但用途隔离、生命周期独立、插入/删除入口不同。











