限流(Rate Limiting)是保障系统稳定性和服务质量的关键机制,尤其在高并发、突发流量、攻击防护等场景中至关重要。
本文将详细介绍四种主流限流算法:
固定窗口(Fixed Window)
滑动窗口(Sliding Window)
令牌桶(Token Bucket)
漏桶算法(Leaky Bucket)
我们将从原理、实现方式、优缺点和适用场景四个维度进行深入分析和对比。
一、固定窗口(Fixed Window)
原理:将时间划分为固定长度窗口,统计每个窗口内的请求数,超过上限则拒绝。
实现方式(常用 Redis INCR + EXPIRE):
key = f"req:{user_id}:{current_minute}"
count = redis.incr(key)
if count == 1:redis.expire(key, 60)
if count > threshold:reject()
优点:
实现简单,性能高。
Redis 支持天然适配。
缺点:
临界突刺问题:在两个窗口交界点,可能放行两倍请求。
适用场景:
精度要求不高的接口。
管控类后台系统。
二、滑动窗口(Sliding Window)
1. 滑动日志(Sliding Log)
原理:记录每次请求时间戳,实时清理超出时间窗口的旧请求,判断窗口内数量。
优点:
流量限制更精确。
避免突刺。
缺点:
需要维护时间戳列表,内存消耗较大。
2. 滑动窗口计数器(Sliding Window Counter)
原理:将一个窗口拆分为多个小窗口,每个子窗口统计请求数,根据时间加权合并。
优点:
性能和精度之间较好平衡。
适用场景:
高并发、登录、敏感操作等流控要求较高的业务。
三、令牌桶算法(Token Bucket)
原理:
系统以固定速率放入“令牌”到桶中;
每次请求需要“取一个令牌”才能通过;
桶容量有限,超过上限的令牌被丢弃;
若桶为空,请求被限流(或排队等)。
示意图:
+----------+
| 令牌桶 |
| |<-- 固定速率生成令牌
| [ ] |
+----------+↓请求来取令牌 -> 成功 or 被限流
优点:
支持突发流量(令牌可积累)。
灵活控制平均速率与突发能力。
Guava / Nginx 都有成熟实现。
缺点:
实现较复杂。
依赖精准时间调度。
适用场景:
接口限频,突发高并发业务。
LLM API 限流 / OpenAI、Stripe 等系统。
四、漏桶算法(Leaky Bucket)
原理:
所有请求先进入一个桶中;
桶以固定速率“漏水”处理请求;
桶满时,新请求要么被丢弃,要么排队等待。
示意图:
请求 → 桶(队列) → 以恒定速率处理(漏水)↑桶满则拒绝或排队
实现方式:
可以用一个队列存储请求;
后台定期以固定速率出队并处理请求。
优点:
平滑处理请求流量,保持处理速率稳定。
防止服务被瞬时流量压垮。
缺点:
不支持突发流量(桶中积压,排队延迟)。
实现复杂度略高于令牌桶。
适用场景:
网关、负载均衡器的请求调度。
强调处理速率恒定的后台任务系统。
五、算法对比总结
特性 | 固定窗口 | 滑动窗口 | 令牌桶 | 漏桶 |
---|---|---|---|---|
实现复杂度 | ⭐(简单) | ⭐⭐(中等) | ⭐⭐⭐(中高) | ⭐⭐(中等) |
限流精度 | 低 | 高 | 中等 | 高 |
是否支持突发请求 | 否 | 否 | ✅ | ❌ |
是否平滑 | ❌ | ✅ | ✅ | ✅ |
是否常用 | ✅(Redis) | ✅(Sentinel) | ✅(Guava、Nginx) | ✅(调度系统) |
六、实战建议
简单限流场景(如后台管理):使用 固定窗口 + Redis 即可。
高并发接口限频:推荐 滑动窗口 或 令牌桶,后者更适合突发请求。
需要平滑处理队列任务:选择 漏桶算法。
七、结语
限流并不是一刀切的“阻止请求”,而是为系统争取喘息空间。不同的限流算法背后体现的是不同的设计哲学与业务权衡。
选对限流算法,既可以保护系统,又能优化用户体验。