·更新于 2026年10月25日· 20 次浏览 Nuxt3Vue3RedisMongoDB缓存BUG解决

Nuxt 3 热文缓存失效被打穿:我用 Redis 单飞重建和旧值兜底止血

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

Nuxt 3 热文缓存失效被打穿:我用 Redis 单飞重建和旧值兜底止血 封面
ZHOUYI

作者

ZHOUYI

那天晚上发了一篇热文,首页推荐刚挂上,详情接口就开始超时。监控里 Nuxt 服务端请求排队,MongoDB 查询数瞬间拉高。起初我以为是文章内容太大,后来发现真正问题是缓存失效那一秒,所有请求都去查同一条数据。这类问题最麻烦的是,它不是稳定复现,而是只在热点出现时突然爆一下。

为什么会瞬间打穿

以前详情缓存只设固定 TTL。热文一旦过期,新请求拿不到缓存,就会同时穿透到 MongoDB。普通文章没事,热文并发高,几百个请求一起查同一条数据,数据库连接池被占满,接口自然超时。这类问题平时看不出来,一到发布、推送、广告位曝光就集中爆发。

我的止血方案

  1. 缓存里不只存内容,还存逻辑过期时间,过期后优先返回旧值。
  2. 用 Redis SET NX 抢重建锁,只让一个请求去查 MongoDB。
  3. 没抢到锁的请求短暂等待或直接吃旧值,避免全部打到数据库。
  4. 重建成功后更新缓存,锁设置较短超时,防止进程异常卡死。

在 Nuxt 3 里,我把这套逻辑放在服务端接口里,而不是让页面组件各自处理。useAsyncData 的 key 仍然带上文章 id,避免不同文章串缓存;页面继续做 SSR,保证 SEO 和首屏可读,但真正的数据库查询被收到一个可控的服务层入口。

ts
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 秒,编辑保存时主动删缓存。

“缓存不是只为了让读快一点,更是在热点时刻保护数据库的闸门。”


Guestbook

留言

若这篇文章也曾停在你心上,留一行字吧,我会看的。

先确认一下邮箱

多这一步,是为了少些机器骚扰,也让我能更踏实地读每一条真人写下的字。验证码 10 分钟内有效;同一邮箱 60 秒内只能发一封。

本文留言

共 0 条已加载 0 条

这里还静悄悄的——愿意做第一个留下脚印的人吗?