在 linux 中避免 nginx 缓存失效导致大量请求击穿后端,核心做法是启用 proxy_cache_lock 指令。该指令允许 nginx 在同一时刻只允许一个请求去回源获取资源,其余并发请求则等待该请求完成并复用结果,从而有效防止缓存穿透(cache penetration)或雪崩效应。

如何配置 proxy_cache_lock 实现互斥回源
直接回答:通过在 location 块中设置 proxy_cache_lock on 和 proxy_cache_lock_timeout 即可实现。当多个请求同时访问同一个未命中缓存的 URL 时,Nginx 会锁定该资源的回源过程,其他请求进入等待队列,超时后直接返回旧缓存或错误。

server {
listen 80;
server_name example.com;
# 定义缓存路径、名称和参数
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;
location / {
# 开启缓存
proxy_cache my_cache;
# 关键配置:开启锁机制
proxy_cache_lock on;
# 关键配置:等待超时时间,避免请求堆积过久
proxy_cache_lock_timeout 5s;
# 设置缓存键,通常包含 URI 和查询参数
proxy_cache_key $scheme$request_method$host$request_uri;
# 后端服务地址
proxy_pass http://backend_server;
# 可选:设置最小文件大小才缓存,避免小文件频繁回源
proxy_cache_min_uses 1;
# 可选:设置缓存过期时间
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
}
}
在上述配置中,proxy_cache_lock on 是核心开关。当第一个请求发现缓存缺失时,它会获取“锁”并发起后端请求。后续到达的相同 URL 请求检测到锁存在,便会进入等待状态。如果等待时间超过 proxy_cache_lock_timeout 设定的值(例如 5 秒),这些等待的请求将不再继续等待,而是根据 proxy_cache_lock_status(默认为 412 Precondition Failed)返回错误或尝试使用可能存在的旧缓存。这种机制极大地减少了后端服务器在缓存失效瞬间承受的并发压力。
结合 stale-while-revalidate 提升用户体验
直接回答:除了互斥锁,还可以结合 proxy_cache_use_stale 中的 updating 参数。这允许 Nginx 在后台更新缓存的同时,直接向客户端提供过期的旧缓存内容,既避免了回源冲击,又保证了服务的可用性。

# 在 location 块中追加以下配置 # 当后端正在更新缓存时,允许返回过期的旧缓存 proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_503 http_504; # 可选:指定在什么情况下可以返回 stale 缓存 # updating: 当有一个请求正在回源更新缓存时,其他请求可以使用旧缓存 proxy_cache_background_update on; # 可选:允许 stale 缓存保留的时间,即使已过期 # 例如,允许过期后 1 小时内仍可使用旧缓存 proxy_cache_valid 200 1h;
这一策略与 proxy_cache_lock 配合使用效果更佳。proxy_cache_lock 负责在微观层面控制并发回源的数量,而 proxy_cache_use_stale updating 则在宏观层面确保即使缓存正在刷新,用户也能立即获得响应,只是内容可能是稍旧的。这种组合拳能最大程度地保护后端服务,特别是在高并发场景下,如秒杀活动或热点数据刷新时。需要注意的是,proxy_cache_background_update 需要 Nginx 版本支持(通常为 1.17.9+ 或 OpenResty),它允许 Nginx 在返回旧缓存的同时,异步发起新的回源请求以更新缓存,进一步解耦了用户请求与后端处理时间。
如何处理 proxy_cache_lock 超时后的请求行为
直接回答:当 proxy_cache_lock_timeout 超时后,默认情况下 Nginx 会返回 412 状态码。可以通过 proxy_cache_lock_status 指令自定义返回的状态码,或者结合 proxy_cache_use_stale 让超时请求直接复用旧缓存,从而避免向用户展示错误页面。
# 自定义超时后的返回状态码,例如改为 503 Service Unavailable proxy_cache_lock_status 503; # 或者,更推荐的做法:让超时请求直接使用旧缓存 # 这样用户不会看到错误,而是看到稍微过时的数据 proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504; # 确保 stale 缓存是可用的 proxy_cache_valid any 1m; # 至少设置一个极短的有效期以确保有缓存可复用
在实际生产环境中,直接返回 412 或 503 可能会影响用户体验。因此,最佳实践是将 proxy_cache_lock_timeout 设置为一个合理的值(如 2-5 秒),并配合 proxy_cache_use_stale。这样,如果回源请求在 5 秒内未完成,等待的请求不会报错,而是直接返回之前缓存的内容。这要求你的业务逻辑能够接受短暂的数据不一致性。对于强一致性要求的场景,可能需要调整 proxy_cache_lock_timeout 为更长的时间,或者放弃部分缓存策略,但这会增加后端压力。此外,监控 Nginx 的 $upstream_cache_status 变量,特别是 STALE 和 LOCKED 状态的计数,有助于评估当前锁机制的效果和后端负载情况。











