Appearance
分布式限流:算法、共享状态与故障策略
修订于 2026-10-12:原文的促销事故、QPS 能力和无来源的配置阈值已删除。限流参数必须依据业务目标和负载测试确定。
限流要先回答“限制什么”:每个用户、每个 API Key、每个租户、每条路由,还是整个服务?同一个数值放在不同维度上,保护效果和误伤范围完全不同。
常见算法
| 方法 | 主要特性 | 需要注意 |
|---|---|---|
| 固定窗口计数 | 实现简单,按时间窗口统计请求数。 | 相邻窗口边界可能出现短时突增。 |
| 滑动窗口 | 更接近最近一段时间的真实请求量。 | 状态与计算成本通常更高。 |
| 令牌桶 | 允许在持续速率之外容纳一定突发量。 | 要分别设置补充速率与桶容量。 |
| 漏桶 | 更关注以稳定速率处理或放行。 | 排队空间和等待时间需受控。 |
算法名称不能直接决定产品行为。AWS API Gateway 文档说明其令牌桶节流是 best effort 的目标,而非严格不可突破的请求上限。Nginx 的 limit_req 文档则说明该模块使用漏桶方法。以具体产品文档为准。
多实例为什么更难
如果每个实例只维护本地计数,同一用户的请求被负载均衡到不同实例时,就可能绕过期望的全局限制。共享存储可以统一状态,但每次请求都访问共享点,会引入延迟和新的故障依赖。
Redis 文档展示了固定窗口计数、滑动窗口和令牌桶等方案。对于“读取状态 → 判断 → 更新状态”的过程,要考虑并发原子性;简单地分开执行计数与过期设置可能产生竞态,具体实现应遵循产品文档。还应设置键生命周期和内存边界。
故障时如何处理
限流器自身不可用时,系统可以选择放行、拒绝或退回到本地保护,但每种选择都有代价:
- 放行可能让下游过载。
- 拒绝可能扩大故障影响。
- 本地限制能提供部分保护,但无法保证全局配额。
应按接口重要性定义故障策略,并对外返回清晰状态。客户端收到 429 后可按服务端提示和剩余期限决定是否退避重试;不能立即无限重试。
上线验证
- 用真实流量估算持续速率、峰值和关键租户分布。
- 在多实例环境测试窗口边界、并发请求和共享存储失效。
- 记录允许、拒绝、排队和限流器错误数量;分别看误伤与下游保护效果。
- 测试热键、恶意请求和突发流量,避免限流状态成为新的瓶颈。
- 给参数留回滚与调节路径;不用单个通用 QPS 值覆盖所有接口。