1. 前言
Nginx 作为高性能的 Web 服务器和反向代理,其 HTTP 缓存功能在降低后端负载、加速内容分发上起着至关重要的作用。proxy_cache_path 和 proxy_cache 等配置指令背后,是一套精心设计的缓存管理机制。本文将从 源码角度 拆解 Nginx HTTP 缓存的核心实现,覆盖 cache zone 内存组织 、内存不足时的 LRU 淘汰 、缓存命中判断 、命中缓存发送流程 、缓存文件内部结构与 cache manager 进程 ,以及 冷启动时缓存元数据加载 等常见问题。
文中源码基于 Nginx 1.25.x 版本(核心逻辑长期稳定),涉及的源文件主要为 src/http/ngx_http_file_cache.c、src/http/ngx_http_cache.h 和 src/core/ngx_rbtree.h 等。阅读本文后,你将能清晰回答:一个 HTTP 请求是如何命中磁盘缓存的、共享内存里到底存了哪些数据,以及缓存空间不足时 Nginx 如何优雅地挤掉旧数据。
2. Cache Zone 内存组织
2.1 共享内存布局
proxy_cache_path keys_zone=NAME:SIZE 声明的 Zone 会创建一块共享内存(ngx_shm_zone_t),多个 worker 进程通过这块内存共享缓存索引。其核心结构体在 ngx_http_cache.h 中:
c
typedef struct {
ngx_http_file_cache_t *cache;
ngx_str_t name;
ngx_shm_zone_t *shm_zone;
} ngx_http_file_cache_shm_t;
而真正的缓存主结构 ngx_http_file_cache_t 包含:
c
typedef struct {
ngx_rbtree_t rbtree; // 红黑树,key 为缓存 key 的 CRC32
ngx_rbtree_node_t sentinel; // 红黑树哨兵节点
ngx_queue_t queue; // 双向队列,用于 LRU
ngx_atomic_t cold; // 1 = 冷缓存,仅插入不读取
off_t max_size; // 缓存目录最大容量
size_t bsize; // 共享内存块大小
ngx_msec_t inactive; // inactive 时间
time_t fail_time;
ngx_uint_t files; // 当前缓存文件总数
ngx_uint_t loader_files; // loader 已加载文件数
// ... 其他字段
} ngx_http_file_cache_t;
共享内存的初始地址 shm.addr 会被转换为该结构体指针,之后的读写都在此块内存上进行。
2.2 缓存节点结构
每一份缓存条目在共享内存中对应一个 ngx_http_file_cache_node_t 节点,它同时被挂载到两棵"树"上:红黑树 (快速查找)和 LRU 双向队列(淘汰顺序)。
c
typedef struct {
ngx_rbtree_node_t node; // 红黑树节点
ngx_queue_t queue; // LRU 队列节点
u_char key[0]; // 变长的缓存 key(紧接结构体之后)
unsigned count:20; // 引用计数
unsigned uses:12; // 访问次数(用于 LRU 权重)
// 缓存的元信息
ngx_http_cache_key_t crc; // key 的 CRC32
time_t expire; // 过期时间
time_t valid_sec; // 有效期
size_t body_start; // body 起始偏移
off_t fs_size; // 文件大小
ngx_msec_t lock_time; // 锁定时间
// ...
} ngx_http_file_cache_node_t;
- 红黑树节点
node的 key 是(crc << 32) | key_hash(实际 64 位 key),通过比较这个 key 来查找缓存条目。 - LRU 队列节点
queue将所有节点串联成一个双向链表,头部是最近最少使用的节点,尾部是最近最常使用的节点。 key字段为柔性数组,紧跟在节点结构体后面存放完整的proxy_cache_key生成的字符串。
这样,每份缓存的内存开销 = sizeof(ngx_http_file_cache_node_t) + key长度,通常约 200~400 字节。一个 10MB 的 keys_zone 大约可以容纳 2~3 万个缓存条目。
2.3 初始化与插入
配置解析时调用 ngx_http_file_cache_set_slot 创建 ngx_http_file_cache_shm_t,然后由 ngx_http_file_cache_init 初始化共享内存中的 ngx_http_file_cache_t,并将红黑树和 LRU 队列准备好。
当有新的缓存文件生成时,ngx_http_file_cache_update 会创建或更新缓存节点:
c
static ngx_int_t
ngx_http_file_cache_update(ngx_http_request_t *r, ngx_temp_file_t *tf)
{
// 1. 计算 crc 和 hash
// 2. 在红黑树中查找或新建 ngx_http_file_cache_node_t
// 3. 更新元信息并使用 LRU 算法调整队列
// 4. 写入缓存文件头
}
关键一点:Nginx 并不立即从共享内存中清除节点,即使磁盘文件已被删除,节点也可能暂时保留,仅在有新文件插入且空间不足时才真正回收。
3. 内存不足时的释放策略
3.1 LRU 淘汰入口
当共享内存中的空闲块不足以分配新节点时,会触发淘汰逻辑,主要入口是 ngx_http_file_cache_free():
c
static ngx_http_file_cache_node_t *
ngx_http_file_cache_free(ngx_http_file_cache_t *cache)
{
ngx_rbtree_node_t *node;
ngx_http_file_cache_node_t *fcn;
while (!ngx_queue_empty(&cache->queue)) {
fcn = ngx_queue_data(ngx_queue_last(&cache->queue),
ngx_http_file_cache_node_t, queue);
if (fcn->count == 0) {
// 从红黑树删除
ngx_rbtree_delete(&cache->rbtree, &fcn->node);
// 归还给 slab 分配器
ngx_slab_free(cache->shpool, fcn);
return fcn;
}
// count > 0,说明节点正在被访问,移动到队首并再次尝试
ngx_queue_remove(&fcn->queue);
ngx_queue_insert_head(&cache->queue, &fcn->queue);
}
return NULL;
}
这段代码会从 LRU 队列尾部 (即最近最少被使用的一端)向前扫描,找到一个引用计数 count == 0 的节点。找到后将其从红黑树和队列中移除,并通过 ngx_slab_free 归还内存。如果当前节点 count > 0,则将其移动到队列头部,继续尝试下一个,避免长时间等待锁。
3.2 引用计数与 inactive 参数
inactive 参数(例如 inactive=1d)指定了缓存文件的"保鲜期"。但它的作用并不是单纯的文件过期时间,而是在 LRU 淘汰和扫描时决定一个节点是否值得保留。
- 在
ngx_http_file_cache_free中并不会检查inactive,即内存淘汰只看引用计数和 LRU 位置。 - 真正的过期清理由 Cache Manager 进程负责,它扫描每个缓存文件的元数据,将
access_time + inactive < now的文件从磁盘和共享内存中删除。 - 此外,当某次请求尝试更新缓存时,也会对过期的旧条目标记
expired = 1,并可能触发删除。
因此,内存不足时的淘汰是纯 LRU + 引用计数 驱动,而 inactive 时间是磁盘文件的老化标准,两者配合使用。
3.3 关于内存碎片
Nginx 使用内部的 ngx_slab_pool_t 管理共享内存,分配小块内存时采用 slab 分类器,可以很好地避免碎片。但频繁分配和释放依然会产生一定程度的外部碎片。因此官方建议预留 keys_zone 比实际估算稍大一些,避免频繁淘汰引发的性能抖动。
下面用一张流程图概括两种清理路径:
#mermaid-svg-orNVYohPWCT9ieKi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-orNVYohPWCT9ieKi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-orNVYohPWCT9ieKi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-orNVYohPWCT9ieKi .error-icon{fill:#552222;}#mermaid-svg-orNVYohPWCT9ieKi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-orNVYohPWCT9ieKi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-orNVYohPWCT9ieKi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-orNVYohPWCT9ieKi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-orNVYohPWCT9ieKi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-orNVYohPWCT9ieKi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-orNVYohPWCT9ieKi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-orNVYohPWCT9ieKi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-orNVYohPWCT9ieKi .marker.cross{stroke:#333333;}#mermaid-svg-orNVYohPWCT9ieKi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-orNVYohPWCT9ieKi p{margin:0;}#mermaid-svg-orNVYohPWCT9ieKi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-orNVYohPWCT9ieKi .cluster-label text{fill:#333;}#mermaid-svg-orNVYohPWCT9ieKi .cluster-label span{color:#333;}#mermaid-svg-orNVYohPWCT9ieKi .cluster-label span p{background-color:transparent;}#mermaid-svg-orNVYohPWCT9ieKi .label text,#mermaid-svg-orNVYohPWCT9ieKi span{fill:#333;color:#333;}#mermaid-svg-orNVYohPWCT9ieKi .node rect,#mermaid-svg-orNVYohPWCT9ieKi .node circle,#mermaid-svg-orNVYohPWCT9ieKi .node ellipse,#mermaid-svg-orNVYohPWCT9ieKi .node polygon,#mermaid-svg-orNVYohPWCT9ieKi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-orNVYohPWCT9ieKi .rough-node .label text,#mermaid-svg-orNVYohPWCT9ieKi .node .label text,#mermaid-svg-orNVYohPWCT9ieKi .image-shape .label,#mermaid-svg-orNVYohPWCT9ieKi .icon-shape .label{text-anchor:middle;}#mermaid-svg-orNVYohPWCT9ieKi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-orNVYohPWCT9ieKi .rough-node .label,#mermaid-svg-orNVYohPWCT9ieKi .node .label,#mermaid-svg-orNVYohPWCT9ieKi .image-shape .label,#mermaid-svg-orNVYohPWCT9ieKi .icon-shape .label{text-align:center;}#mermaid-svg-orNVYohPWCT9ieKi .node.clickable{cursor:pointer;}#mermaid-svg-orNVYohPWCT9ieKi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-orNVYohPWCT9ieKi .arrowheadPath{fill:#333333;}#mermaid-svg-orNVYohPWCT9ieKi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-orNVYohPWCT9ieKi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-orNVYohPWCT9ieKi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-orNVYohPWCT9ieKi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-orNVYohPWCT9ieKi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-orNVYohPWCT9ieKi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-orNVYohPWCT9ieKi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-orNVYohPWCT9ieKi .cluster text{fill:#333;}#mermaid-svg-orNVYohPWCT9ieKi .cluster span{color:#333;}#mermaid-svg-orNVYohPWCT9ieKi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-orNVYohPWCT9ieKi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-orNVYohPWCT9ieKi rect.text{fill:none;stroke-width:0;}#mermaid-svg-orNVYohPWCT9ieKi .icon-shape,#mermaid-svg-orNVYohPWCT9ieKi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-orNVYohPWCT9ieKi .icon-shape p,#mermaid-svg-orNVYohPWCT9ieKi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-orNVYohPWCT9ieKi .icon-shape .label rect,#mermaid-svg-orNVYohPWCT9ieKi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-orNVYohPWCT9ieKi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-orNVYohPWCT9ieKi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-orNVYohPWCT9ieKi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 场景二:Cache Manager 定时清理
场景一:内存不足触发 LRU 淘汰
是
否
是
是
否
否
是
否
Worker 尝试分配新节点
共享内存空闲块不足
调用 ngx_http_file_cache_free()
从 LRU 队列尾部取节点
引用计数 count == 0 ?
从红黑树删除节点
ngx_slab_free 归还内存
新节点成功分配
将节点移至 LRU 队列头部
master 定时器到期
唤醒 Cache Manager 进程
遍历缓存目录,读取文件头
access_time + inactive < now ?
引用计数 count == 0 ?
删除磁盘缓存文件
从共享内存移除节点
跳过,等待下次扫描
总缓存大小 > max_size ?
按 LRU + 过期时间排序删除
暂不清理
manager_sleep 后重新注册定时器
节点从红黑树与 LRU 队列中移除
4. 缓存命中判断
4.1 Cache Key 的生成与查找
Nginx 所使用的 cache key 由 proxy_cache_key 指令定义,默认值为 $scheme$proxy_host$request_uri。生成 key 后,通过 ngx_crc32_long 计算 CRC32,然后调用 ngx_http_file_cache_lookup 在红黑树中查找:
c
static ngx_http_file_cache_node_t *
ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key,
size_t len, ngx_uint_t hash)
{
ngx_rbtree_node_t *node, *sentinel;
ngx_http_file_cache_node_t *fcn;
uintptr_t val;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cache->shpool->log, 0, "cache lookup");
val = ((uintptr_t) hash << 32) | len;
node = cache->rbtree.root;
sentinel = cache->rbtree.sentinel;
while (node != sentinel) {
if (val < node->key) {
node = node->left;
continue;
}
if (val > node->key) {
node = node->right;
continue;
}
// key 相同,进行字符串校验
fcn = (ngx_http_file_cache_node_t *) node;
if (len == fcn->node.data
&& ngx_memcmp(key, fcn->key, len) == 0)
{
return fcn;
}
// 继续在相同 key 的子树中查找
node = (val < node->key) ? node->left : node->right;
}
return NULL;
}
首先使用 (hash << 32) | len 作为红黑树 key 的一部分;若相等,再通过字面比较来避免 hash 冲突。找到节点后,并不代表缓存一定"命中",还需要进行一系列有效性检查。
4.2 有效性验证
查找成功返回节点后,调用方(例如 ngx_http_file_cache_open)会继续验证:
- 冷启动标志 :若
cache->cold为 1,说明 loader 正在加载缓存,此时不认为命中,直接回源。 - 文件状态:检查磁盘上的缓存文件是否存在、尺寸是否正确。
- 新鲜度 :若节点设置了
expire且当前时间已过期,则标记expired = 1,并视配置决定是直接回源还是使用过期内容进行协商(proxy_cache_use_stale)。 - 并发控制 :对于正在更新或刚生成的缓存文件,会有锁机制防止"惊群效应",通过
ngx_http_file_cache_lock实现。
一旦所有检查通过,请求便确认"缓存命中",进入发送流程。
5. 命中缓存的发送过程
5.1 缓存发送总览
确定缓存命中后,Nginx 会构造一个特殊的 handler 链,调用 ngx_http_cache_send:
c
ngx_int_t
ngx_http_cache_send(ngx_http_request_t *r)
{
// 从缓存节点中获取文件信息
// 设置响应头,包括 Content-Type、Content-Length、Last-Modified 等
// 启动文件发送
return ngx_http_upstream_cache_send(r, &ctx->cache);
}
最终的发送使用了 ngx_http_send_header 和 ngx_http_output_filter,对于文件内容,通过 ngx_http_cache_send_body 利用 sendfile 系统调用将磁盘文件直接写入 socket 缓冲区,零拷贝传输。
5.2 Range 请求与 Slice 模块
发送缓存文件时,还需要处理 Range 请求和 Slice 模块。
- 如果客户端请求 Range 头,并且缓存文件有效,Nginx 会读取文件对应的片段进行发送,而不是发送整个文件。
- 当启用
slice指令时,大文件被切割成多个切片,每个切片独立缓存。此时缓存命中需要拼接多个切片,并作为完整响应(或分段)发送。这部分涉及ngx_http_slice_filter_module和ngx_http_file_cache_slice_header等逻辑,不在本文展开。
5.3 优化要点
open_file_cache与缓存发送无关 ,那个指令针对静态文件的 inode 缓存,不适用于proxy_cache。- 高并发场景下,需要考虑
sendfile_max_chunk限制单次传输大小,避免长时间占用 worker。
6. 缓存文件内容与 Cache Manager 进程
6.1 缓存文件内部格式
Nginx 的磁盘缓存文件并非直接保存后端原始响应体,而是在文件头部附加了一段元数据。结构定义在 ngx_http_cache.h:
c
typedef struct {
ngx_http_file_cache_header_t header;
u_char body[1]; // 实际为一个占位,表示 body 开始
} ngx_http_file_cache_file_t;
typedef struct {
ngx_uint_t version; // 版本号
time_t valid_sec; // 缓存有效期
time_t last_modified;// 最后修改时间
time_t date; // 日期
ngx_http_cache_key_t crc; // key 的 CRC32
size_t header_start; // 响应头起始偏移
size_t body_start; // 响应体起始偏移
off_t content_length;// 原始响应体长度
u_char etag[NGX_HTTP_CACHE_ETAG_LEN];
ngx_str_t vary[NGX_HTTP_CACHE_VARY_LEN];
char vary_header[...];
} ngx_http_file_cache_header_t;
写入缓存文件时,ngx_http_file_cache_write_header 会先写入 ngx_http_file_cache_header_t,再写入后端返回的 HTTP 响应头,最后才是响应体。因此一个缓存文件的布局为:
|-- header (固定大小) --|-- 原始响应头(变长) --|-- 响应体 --|
读取时,通过 header_start 和 body_start 定位,配合 ngx_http_file_cache_read_header 解析,从而能够重构出完整的 HTTP 响应。
6.2 Cache Manager 进程
Cache Manager 并不是常驻进程,而是一个由 master 定时唤醒的任务(通过 ngx_process_events_and_timers 中的定时器)。核心函数为 ngx_http_file_cache_manager:
c
static void
ngx_http_file_cache_manager(void *data)
{
ngx_http_file_cache_t *cache = data;
for ( ;; ) {
// 1. 计算应当清理的体积阈值 (watermark)
// 2. 遍历所有缓存文件
while (cache->files < max) {
if (ngx_http_file_cache_manager_delete(cache) != NGX_OK) {
break;
}
}
// 3. 重新注册定时器
ngx_add_timer(&cache->manager_event, cache->manager_sleep);
return;
}
}
主要工作:
- 扫描缓存目录,读取每个文件头。
- 对比
access_time+inactive判断是否过期。 - 若过期且引用计数为 0,删除文件并从共享内存中移除节点。
- 若总缓存大小超过
max_size,则以 LRU + 过期时间结合的方式删除文件,直到降至安全水位。 - 为了避免影响正常请求,manager 在每次扫描后都会
sleep(默认 60s,可配置manager_sleep)。
6.3 与 Worker 协作
当 worker 进程发现缓存文件过期,但 proxy_cache_use_stale 允许使用过期内容时,它会先发送过期数据,同时在后台标记该条目,等待 Manager 进程下次扫描时统一清理。这体现了 Nginx "先服务再清理"的设计哲学。
7. 冷启动时如何处理命中的缓存
7.1 Cache Loader 进程
Nginx 启动或 reload 时,共享内存中的数据全部丢失,但磁盘上仍存有大量缓存文件。为了让 worker 能够命中这些缓存,需要一个 Cache Loader 进程(ngx_http_file_cache_loader)将磁盘元数据加载到共享内存。
c
static void
ngx_http_file_cache_loader(void *data)
{
ngx_http_file_cache_t *cache = data;
for ( ;; ) {
// 1. 打开缓存目录,遍历文件
// 2. 读取每个缓存文件的 header
// 3. 在共享内存中创建 ngx_http_file_cache_node_t
// 4. 限制每次加载文件数,避免长时间阻塞
ngx_add_timer(&cache->loader_event, cache->loader_sleep);
return;
}
}
关键点:
- Loader 在多个 worker 启动之前运行(由 master 发起),也可以配置
loader_files限制每次加载的最大文件数,剩余的留待下次 sleep 后再加载。 - 加载过程中,
cache->cold被置为 1,所有 worker 的缓存查找都会直接返回"未命中",强制回源。这避免了 worker 在索引尚不完整时读到不一致的数据。 - 当 loader 完成全部扫描后,
cold置为 0,worker 即可正常使用缓存。
7.2 冷启动期间的命中策略
在大型站点中,可能有几十万个缓存文件,loader 可能需要若干分钟才能完成加载。这段时间内,所有请求都会回源,后端压力瞬间增大。为了解决这个问题,Nginx 提供了一种折中:
loader_threshold:当 loader 加载的文件数达到此阈值后,会提前将cold置为 0。此时未加载的缓存文件对应的请求依然回源,但已经加载的部分可以正常命中。loader_sleep:每次加载若干文件后暂停一段时间(默认 200ms),将 CPU 和 IO 让给其他进程,避免 loader 抢占过多资源。
另外,Nginx 商业版提供了 cache_key_zone_file 指令,可以将共享内存 dump 到文件,下次启动直接从文件恢复,避免 loader 扫描,不过开源版需要通过 nginx-modules 扩展或自行实现。
7.3 冷启动场景实战
假设缓存目录 cache/ 下有 20 万个文件,keys_zone=one:10m,loader_files=200,loader_sleep=50ms。冷启动过程如下:
- master 进程等待配置加载完毕。
- 调用
ngx_spawn_process创建 loader 子进程,并置cold=1。 - loader 进程每次读取 200 个文件的头部,在共享内存中创建节点。
- 每处理完一批,调用
ngx_msleep(50)休眠。 - 当全部文件扫描完毕(或达到
loader_threshold),loader 进程退出,cold被置为 0。 - 此时 worker 可以命中已加载的缓存。
8. 总结
本文深入了 Nginx HTTP Cache 的内部实现,核心要点回顾:
- 内存组织 :基于共享内存的红黑树 + LRU 双向队列,每个缓存条目以
ngx_http_file_cache_node_t存储,key 为 CRC32 + hash。 - 内存不足淘汰 :
ngx_http_file_cache_free从 LRU 队尾扫描引用计数为 0 的节点,直接归还 slab 内存,与inactive参数解耦。 - 命中判断 :通过红黑树查找,再校验文件存在性、过期状态、
cold标志等。 - 发送流程 :读取 header + 原始响应头,使用
sendfile零拷贝发送,支持 Range 和 Slice。 - Cache Manager :定期扫描磁盘缓存文件,按
inactive+max_size清理过期条目,避免影响正常请求。 - 冷启动 :Cache Loader 进程从磁盘重建共享内存索引,期间
cold标志阻止命中,加载分批进行以降低冲击。
理解这些源码逻辑,不仅有助于排查 Nginx 缓存的奇怪行为(比如"为什么 reload 后缓存全部回源"),也能针对高并发环境设计更合理的 keys_zone 大小、inactive 时间和 loader 参数。希望本文能成为你深入 Nginx 内部的一把钥匙。