Nuxt 3 热文缓存失效被打穿:我用 Redis 单飞重建和旧值兜底止血
一次热文发布后,文章详情接口瞬时超时。真正的问题不是 MongoDB 查询慢,而是缓存同时过期,请求集中穿透。我用 Redis 互斥重建、逻辑过期和旧缓存兜底,把热点流量挡在数据库外面。

作者
ZHOUYI
那天晚上发了一篇热文,首页推荐刚挂上,详情接口就开始超时。监控里 Nuxt 服务端请求排队,MongoDB 查询数瞬间拉高。起初我以为是文章内容太大,后来发现真正问题是缓存失效那一秒,所有请求都去查同一条数据。这类问题最麻烦的是,它不是稳定复现,而是只在热点出现时突然爆一下。
为什么会瞬间打穿
以前详情缓存只设固定 TTL。热文一旦过期,新请求拿不到缓存,就会同时穿透到 MongoDB。普通文章没事,热文并发高,几百个请求一起查同一条数据,数据库连接池被占满,接口自然超时。这类问题平时看不出来,一到发布、推送、广告位曝光就集中爆发。
我的止血方案
- 缓存里不只存内容,还存逻辑过期时间,过期后优先返回旧值。
- 用 Redis SET NX 抢重建锁,只让一个请求去查 MongoDB。
- 没抢到锁的请求短暂等待或直接吃旧值,避免全部打到数据库。
- 重建成功后更新缓存,锁设置较短超时,防止进程异常卡死。
在 Nuxt 3 里,我把这套逻辑放在服务端接口里,而不是让页面组件各自处理。useAsyncData 的 key 仍然带上文章 id,避免不同文章串缓存;页面继续做 SSR,保证 SEO 和首屏可读,但真正的数据库查询被收到一个可控的服务层入口。
const cacheKey = `post:${id}`;
const lockKey = `lock:post:${id}`;
const raw = await redis.get(cacheKey);
if (raw) {
const cached = JSON.parse(raw);
if (cached.expireAt > Date.now()) return cached.value;
}
const locked = await redis.set(lockKey, token, 'PX', 3000, 'NX');
if (!locked && raw) return JSON.parse(raw).value;
try {
const value = await db.findPost(id);
await redis.set(cacheKey, JSON.stringify({ value, expireAt: Date.now() + 60000 }), 'PX', 86400000);
return value;
} finally { await releaseLock(lockKey, token); }踩坑和边界
实际跑下来,最容易踩的不是代码,而是兜底策略太理想化。旧值不能无限用,文章编辑后必须尽快失效;锁释放要带标识,避免误删别人的锁;重建失败时要保留旧缓存,而不是把错误写进去。对爬虫和分享预览,我也会给 SSR 输出加一层短 TTL,避免外部流量参与击穿。另外,逻辑过期时间不要太长,否则用户看到旧内容会困惑;我一般给详情 30 到 60 秒,编辑保存时主动删缓存。
“缓存不是只为了让读快一点,更是在热点时刻保护数据库的闸门。”