Nuxt 3 接口被刷后,我用 Redis 令牌桶给搜索和评论加限流
一次异常流量和慢查询报警,让我意识到个人站不能裸奔。本文记录用 Redis 令牌桶按 IP/用户维度限流、降级与兜底。

作者
ZHOUYI
前几天凌晨,个人站的慢查询报警突然响了。起初我以为是 MongoDB 索引没建好,翻日志才发现搜索接口在短时间内被同一个 IP 段高频调用,评论接口也有疑似脚本批量提交。个人站流量不大,但一旦被刷,数据库和邮件通知都会被拖垮。
先止血:区分接口优先级
我没有一上来就封 IP,因为误伤正常用户很麻烦。先把接口分级:搜索是读接口,允许缓存和降级;评论是写接口,必须限制频率并保留审计。限流 key 采用接口 + 用户维度:登录用户优先取 userId,未登录再取 IP,最后兜底到请求指纹。
用 Redis 做令牌桶
方案选了 Redis 令牌桶。固定窗口实现简单,但容易在临界点突发;漏桶太平滑,评论这种低频操作体验不好。令牌桶可以限制平均速率,又允许桶里攒一点令牌,适合搜索联想、提交评论这类短突发场景。我在 Nuxt 3 的 server 接口里调用,核心是把剩余令牌和上次更新时间放进 Redis。
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,再叠加短期黑名单,但不把黑名单当主防线。
- Redis 不可用时,退回进程内滑动计数,只保护评论和登录
- 搜索被限流时优先返回热门词缓存,避免继续查 MongoDB
- 评论触发风险时进入人工审核或验证码,不直接丢弃
“限流的目标不是拒绝请求,而是把有限资源留给真实用户。”
小结
这次改造后,慢查询报警基本消失,评论垃圾也少了很多。踩坑点主要有三个:Redis 时间要用服务器时间或统一时钟,避免多实例漂移;key 要设置过期,防止冷数据堆积;阈值要根据日志逐步调,不要照抄。具体命令和配置以官方文档为准。