🌊 为什么要"限流"?
想象一个场景:你辛辛苦苦搭建的 Next.js API 服务,正沐浴在阳光下稳定运行,忽然来了一个过分勤劳的客户端用户(或机器人 🤖),开始毫无节制地请求接口,疯狂到让 CPU 开始思考人生。
结果自然是:
💥「服务崩溃 → 用户暴躁 → 老板追杀」
于是我们需要"限流机制"(Rate Limiting)。
限流的本质,就是在 单位时间内限制某个用户或 IP 的请求次数,从而保持服务稳定。例如:
| 指标 | 意义 |
|---|---|
| 10 req / 10 s | 每 10 秒最多处理 10 个请求 |
| 100 req / min | 每分钟处理 100 个请求 |
| 429 状态码 | 拒绝请求并提示"停一下!" |
🧩 Upstash 与 Redis ------ 天作之合
我们不想自己去维护复杂的 Redis 集群来追踪请求计数,于是 Upstash 这个小精灵出现了:
- ☁️ 无服务器 Redis(Serverless Redis)
- 💸 按调用计费,不用担心多一分钱的浪费
- ⚡️ 延迟低,速度快
- 🔐 免费额度够个人项目用
而 @upstash/ratelimit 就是它的魔法棒,让限流在 Next.js 中轻松实现。
🚀 开始动手:Next.js + @upstash/ratelimit
下面我们构造一个限流且带审计日志的 API。
1️⃣ 安装依赖
bash
npm install @upstash/ratelimit @upstash/redis
2️⃣ 创建 Redis 客户端
在 /lib/redis.js:
arduino
import { Redis } from "@upstash/redis";
export const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
});
🔑 安全提示: 环境变量千万别放到 GitHub 上,除非你想给别人白送额度。
3️⃣ 初始化 RateLimiter
在 /lib/ratelimit.js:
javascript
import { Ratelimit } from "@upstash/ratelimit";
import { redis } from "./redis";
export const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(5, "10 s"), // 每10秒最多允许5次请求
analytics: true, // 启用分析数据
prefix: "ratelimit:api",
});
🔍 审计日志:记录谁在"频繁敲门"
当请求超限时,我们顺带写入日志。
日志可以简单保存在 Redis,也可以后续接入到像 Datadog、OpenTelemetry 等监控系统。
在 /pages/api/hello.js:
javascript
import { ratelimit } from "../../lib/ratelimit";
import { redis } from "../../lib/redis";
export default async function handler(req, res) {
const ip = req.headers["x-real-ip"] || req.socket.remoteAddress || "unknown";
const { success, limit, reset, remaining } = await ratelimit.limit(ip);
if (!success) {
await redis.rpush("audit:rate_limit_log", {
ip,
time: new Date().toISOString(),
path: req.url,
});
return res.status(429).json({
message: "⛔ 太快啦,稍等一下再试!",
limit,
reset,
remaining,
});
}
res.status(200).json({ message: `✅ 欢迎,来自 ${ip} 的合格请求!` });
}
📈 小试牛刀:查看日志
只要登录 Redis 查看我们写入的日志:
ini
const logs = await redis.lrange("audit:rate_limit_log", 0, -1);
console.log("🚨 限流日志记录:", logs);
输出示例:
lua
[
{ ip: '203.0.113.1', time: '2025-10-31T06:00:00Z', path: '/api/hello' },
{ ip: '203.0.113.1', time: '2025-10-31T06:00:02Z', path: '/api/hello' },
...
]
📜 小贴士:你也可以用 Cloudflare KV、Supabase 或 PostgreSQL 来保存日志,用于更强大的审计分析。
🧠 一点底层思考:Rate Limit 的本质
限流算法背后其实就像"水桶原理"一样。常见的策略有:
- 🪣 固定窗口(Fixed Window) :简单粗暴,每个时间窗限固定次数。
- ⏳ 滑动窗口(Sliding Window) :更平滑,统计窗口"滑动"过去一段时间请求。
- 💧 令牌桶(Token Bucket) :每次请求取一个"令牌",定期补充。
- 💦 漏桶(Leaky Bucket) :请求流入"桶",系统按固定速率漏出处理。
@upstash/ratelimit 默认提供了"滑动窗口",兼顾性能与公平性。
🖼️ 可视化(脑内小剧场)
下面是一幅简单的比喻图:
arduino
┌──────────┐ 请求 x5 次/10秒
│ Client 🌍│ ─────┐
└──────────┘ │
▼
┌──────────┐
│ Ratelimit │ ------ "5次够了!去喝杯茶☕"
└──────────┘
▼
┌──────────┐
│ Redis 🧠 │ ← 记录日志 & 限流计数
└──────────┘
🧾 总结
| 项目 | 内容 |
|---|---|
| 功能 | 限制接口调用速率、防止滥用 |
| 库 | @upstash/ratelimit, @upstash/redis |
| 审计 | Redis 保存请求日志 |
| 应用场景 | API 接口、Webhooks、防刷票系统 |
| 优点 | 简洁、轻量、Serverless 友好 |
🎁 拓展玩法(进阶食谱)
- 多维度限流:不仅按 IP,还可以按用户 ID、API key、Referer 等。
- 图表可视化审计:用 Next.js/Chart.js 展示限流日志的趋势。
- 智能警告系统:超过阈值时触发 Slack/Email 通知管理员。
- 动态限流:高峰期更严,闲时更松。