·更新于 2026年10月25日· 11 次浏览 Nuxt 3Vue 3Node.jsRedis限流工程实践

Nuxt 3 接口被刷后,我用 Redis 令牌桶给搜索和评论加限流

一次异常流量和慢查询报警,让我意识到个人站不能裸奔。本文记录用 Redis 令牌桶按 IP/用户维度限流、降级与兜底。

Nuxt 3 接口被刷后,我用 Redis 令牌桶给搜索和评论加限流 封面
ZHOUYI

作者

ZHOUYI

前几天凌晨,个人站的慢查询报警突然响了。起初我以为是 MongoDB 索引没建好,翻日志才发现搜索接口在短时间内被同一个 IP 段高频调用,评论接口也有疑似脚本批量提交。个人站流量不大,但一旦被刷,数据库和邮件通知都会被拖垮。

先止血:区分接口优先级

我没有一上来就封 IP,因为误伤正常用户很麻烦。先把接口分级:搜索是读接口,允许缓存和降级;评论是写接口,必须限制频率并保留审计。限流 key 采用接口 + 用户维度:登录用户优先取 userId,未登录再取 IP,最后兜底到请求指纹。

用 Redis 做令牌桶

方案选了 Redis 令牌桶。固定窗口实现简单,但容易在临界点突发;漏桶太平滑,评论这种低频操作体验不好。令牌桶可以限制平均速率,又允许桶里攒一点令牌,适合搜索联想、提交评论这类短突发场景。我在 Nuxt 3 的 server 接口里调用,核心是把剩余令牌和上次更新时间放进 Redis。

typescript
async function allow(key: string, rate: number, capacity: number) {
  const now = Date.now();
  const redisKey = `ratelimit:${key}`;
  const [tokens, ts] = await redis.hmget(redisKey, 'tokens', 'ts');
  let t = Number(tokens ?? capacity);
  const last = Number(ts ?? now);
  t = Math.min(capacity, t + ((now - last) / 1000) * rate);
  if (t < 1) return false;
  await redis.hset(redisKey, { tokens: t - 1, ts: now });
  await redis.expire(redisKey, 60);
  return true;
}

降级、兜底和阈值

上线时我按接口设置不同阈值:搜索按 IP 每秒补 2 个令牌,桶容量 8;评论按用户每分钟补 3 个,桶容量 5。超过限制返回 429,并告诉前端剩余重试时间。对于明显异常的 UA 和高错误率 IP,再叠加短期黑名单,但不把黑名单当主防线。

  1. Redis 不可用时,退回进程内滑动计数,只保护评论和登录
  2. 搜索被限流时优先返回热门词缓存,避免继续查 MongoDB
  3. 评论触发风险时进入人工审核或验证码,不直接丢弃

“限流的目标不是拒绝请求,而是把有限资源留给真实用户。”

小结

这次改造后,慢查询报警基本消失,评论垃圾也少了很多。踩坑点主要有三个:Redis 时间要用服务器时间或统一时钟,避免多实例漂移;key 要设置过期,防止冷数据堆积;阈值要根据日志逐步调,不要照抄。具体命令和配置以官方文档为准。

Guestbook

留言

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

先确认一下邮箱

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

本文留言

共 0 条已加载 0 条

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