接口限流:固定窗口/滑动窗口/漏桶/令牌桶
更新时间:2026-09-18。限流是保护系统不被突发流量压垮的第一道闸。它和熔断降级、背压/Reactor Flux、服务雪崩防护共同构成可靠性体系。
一、为什么需要限流
系统的处理能力有上限(线程池、连接池、下游配额)。超过上限的请求若全部放行,会导致排队、超时、资源耗尽,进而雪崩。限流在入口主动拒绝一部分请求(fail fast),保住核心容量。

二、四种算法对比
| 算法 | 思想 | 突发能力 | 平滑性 | 典型实现 |
|---|---|---|---|---|
| 固定窗口 | 每个时间窗计数,超限拒绝 | 窗口边界可放 2 倍流量 | 差 | 计数器/Redis INCR+EXPIRE |
| 滑动窗口 | 细分子窗口/日志按时间滑动统计 | 精确、无边界翻倍 | 较好 | Sentinel、滑动日志 |
| 漏桶 Leaky Bucket | 请求进桶,以固定速率漏出 | 强制恒定速率 | 最好(绝对平滑) | 队列+定时消费 |
| 令牌桶 Token Bucket | 以固定速率放令牌,请求取令牌,桶可攒 | 允许一定突发 | 较好 | Guava/Redisson RateLimiter |
- 固定窗口临界问题:1 分钟限 100,在 0:59 放 100、1:00 又放 100,瞬间 200。滑动窗口正是为消除它。
- 令牌桶 vs 漏桶:令牌桶因能囤积令牌,允许合理突发,更适合保护「平均速率但能扛短时尖峰」的 API;漏桶绝对平滑,适合向下游恒定速率输送(如对接有严格 QPS 的第三方)。
三、单机限流:Guava RateLimiter
java
RateLimiter limiter = RateLimiter.create(100.0); // 每秒 100 个令牌(QPS=100)
if (limiter.tryAcquire(1, 50, TimeUnit.MILLISECONDS)) {
handle(req);
} else {
resp.setStatus(429); // Too Many Requests,可带 Retry-After
}注意 Guava 默认是 SmoothBursty,tryAcquire() 不阻塞、acquire() 会阻塞等待;其「预消费」允许提前取未来令牌(后续请求多等),突发语义要理解清楚。Guava 限流器是单机内存,多实例部署时每台各自计数,总阈值=单机阈值×实例数。
四、分布式限流:Redisson / Redis / Sentinel
java
// Redisson 令牌桶(基于 Redis,多实例共享配额)
RRateLimiter rl = redissonClient.getRateLimiter("rl:createOrder");
rl.trySetRate(RateType.OVERALL, 500, 1, RateIntervalUnit.SECONDS);
if (!rl.tryAcquire(1)) { reject429(); }- 共享配额适合「对下游有总 QPS 约束」的场景;代价是每次获取令牌的网络往返,可用 Lua 脚本原子化或本地批量领令牌分摊。
- Sentinel:按资源做 QPS/线程数流控,支持滑动窗口、冷启动(Warm Up)、匀速排队,集群限流可配 token server,适合微服务体系并带控制台。
- 网关层(Nginx
limit_req、Spring Cloud Gateway、Envoy local/global rate limit)可在更靠前的入口统一限流。
五、落地要点
- 限流维度/键:全局、用户、IP、接口、租户——按业务最需要保护的维度做(如
rl:user:{uid}:{api}),并给每维合理默认值,避免单用户耗尽全局。 - 拒绝要友好:返回
429 + Retry-After,幂等读可让客户端退避重试;区分「被限流」和「系统错误」,监控拒绝率。 - 多级限流:网关粗粒度(防 DDoS/总量)+ 服务细粒度(保护关键资源)+ 下游配额(保护第三方),不要只靠一层。
- 冷启动预热:刚启动/JIT 未热、缓存未暖时承受不了满额流量,用 Warm Up 让阈值爬坡。
- 限流与熔断别混淆:限流管「入口流量速率」,熔断管「下游故障时快速失败」,二者常配合;超出速率也可用队列削峰,但必须配合有界队列+背压(见背压文),否则只是把压力转成堆积。
六、常见坑
- 固定窗口用于严格保护:临界 2 倍突发会击穿下游,严格场景用滑动窗口/令牌桶。
- 多实例只装单机限流器:以为限 500 QPS,实际 500×实例数;要总配额上分布式限流。
- Redis 限流非原子/时钟问题:INCR+EXPIRE 竞态会让窗口泄漏,用 Lua 保证原子。
- 限流粒度过粗:全局 QPS 限流无法防单热点用户,需要按用户/资源维度。
- 拒绝无退避:客户端无脑重试会形成重试风暴,429 应带退避并保证重试本身也受限。
- 令牌桶突发参数过大:桶容量等于允许的瞬时突发,设太大等于没保护,应按下游真实承压能力定。
总结
限流在入口按容量主动丢弃超额请求以避免资源耗尽雪崩:固定窗口实现简单但窗口边界会放出两倍流量,滑动窗口统计精确但成本略高,漏桶强制恒定速率最平滑适合对接严格下游,令牌桶按固定速率发令牌且允许囤积产生合理突发、是保护 API 平均速率的首选;单机用 Guava RateLimiter(注意 SmoothBursty 的预消费、acquire 阻塞与每实例独立计数),多实例要总配额时用 Redisson 令牌桶/Lua 原子脚本或 Sentinel 集群限流,网关层用 Nginx/Gateway/Envov 前置粗粒度限流;落地要按全局/用户/接口多维度设键、返回 429 与 Retry-After 并让退避重试本身受限、配合冷启动预热,并分清限流管入口速率而熔断管下游故障、有界队列削峰必须配合背压。