第 4 章 设计限流器
引言
本章探讨限流器(Rate Limiter)的设计与实现。限流器是一种系统组件,用于控制客户端或服务发送流量的速率。限流器对于防止滥用、降低成本以及保证服务器资源的稳定性至关重要。它的应用示例包括限制发帖次数、账号注册次数以及领取奖励的次数。
限流的好处
- 防范 DoS 攻击: 拦截过量的调用,避免资源耗尽。
- 降低成本: 限制不必要的请求,减少服务器开支。
- 防止过载: 过滤掉过多的请求,保持服务器性能稳定。
第 1 步:理解问题
关键功能
- 服务端的 API 限流器。
- 支持多种限流规则。
- 能够应对分布式环境下的大规模系统。
- 可以作为独立服务,也可以作为应用层代码实现。
- 请求被限流时通知用户。
需求
- 准确地限制请求。
- 尽可能低的延迟。
- 低内存占用。
- 支持分布式。
- 清晰的异常处理。
- 高容错性。
第 2 步:高层设计
放置位置的选择
- 在客户端实现: 由于可能被恶意利用,并不可靠。
- 在服务端实现: 便于控制且可靠,是首选方案。
- 中间件(API 网关): 一种灵活的选择,可以集成限流功能。
放置位置的指导原则
- 评估当前的技术栈,选择高效的方案。
- 根据业务需求选择合适的算法。
- 如果采用了微服务,可以使用 API 网关。
- 如果资源有限,可以选择商业方案。
第 3 步:限流算法
1. 令牌桶
- 描述: 以固定速率向桶中添加令牌,每个请求消耗一个令牌。
- 参数: 桶的大小和令牌补充速率。
- 优点: 易于实现,节省内存,支持突发流量。
- 缺点: 需要仔细调整参数。
2. 漏桶
- 描述: 使用先进先出(FIFO)队列,以固定速率处理请求。
- 优点: 节省内存,流出速率稳定。
-
缺点: 突发流量可能会导致最近的请求被延迟处理。
示例:https://github.com/uber-go/ratelimit
3. 固定窗口计数器
- 描述: 把时间划分为固定的区间,并使用计数器来限制请求数。
- 优点: 简单,在特定场景下效率高。
-
缺点: 窗口边缘的流量尖峰可能会超出限额。
-
时间窗口边缘的突发流量可能会导致通过的请求数超过允许的配额。

4. 滑动窗口日志
- 描述: 通过记录时间戳来实现滚动的时间窗口。
- 优点: 限流准确。
- 缺点: 内存消耗高。
5. 滑动窗口计数器
- 描述: 结合固定窗口和滑动日志两种方法,用于平滑流量尖峰。
- 优点: 节省内存,能应对突发流量。
- 缺点: 由于是近似计算,可能不够严格。
高层架构
- 数据存储: 使用内存缓存(例如 Redis)来实现快速的计数器操作。
- 步骤:
- 客户端向中间件发送请求。
- 中间件检查 Redis 中的计数器。
- 根据限额处理或拒绝该请求。
进阶考虑
分布式环境
- 挑战: 竞态条件、同步问题。
- 解决方案: 使用锁、Lua 脚本或 Redis 中的有序集合(sorted set)。采用集中式数据存储来实现同步。
性能优化
- 采用多数据中心部署以降低延迟。
- 采用最终一致性(eventual consistency)模型进行同步。
监控
- 定期进行数据分析,确保算法有效,并按需调整规则。