知识是人生的灯塔,只有不断学习,才能照亮前行的道路
📢 大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 🚀,若此文对你有帮助,一定记得点个关注⭐与小红星❤️或加入到作者知识星球『 全栈工程师修炼指南』,转发收藏学习不迷路 😋 。
Nginx 后端(上游)缓存指令浅析与实践
上一小节《Nginx | 核心知识150讲,百万并发下性能优化之前端静态缓存、后端动态缓存实践笔记》,讲解了缓存相关概念基础知识,以及实践两个Nginx静态资源缓存的指令配置示例,想必大家都有了一定的了解。接下来,我们将深入探讨Nginx后端(上游)缓存的配置与实践,在此之前,我们将着重讲解 Nginx 的缓存更新流程以及 ngx_http_proxy_module 模块提供了对上游服务动态内容的缓存支持的指令与实践。
温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接:https://articles.zsxq.com/id_6hpaidhf5l5s.html
在讲解之前,同样针对 Nginx 缓存处理流程划分为两个部分进行一个简要介绍,以便于大家在后续的指令学习与实践过程中能有更深的理解。
第一部分:对客户端请求的缓存处理流程
1.检查是否开启 proxy_cache 指令, 必须配置 proxy_cache zone_name; 指令,其中 zone_name 为共享内存块名称, 若未配置,则直接向上游发送请求,且不进行任何缓存操作。
2.检查请求方法是否匹配 proxy_cache_methods 指令规定的方法,默认只缓存 GET 和 HEAD 请求。
3.检查proxy_cache_convert_head 指令决定是否将 HEAD 请求视为 GET 请求处理,若开启转换,则后续流程按GET请求对待,允许使用缓存。
4.使用 proxy_cache_key 指令定义缓存关键字, 默认关键字为 $scheme$proxy_host$request_uri 组成,可自定义变量组合生成关键字,关键字经MD5哈希处理以缩短长度并提高查找效率。
5.检查 proxy_cache_bypass 指令值,若变量值为真(非空且不为"0"),则跳过缓存,直接向上游发送请求,用于动态绕过缓存的场景,如带特定参数时不使用缓存。
6.在 proxy_cache 指定的共享内存区域中查找对应关键字的缓存项(使用二叉树查找),若缓存不存在则在共享内存中分配新节点,标记该请求将使用缓存并向上游发送请求等待响应,若缓存存在更新LRU链表,将当前缓存项移至头部,避免近期被淘汰,更新节点访问次数,用于 proxy_cache_min_uses 指令判断。
7.检查缓存响应状态与过期情况,若缓存响应为错误类(如404、500)且已过期,则不使用缓存,直接向上游发送请求;若非错误响应且未过期,则直接向下游返回缓存内容;若非错误响应但已过期,便根据proxy_cache_background_update 指令判断是否启用后台更新, 若启用,则立即用过期缓存响应客户端,同时发起子请求更新缓存, 若未启用,则阻塞等待上游新响应后再返回。
8.共享内存节点分配失败处理,若首次分配节点失败,尝试淘汰已过期的缓存条目释放空间,再次尝试分配;若再次分配仍失败,放弃使用缓存,请求不再经过缓存模块处理,直接透传至上游。
weiyigeek.top-客户端请求的缓存处理流程图
接收上游响应的缓存处理流程
1.检查 proxy_no_cache 指令,若变量值为真(非空且不为"0"),则不使用缓存。
2.检查 proxy_cache_valid 指令,根据响应状态码设置缓存有效期,若响应未匹配此指令规则,则不更新缓存,直接转发响应。
3.判断响应码,若响应码为 200(成功)或 206(部分内容),则更新缓存条目的 ETag 和 Last-Modified 字段,以便后续条件请求使用。
4.处理影响缓存的响应头部,依次检查可能阻止缓存的头部,如 Set-Cookie 头部、Vary 头部当未被 proxy_ignore_headers 将导致不缓存,可通过 proxy_ignore_headers Set-Cookie; 忽略 Set-Cookie 头。
5.开始读取并转发上游响应,此过程为多次循环操作,因响应体可能较长,需分块处理。
6.当全部响应读取完毕后,将临时文件重命名为正式缓存文件,并移入缓存目录,此操作本质上是 rename 系统调用。若临时目录与缓存目录位于不同磁盘设备上,rename 操作会退化为"拷贝 + 删除",性能开销大;因此建议将临时目录(proxy_temp_path)与缓存目录(proxy_cache_path)置于同一设备。
7.更新共享内存状态,更新共享内存中维护的缓存索引树(如红黑树),记录新缓存项的键(key)与其对应文件路径的关系,以实现快速查找。
weiyigeek.top-接收上游响应的缓存处理流程图
简单来说,Nginx 缓存更新流程大致包括以下几个步骤:
1\] 检测缓存失效:Nginx通过后端服务器的数据更新机制检测到缓存失效,触发缓存更新操作。
\[2\] 获取新资源:Nginx从后端服务器获取新的动态资源。
\[3\] 存储新资源:Nginx将新获取的资源存储到本地缓存中。
\[4\] 通知客户端:Nginx将更新后的资源通知给前端,前端根据新的资源进行相应的处理。
好了,作者分别介绍了 Nginx 对于客户端请求的处理流程以及接收上游响应的缓存处理流程,它们共同构成了 Nginx 缓存系统的完整工作机制。接下来,我们将通过学习实践来加深对上述指令的理解与应用。
*** ** * ** ***
**指令参数**
定义存放缓存的载体(共享内存)以及启用缓存
* proxy_cache_path:设置缓存的路径和其他参数,缓存文件将以 MD5 值命名。
```go
Syntax: proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [min_free=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
Default: ---
Context: http
# 参数说明:
path: 缓存文件的存储路径
levels: 指定缓存目录的层级,默认为1:2
use_temp_path: 指定是否使用临时路径存储缓存文件,默认为 on 表示使用临时路径,off 表示使用 path 参数定义路径存放临时文件
keys_zone: 设置共享内存的名称和大小,用于存储缓存元信息。例如:keys_zone=mycache:10m 表示创建一个名为 mycache 的共享内存区域,大小为 10MB,注意1MB可存储约8000个缓存项
inactive: 设置非活跃时间(单位为秒),超过此时间的缓存将被自动清理。例如:inactive=60s 表示设置非活跃时间为 60 秒
max_size: 设置最大磁盘空间使用量(单位为字节),超过后由 cache_manager 进程按LRU淘汰。例如:max_size=1g 表示设置最大磁盘空间使用量为 1GB。
manager_files:单次淘汰循环最多处理文件数,默认100
manager_sleep:每次淘汰后休眠时间,默认200毫秒
manager_threshold:单次淘汰最大耗时,默认50毫秒
# 启动一分钟后,特殊的"cache loader"进程被激活,从磁盘中缓存文件加载到共享内存,以下参数可控制资源消耗
loader_files:单次加载循环最多处理文件数,默认100
loader_sleep:每次加载后休眠时间,默认200毫秒
loader_threshold:单次加载最大耗时,默认50毫秒
purger=on|off: 控制是否启用缓存清理进程,默认 off
purger_files=number: 单次清理循环最多处理文件数,默认10
purger_threshold=number: 设置一次迭代的持续时间上限,默认50毫秒
purger_sleep=number: 每次清理后休眠时间,默认50毫秒
# 示例:
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=one:10m;
# 格式:/data/nginx/cache/c/29/b7f54b2df7773722d382f4809d65029c
```
* `proxy_cache`: 指定 proxy_cache_path 指令创建用于缓存的共享内存的`区域名称(zone)`,同一`区域名称`可以在多个地方使用。
```go
Syntax: proxy_cache zone | off;
Default: proxy_cache off;
Context:http, server, location
```
定义缓存策略,控制缓存行为
* `proxy_cache_key`: 设置缓存的关键字,可使用任意 Nginx 变量组合。
```go
Syntax: proxy_cache_key string;
Default: proxy_cache_key $scheme$proxy_host$request_uri;
Context: http, server, location
# 示例,其中 $scheme、$proxy_host、$uri、$is_args 和 $args 是 Nginx 变量,分别表示请求的协议方案(如 http 或 https)、代理的主机名、请求的 URI、是否带有查询字符串以及查询字符串本身。
proxy_cache_key $scheme$proxy_host$uri$is_args$args;
# 示例,使用 $host 和 $request_uri 作为缓存键值,并附加了 cookie 中的 user 值。
proxy_cache_key "$host$request_uri $cookie_user";
```
* `proxy_cache_methods`: 允许缓存的请求方法,默认为 GET 和 HEAD。
```go
Syntax: proxy_cache_methods GET | HEAD | POST ...;
Default: proxy_cache_methods GET HEAD;
Context:http, server, location
```
* `proxy_cache_convert_head`: 允许将 HEAD 请求转换为 GET 请求进行缓存,若为 off 则 HEAD 请求不会命中 GET 的缓存。
```go
Syntax: proxy_cache_convert_head on | off;
Default: proxy_cache_convert_head on;
Context: http, server, location
```
* `proxy_cache_valid`: 设置不同响应状态码的缓存时间,响应码是可选的。
```go
Syntax: proxy_cache_valid [code ...] time;
Default: ---
Context: http, server, location
# 示例演示
# 表示仅对200、301、302响应码缓存5分钟
proxy_cache_valid 5m;
# 表示仅对200和302响应码缓存10分钟
proxy_cache_valid 200 302 10m;
```
特别注意:前面在处理上游响应头部中介绍到 `X-Accel-Expires: ` 头部字段会影响 Nginx 缓存行为,若为 0 则禁用缓存,若为`正值`或`@时刻`则会覆盖 `expires` 指令设置的缓存时间。另外若响应头中含有 `Set-Cookie`(表示服务器要求浏览器设置 Cookie)、`Vary: *`(决定后续请求是否可使用缓存或需向源服务器重新请求) 字段,则不缓存,可通过 `proxy_ignore_headers` 指令忽略该头部以允许缓存。
* `proxy_cache_min_uses`: 设置缓存项在达到此使用次数后才被认为是有效的,否则将被视为陈旧并被忽略。这有助于防止由于偶然的请求而导致缓存过早失效。例如,如果一个资源只在第一次被访问时才真正需要更新,则可以将其设置为一个较高的值(如10),以确保只有热点资源才被缓存。
```go
Syntax: proxy_cache_min_uses number;
Default: proxy_cache_min_uses 1;
Context: http, server, location
```
* `proxy_no_cache`: 设置不缓存的请求条件,可以使用任意 Nginx 变量,当任一变量非空(true)时,不缓存该响应,若要启用所有缓存,可以使用 `proxy_no_cache off;` 或省略此指令,在实践使用时可根据请求中的特定 `header` 或 `cookie` 值决定是否跳过缓存。
```go
Syntax: proxy_no_cache string ...;
Default: ---
Context: http, server, location
# 示例
proxy_no_cache $cookie_nocache $arg_nocache$arg_comment;
proxy_no_cache $http_pragma $http_authorization;
```
* `proxy_cache_max_range_offset`: 设置缓存范围请求的最大偏移量(以字节为单位),超出此值将不使用缓存。
```go
Syntax: proxy_cache_max_range_offset number;
Default: ---
Context: http, server, location
```
* `proxy_cache_bypass`: 设置不使用缓存的请求条件,可以使用任意 Nginx 变量,当请求满足其中一个条件时,不使用缓存,简单来说即使存在有效缓存也向上游请求。
```go
Syntax: proxy_cache_bypass string ...;
Default: ---
Context: http, server, location
# 示例
proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;
proxy_cache_bypass $http_pragma $http_authorization;
```
另外,在 Nginx 反向代理上游服务器 upstream 模块中提供了一个变量 `$upstream_cache_status`,用以记录缓存命中状态,可在日志或响应头中输出,其值有以下几种情况, 其中前三种情况大家可能在使用了 CDN 的站点中经常见到。
* HIT:命中缓存
* MISS: 未命中缓存
* BYPASS: 未使用缓存,直接向上游请求,因 proxy_cache_bypass 生效而未使用缓存
* EXPIRED: 缓存已过期(根据上游头判断),但仍在使用
* STALE: 命中陈旧缓存(因启用陈旧缓存功能)
* UPDATING: 缓存过期且正在后台更新,当前返回旧内容。
* REVALIDATED: 经异步验证后确认陈旧内容已更新
**实例演示**
**例1.使用 Nginx 反向代理缓存上游服务器响应,探究上游服务器响应头对缓存的影响。**
步骤 01.在 Nginx 服务器上配置两个监听 8010 和 8011端口用于模拟上游服务器,并使用 upstream 模块定义上游服务器组。
```go
tee backend_server.conf <<'EOF'
# 创建上游服务器
upstream backend_server {
# 定义共享内存区,用于在工作进程间同步负载信息,可根据后端服务器数量调整。
zone backend_zone 64k;
# 指定多个上游服务组,缺省使用轮询负载均衡算法
server 10.20.172.214:8010;
server 10.20.172.214:8011;
# 设置与上游服务器的长连接,最多保持10个空闲的保活连接。
keepalive 10;
keepalive_timeout 60s; # 设置与上游服务器的长连接,空闲连接的超时时间。
}
# 模拟上游服务器其监听端口为8010
server {
listen 8010;
# 任意主机名
server_name _;
# 设置字符集为utf-8
charset utf-8;
# 设置默认响应类型为文本
default_type text/plain;
# 根目录
location / {
root /usr/local/nginx/html;
index index.html;
}
# 模拟api接口响应
location ^~ /api {
return 200 '$time_iso8601 $request_id $server_addr:$server_port $request_uri\n';
}
}
# 模拟上游服务器其监听端口为8011
server {
listen 8011;
server_name _;
charset utf-8;
default_type text/plain;
location / {
root /usr/local/nginx/html;
index index.html;
}
location ^~ /api {
return 200 '$time_iso8601 $request_id $server_addr:$server_port $request_uri\n';
}
}
EOF
```
步骤 02.在 Nginx 服务器上配置缓存路径以及反向代理,并启用缓存策略。
```go
tee test.conf <<'EOF'
# 定义缓存路径,为 data01 缓存区域分配10MB内存空间,加载缓存索引文件,并设置最大内存使用限制为128MB,删除一分钟内不活跃的缓存条目。
proxy_cache_path /usr/local/nginx/cache levels=2:2 keys_zone=data01:10m loader_threshold=300 loader_files=200 max_size=128m inactive=1m;
# 虚拟主机配置,反向代理到上游服务器组
server {
listen 80;
server_name test.weiyigeek.top;
charset utf-8;
default_type text/html;
# 启用 HTTP/2 支持
http2 on;
# 日志文件
access_log /var/log/nginx/test.log main;
error_log /var/log/nginx/test.err.log debug;
location / {
proxy_pass http://backend_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 启用缓存,指定缓存区域为 data01
proxy_cache data01;
# 响应码为200的响应数据进行缓存有效期为1分钟
proxy_cache_valid 200 1m;
# 自定义响应头,用于验证上游缓存状态
add_header X-Cache-Status $upstream_cache_status;
}
}
EOF
```
步骤 03.重启 Nginx 服务,使用 curl 命令测试缓存效果,由下图可见,第一次请求未命中缓存,第二次请求则直接从缓存中获取响应,在一分钟后再次请求,缓存过期,此时会将请求到上游,并再次更新缓存,如此循环🔁往复。
```go
# 重启 Nginx 服务
nginx -s reload
# 测试缓存
curl http://test.weiyigeek.top/api -i # 多次执行,观察缓存命中情况
curl http://test.weiyigeek.top/api -i # 等待一分钟后再次执行,观察缓存命中情况
# 查看缓存目录
$ tree ./cache
./cache
└── 28
└── 0b
└── 19b5b295ffa4546f60dc8533a74d0b28
3 directories, 1 file
$ more /usr/local/nginx/cache/28/0b/19b5b295ffa4546f60dc8533a74d0b28
```
 weiyigeek.top-验证上游服务器200响应缓存图
温馨提示: 在测试过程中,你将会发现在缓存有效期内,客户端发送的请求并不会被转发到上游服务器中,而是使用缓存数据来响应客户端的请求,这种机制可以减少对上游服务器的访问,另外,在缓存时间内若后端异常对于客户端已缓存的GET请求依然会命中缓存,直到缓存过期后才不再使用。
步骤 04.在 Nginx 上游服务器配置块中,分别加入下述响应头`X-Accel-Expires`、`Cache-Control`、`Vary`,以探究其对 Nginx 反向代理缓存的影响。
```go
$ vim backend_server.conf
....
# 以 8010 为例,在实践时 8011 都需要增加修改。
location / {
root /usr/local/nginx/html;
# 首先加入.允许所有用户缓存,有效期为3s。
add_header X-Accel-Expires 3;
# 最后注释上两条加入此条,不允许缓存
add_header Vary *;
index index.html;
}
location ^~ /api {
# 其次注释上一条加入此条,允许所有用户缓存,有效期为3s,允许在缓存过期后继续使用过时的数据3s。
add_header Cache-Control "public, max-age=3, stale-while-revalidate=3";
return 200 '$time_iso8601 $request_id $server_addr:$server_port $request_uri\n';
}
....
```
步骤 05.重启 Nginx 服务,分布使用 curl 命令测试对应的上游响应头对缓存影响效果。
* `X-Accel-Expires`: 允许所有用户缓存,有效期为3s,将覆盖 Nginx 反向代理中设置的缓存时间,执行 `curl http://test.weiyigeek.top/ads.txt -i` 命令结果如下。
 weiyigeek.top-验证X-Accel-Expires响应头对缓存影响图
* `add_header Cache-Control "public, max-age=3, stale-while-revalidate=3";` 允许所有用户缓存,有效期为3s,允许在缓存过期后3s内继续使用过时的数据,也将覆盖 Nginx 反向代理中设置的缓存时间,执行 `curl http://test.weiyigeek.top/api -i` 命令结果如下。
 weiyigeek.top-验证Cache-Control响应头对缓存影响图
* `Vary *;`: 此响应头每次请求都会重新验证资源是否更新,这将导致 Nginx 反向代理无法使用缓存,执行 `curl http://test.weiyigeek.top -I` 命令结果如下:
```go
HTTP/1.1 200 OK
Server: nginx/1.29.0
Date: Thu, 25 Dec 2025 14:12:10 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 14431
Connection: keep-alive
Last-Modified: Wed, 24 Dec 2025 08:45:13 GMT
ETag: "694ba819-385f"
Vary: *
Accept-Ranges: bytes
X-Cache-Status: MISS
```
 weiyigeek.top-验证Vary响应头对缓存影响图
至此,完成了 Nginx 反向代理缓存机制的探究,并了解了上游服务器特定响应头对缓存的影响,相信各位读者已经掌握了 Nginx 反向代理缓存机制的精髓了吧。
最后,在配置完 Nginx 动态资源缓存的缓存更新机制后,还需要进行监控和调优。通过监控系统的性能指标,及时发现和解决缓存更新过程中的问题,提高系统的性能和稳定性。同时,还需要根据实际需求对缓存策略进行调优,以达到最佳的性能效果。

**END**
#### 加入:作者【全栈工程师修炼指南】知识星球
『 全栈工程师修炼指南』星球,主要涉及全栈工程师(Full Stack Development)实践文章,持续更新包括但不限于企业SecDevOps和网络安全等保合规、安全渗透测试、编程开发、云原生(Cloud Native)、物联网工业控制(IOT)、人工智能Ai,从业书籍笔记,人生职场认识等方面资料或文章。
Q: 加入作者【全栈工程师修炼指南】星球后有啥好处?
✅ 将获得作者最新工作学习实践文章以及网盘资源。
✅ 将获得作者珍藏多年的全栈学习笔记(需连续两年及以上老星球友,也可单次购买)
✅ 将获得作者专门答疑学习交流群,解决在工作学习中的问题。
✅ 将获得作者远程支持(在作者能力范围内且合规)。

#### 获取:作者工作学习全栈笔记
作者整理了10年的工作学习笔记(涉及网络、安全、运维、开发),需要学习实践笔记的看友,可添加作者微信或者回复【工作学习实践笔记】,当前价格¥299,除了获得从业笔记的同时还可进行问题答疑以及每月远程技术支持,希望大家多多支持,收获定大于付出!

****知识推荐** **往期文章****
* 🔥【最新】[Nginx \| 核心知识150讲,百万并发下性能优化之前端静态缓存、后端动态缓存实践笔记](https://mp.weixin.qq.com/s?__biz=MzIwNDA3ODg3OQ==&mid=2648016760&idx=1&sn=3ef14ad226ae29627beeb77a85e878a7&scene=21#wechat_redirect)
* 🔥【最新】[Nginx \| 核心知识150讲,百万并发下性能优化之SSL证书签发与HTTPS加密传输实践笔记](https://mp.weixin.qq.com/s?__biz=MzIwNDA3ODg3OQ==&mid=2648016714&idx=1&sn=b722538ff15e838a10268641299c5fe9&scene=21#wechat_redirect)
* 🔥【最新】[Nginx \| 核心知识150讲,百万并发下性能优化之HTTP协议中反向代理实践笔记](https://mp.weixin.qq.com/s?__biz=MzIwNDA3ODg3OQ==&mid=2648016626&idx=1&sn=b1f050f8678bb6c6dcf53488ba126371&scene=21#wechat_redirect)
* 💡【相关】[告别Nginx原始日志!VictoriaLogs+Grafana构建企业级应用日志分析平台实战](https://mp.weixin.qq.com/s?__biz=MzIwNDA3ODg3OQ==&mid=2648015277&idx=1&sn=2f252314a313ef8de56b053b3adca790&scene=21#wechat_redirect)
* 💡【相关】[企业运维实践-Nginx使用geoip2模块并利用MaxMind的GeoIP2数据库实现处理不同国家或城市的访问最佳实践指南](https://mp.weixin.qq.com/s?__biz=MzIwNDA3ODg3OQ==&mid=2648000515&idx=1&sn=59bfba0d013be4945384dc3c622c81e5&scene=21#wechat_redirect)
****若文章对你有帮助,请将它转发给更多的看友,**若有疑问的小伙伴,可在评论区留言你想法哟 💬!******
