为什么接口需要限流?
限流是保护系统免受过载请求冲击的常用手段。无论是防止恶意爬虫、应对突发流量,还是保障核心接口的稳定性,选择合适的限流算法都至关重要。本文对比计数器、漏桶和令牌桶三种算法在接口层的落地差异,帮助你做出合理选型。
1. 固定窗口计数器
原理:将时间划分为固定窗口(如1秒),每个窗口内维护一个计数器。请求到达时计数器加1,超过阈值则拒绝,窗口结束后计数器清零。
优点:实现简单,内存占用小,适合单机或对精度要求不高的场景。
缺点:存在“临界问题”。例如限制每秒100次,若在窗口最后10毫秒涌入100次,下一个窗口开始又涌入100次,实际2个窗口间可能通过200次请求,超出预期。
落地建议:
- 使用 Redis 的
INCR+EXPIRE实现分布式计数器。 - 注意原子性,可用 Lua 脚本保证。
- 适用于对突发流量不敏感的粗粒度限流。
2. 滑动窗口计数器
原理:固定窗口的改进版,将大窗口拆分为多个小窗口(如1秒拆成10个100毫秒),统计最近N个小窗口的总请求数。
优点:缓解临界问题,精度更高。
缺点:需要存储更多状态,内存消耗增加。
落地建议:
- 可用 Redis 的 ZSET 或 List 存储时间戳,但需注意性能。
- 适合对平滑性有一定要求的 API 网关。
3. 漏桶算法
原理:请求像水一样注入漏桶,桶以恒定速率漏水(处理请求)。桶满则拒绝新请求。
优点:强制恒定输出速率,能平滑突发流量,保护下游服务。
缺点:无法应对合理的突发流量(即使系统有空闲资源),且需要队列缓冲,增加延迟。
落地建议:
- 常用于流量整形,如 Nginx 的
limit_req模块即基于漏桶。 - 实现时可用队列 + 定时消费,或令牌桶模拟。
4. 令牌桶算法
原理:以恒定速率向桶中添加令牌,桶有容量上限。请求到达时需获取令牌,无令牌则拒绝或等待。
优点:允许一定程度的突发流量(桶中积累的令牌可一次性使用),同时限制平均速率。
缺点:实现稍复杂,需维护令牌数量和时间戳。
落地建议:
- 常用实现:Guava RateLimiter(单机)、Redis + Lua(分布式)。
- 适合需要兼顾平均速率和突发能力的接口,如 API 限流。
选型对比
| 算法 | 突发流量 | 平滑度 | 实现复杂度 | 典型场景 |
|---|---|---|---|---|
| 固定窗口 | 允许但可能超限 | 差 | 低 | 简单计数 |
| 滑动窗口 | 较平滑 | 较好 | 中 | API 网关 |
| 漏桶 | 严格限制 | 好 | 中 | 流量整形 |
| 令牌桶 | 允许一定突发 | 较好 | 中高 | 通用限流 |
落地注意事项
- 分布式一致性:多实例部署时,需使用 Redis 等集中存储,并保证原子性。
- 性能开销:限流本身不应成为瓶颈,避免频繁网络往返,可考虑本地缓存 + 集中同步。
- 降级策略:限流触发后,应返回友好错误(如 429 Too Many Requests),并记录日志。
- 动态调整:阈值最好可配置,便于根据业务变化调整。
总结
- 若只需简单防护,固定窗口计数器足够。
- 若需平滑流量且允许突发,令牌桶是首选。
- 若需严格限制输出速率,漏桶更合适。
- 滑动窗口是固定窗口与漏桶之间的折中。
实际选型应结合业务特点、精度要求和系统架构,必要时可组合使用。