当 Nginx 作为反向代理缓存节点时,每一次客户端请求都会经历缓存查找、命中返回或回源取新的过程。要想知道缓存策略是否真正生效,最直接的手段是把缓存状态写入访问日志。Nginx 的 $upstream_cache_status 变量记录了当前请求与上游缓存交互的结果,它可能的值包括 MISS、HIT、EXPIRED、BYPASS、UPDATING、STALE 等。这些状态能帮助排查命中率下降、回源增多的问题。

一、在日志中识别缓存命中与回源状态
默认的 combined 日志格式不会输出缓存状态,因此需要在 http 块中定义一个包含缓存变量的日志格式。下面这段配置将客户端地址、请求时间、请求方法、URI、上游缓存状态、响应大小和 User-Agent 写入日志。其中 $upstream_cache_status 是关键字段,如果该值显示为 MISS 或 EXPIRED,说明请求发生了回源;如果显示为 HIT,则直接命中缓存。
log_format cache_log '$remote_addr - [$time_local] "$request" '
'cache_status=$upstream_cache_status '
'body_bytes_sent=$body_bytes_sent '
'"$http_user_agent"';
access_log /var/log/nginx/cache_access.log cache_log;
有了这样的日志格式,就可以借助 awk 或日志分析工具统计某一时间段内 HIT、MISS 的比例。例如当 MISS 持续升高,往往意味着缓存键设置不合理、缓存有效期过短或请求携带了导致绕过的头信息。此时需要结合缓存控制指令进一步调整。
还需要关注 BYPASS 状态。它表示该请求因为满足某些条件跳过了缓存直接回源,例如客户端带有 Pragma: no-cache 请求头,或者被 proxy_cache_bypass 判断为需要回源。日志中的这个状态可以迅速定位哪些请求在主动绕过缓存,从而决定是否需要收紧绕过条件。
二、核心缓存控制指令如何影响回源行为
Nginx 控制回源缓存的核心指令集中在 proxy_cache_path、proxy_cache、proxy_cache_valid、proxy_cache_key 等。其中 proxy_cache_valid 用来按响应码设置缓存有效期,例如对 200 响应缓存 10 分钟,对 404 响应缓存 1 分钟。如果没有设置合适的有效期,缓存可能很快过期,导致大量请求重新回源。
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m;
server {
location / {
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_pass http://backend;
}
}
另一个影响回源频率的指令是 proxy_cache_key。默认情况下 Nginx 使用协议、主机、URI 和参数组合作为缓存键,但如果源站返回的内容会因为某个请求头而变化,就需要把该头信息纳入缓存键。比如需要区分不同客户端的 Accept-Language 时,可以配置 proxy_cache_key "$scheme$request_method$host$request_uri$http_accept_language"。缓存键越精确,回源次数越少,但也会占用更多缓存空间,需要在命中和容量之间权衡。
同时 proxy_cache_min_uses 可以设置一个资源在多少次请求后才被缓存,proxy_cache_methods 控制哪些 HTTP 方法允许缓存。默认只有 GET 和 HEAD 会被缓存,POST 等不会被缓存。当 API 网关面对大量幂等 POST 请求时,可以按需调整这些指令,但要谨慎避免缓存非幂等请求。
三、用缓存绕过与过期策略降低无效回源
回源并非总是坏事,有些场景必须主动绕过缓存,例如用户已登录的个性化页面、带有特定 Cookie 的会话请求。Nginx 提供 proxy_cache_bypass 和 proxy_no_cache 两条指令,前者决定是否绕过缓存直接回源,后者决定回源得到的响应是否不写入缓存。两者经常组合使用,比如当请求包含 Cookie: nocache=1 时,既不读缓存也不写缓存。
location /private/ {
proxy_cache my_cache;
proxy_cache_bypass $cookie_nocache $http_pragma;
proxy_no_cache $cookie_nocache $http_pragma;
proxy_pass http://backend;
}
这类配置可以避免个性化内容污染公共缓存,同时也能防止某些请求头触发不必要的缓存更新。但要留意条件变量是否覆盖过广,例如把整个 $http_authorization 都作为绕过条件,会导致所有携带认证头的请求全部回源,缓存形同虚设。建议在日志中观察 BYPASS 比例,结合业务逐步收窄绕过条件。
当源站出现故障或响应超时,Nginx 并不一定要立刻返回错误。通过 proxy_cache_use_stale 可以在 error、timeout、updating 等状态下继续提供旧的缓存内容。这个指令对降低故障期间的回源压力非常有效。例如配置 proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504; 后,即使源站返回 502,Nginx 也会尝试用过期缓存响应,避免请求打到已经过载的源站。
此外,proxy_cache_lock 用于防止缓存击穿。当某个热门资源同时过期,多个请求同时回源会造成源站瞬时压力。启用 proxy_cache_lock on; 后,Nginx 只允许一个请求回源,其余请求等待该请求完成并填充缓存,从而大幅减少并发回源数量。结合 proxy_cache_lock_age 可以设置锁的最长等待时间,防止请求长时间挂起。
从日志角度看,合理使用这些指令后,$upstream_cache_status 中的 UPDATING 和 STALE 会相应出现,这两个状态可以帮助确认旧缓存兜底和缓存锁是否按预期工作。持续监控这些字段,再配合调整 proxy_cache_valid 与绕过条件,就能形成一条从观测到优化的完整链路。