缓存击穿是热点key过期瞬间大量请求并发回源数据库,核心解法是精准加锁(如proxy_cache_lock)、允许返回过期缓存(stale)、后台静默更新,并通过错峰过期与请求归并降低miss密度。

秒杀场景下,缓存失效引发的击穿问题不是“能不能缓存”,而是“失效时谁扛、怎么退、退多快”。核心思路是:不让所有请求在同一刻回源,也不让第一个请求失败或卡住拖垮全局。
用 proxy_cache_lock 精准串行化未命中请求
关键不在开不开 lock,而在锁得准、等得稳:
- 统一 cache key:对秒杀页如
/api/seckill/item/123,去掉 query 参数和易变 header,设为proxy_cache_key "$scheme$host$uri";,避免?t=1698765432导致每个请求都是新 key - 必须配齐三要素:定义
keys_zone缓存区、location 中启用proxy_cache、再加proxy_cache_lock on;,缺一不可 - 设置合理超时:
proxy_cache_lock_timeout 5s,太短会放弃锁直接穿透,太长用户明显卡顿
失效不等于停摆:stale + background update 是保命组合
首个请求慢、超时或失败时,其余等待请求不能干等——要能“退得快”:
- 允许返回过期缓存:
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; - 开启后台静默更新:
proxy_cache_background_update on;,让旧缓存继续服务,后台悄悄刷新 - 连 404 也缓存:
proxy_cache_valid 404 10s;,防恶意刷不存在商品 ID 导致反复穿透
从源头减少 MISS:错峰过期 + 请求归并
击穿压力大小,取决于单位时间有多少请求同时 MISS。主动降低 MISS 密度才是治本:
- 按小时扰动 key:
proxy_cache_key "$scheme$host$uri $time_iso8601_hour";,让整点失效变成自然滚动淘汰 - 路径级归并:
proxy_cache_key "$scheme$host:/api/seckill/item";,把所有商品详情请求压成一个 key,极大压缩缓存单元数 - 配合
proxy_cache_valid 200 302 1s;(开抢后)+proxy_cache_lock on;,实现“1 秒内只放行 1 个回源”
验证是否真生效:别只看配置,要看日志和响应头
上线后必须确认机制在运行:
- 在 access_log 中加入
$upstream_cache_status,观察是否出现大量MISS或EXPIRED - 响应头加
add_header X-Cache $upstream_cache_status;,用 curl -I 直接看每次请求是 HIT 还是 LOCKED - 检查缓存目录是否有文件生成:
ls /var/cache/nginx/proxy_cache/ | wc -l,结合stat看修改时间是否集中











