nginx缓存与负载均衡深度协同:请求先经upstream选定后端,再由proxy_cache判断缓存命中与否;缓存键默认与后端无关,确保共享;命中时直接响应,大幅降低后端负载。

Nginx 的缓存层与负载均衡模块不是各自独立运行的,而是通过请求流转路径深度协同:客户端请求先经由 upstream 负载均衡逻辑 决定转发目标,再在代理过程中由 proxy_cache 相关指令 判断是否可缓存、是否命中、是否需回源——二者分工明确、顺序固定,共同提升系统吞吐与响应稳定性。
缓存发生在负载均衡之后,而非之前
这是关键前提。Nginx 的请求处理流程是线性的:接收到请求 → 匹配 location → 执行 proxy_pass → 触发 upstream 选择后端服务器 → 在向该后端发起代理请求前/后,根据 cache 配置决定是否读取或填充缓存。
- 缓存键(cache key)默认基于
$scheme$proxy_host$request_uri生成,与后端具体哪台机器无关,因此同一请求无论被轮询到哪台 server,只要 URI 和协议一致,就共享同一个缓存实体 - 如果在 upstream 中使用了
ip_hash或hash $cookie_session_id等会话保持策略,缓存仍有效——因为缓存动作发生在“选定后端之后、发出请求之前”,不干扰调度逻辑 - 注意:若后端返回了
Cache-Control: private、Set-Cookie或状态码为 200 以外(如 302、500),默认不会缓存;可通过proxy_ignore_headers或proxy_cache_valid显式覆盖
缓存可显著降低后端负载,放大负载均衡效果
当缓存命中时,Nginx 直接返回本地副本,完全绕过后端集群。这意味着:原本需分发至 3 台 Tomcat 的请求,可能 70% 由 Nginx 缓存直接响应,仅 30% 真正打到 upstream 里——相当于在负载均衡器前端叠加了一层“静态分流阀”。
- 对静态资源(JS/CSS/图片)、API 查询结果(如商品详情页 HTML)、GraphQL 响应等低频变更内容,启用缓存后,后端服务器 CPU 和数据库连接压力明显下降
- 配合
proxy_cache_lock on,可防止缓存失效瞬间大量并发回源(thundering herd),让首个未命中请求去回源,其余等待并复用其结果 - 建议将
proxy_cache_valid 200 302 10m与proxy_cache_use_stale error timeout updating结合,提升异常期间的可用性
健康检查与缓存失效需联动设计
上游服务器宕机时,Nginx 会自动剔除故障节点,但缓存内容不会自动失效。若某台后端曾返回过旧数据并被缓存,而它下线后其他正常后端返回新数据,缓存仍可能继续提供旧版本——这属于语义一致性风险。
- 推荐做法:对强时效性接口(如用户订单状态),禁用缓存或设置极短有效期(
proxy_cache_valid 200 10s) - 利用
proxy_cache_bypass $arg_nocache,支持带?nocache=1参数强制跳过缓存,便于人工验证或灰度发布 - 若使用外部缓存失效机制(如 Redis 发布失效消息),可通过 Nginx 的 Lua 模块(ngx_http_lua_module)或 OpenResty 实现动态 purge,但原生 Nginx 不支持按 key 主动清理
配置上需显式关联缓存区与 upstream
缓存路径(proxy_cache_path)和缓存区名称(proxy_cache)虽不直接引用 upstream 名称,但实际生效依赖于它们在同一 context(如 server 或 location)中被同时调用。
- 错误写法:在 http 块定义了
proxy_cache my_cache,但在 location 里只写了proxy_pass http://backend,却没加proxy_cache my_cache—— 缓存不会启用 - 正确结构示例:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g; upstream backend { server 192.168.1.10:8080; server 192.168.1.11:8080; } server { location /api/ { proxy_pass http://backend; proxy_cache my_cache; proxy_cache_valid 200 5m; } } - 特别注意:
proxy_cache_key若包含后端变量(如$upstream_addr),会导致同一请求因打到不同 server 而生成多个缓存副本,浪费空间且失去共享意义,一般应避免











