示例说明
单机内存限流在多实例部署下限额会被放大 N 倍,把窗口计数放进 Redis 才是全局限口径。consume 原子地扣减点数并返回剩余额度与下次重置毫秒数,可直接换算成限流响应头。insuranceLimiter 是降级预案:Redis 不可用时自动切换到本地内存限流,宁可收紧也不放行失控。
技术要点
- Redis 计数使限流口径全局一致
- consume 返回值可直接生成 Retry-After 头
- 保险限流器覆盖存储故障场景
代码解读
limiter 配置 60 秒 100 点的窗口与更保守的保险参数;guard 把 consume 结果映射成带剩余额度与重置时间的对象,被限流时 rej 不是 Error 实例而是拒绝对象,据此区分存储故障与正常限流。burst 演示并发扣减的瞬时判定。
使用建议
按用户或按 API key 选键,避免共享 IP 出口误伤。突发型接口可用突发桶算法的 blockStrategy 配置。限流响应务必带上标准限流头,让合规客户端自适应退避。
代码示例
import { RateLimiterRedis } from 'rate-limiter-flexible';
import { createClient } from 'redis';
const redis = createClient({ url: 'redis://127.0.0.1:6379' });
await redis.connect();
const limiter = new RateLimiterRedis({
storeClient: redis,
keyPrefix: 'rl:api',
points: 100,
duration: 60,
insuranceLimiter: {
points: 60,
duration: 60,
},
});
async function guard(userId) {
try {
const res = await limiter.consume(userId, 1);
return {
allowed: true,
remaining: res.remainingPoints,
resetMs: res.msBeforeNext,
};
} catch (rej) {
if (rej instanceof Error) throw rej; // Redis 故障,交给保险限流器兜底
return {
allowed: false,
remaining: rej.remainingPoints,
retryAfterMs: rej.msBeforeNext,
};
}
}
const first = await guard('user-9');
const burst = await Promise.all(
Array.from({ length: 5 }, () => guard('user-9')),
);
console.log(first.allowed, burst.filter((b) => !b.allowed).length, 'throttled');
await redis.quit();