限流算法与容量保护
限流把进入系统的请求速率或并发限制在可承受范围内。选择算法前要先确定保护对象:是每秒到达量、同时执行量、租户配额,还是下游某种昂贵资源。
1. 固定窗口计数实现简单
固定窗口把时间切成一分钟或一秒,窗口内计数达到上限后拒绝。它容易用本地原子计数或 Redis 实现,但窗口边界会允许突发:前一窗口末尾和后一窗口开头都打满时,短时间流量可接近两倍阈值。
它适合账单或粗粒度配额,不适合需要平滑保护的关键资源。
2. 滑动窗口提高时间精度
滑动日志记录窗口内每个请求时间,统计准确但内存和清理成本随请求量增长。滑动窗口计数器把时间分成多个小格,按重叠比例近似当前窗口,成本更可控。
格子越细,结果越接近精确滑动窗口,状态和协调成本也越高。限流精度应服务于容量目标,不必追求每个毫秒绝对准确。
3. 漏桶把输出整形成稳定速率
请求进入有界队列,处理方按固定速率取出;队列满时拒绝。它可以平滑突发,但会增加排队延迟,也无法立即利用后端刚释放的额外容量。
队列必须有长度和等待时限。没有边界的“削峰”最终会变成内存耗尽和过期请求堆积。
4. 令牌桶允许受控突发
系统按固定速率生成 token,桶容量限制最多积累多少。请求取得 token 后立即通过,没有 token 则等待或拒绝。
- 生成速率控制长期平均流量。
- 桶容量控制一次允许的突发量。
令牌桶适合下游能承受短突发的在线请求。每种请求成本不同,可以按预计 CPU、数据库查询或 token 消耗使用加权配额,而不是一律每请求一个令牌。
5. 并发限制保护在途资源
速率相同的请求,耗时从 20 ms 上升到 2 s 后,在途数量会增加百倍。只限制 QPS 不能防止线程、连接和内存被占满。
并发限制直接约束 active request 数。可根据 Little's Law 用到达率和目标延迟估算初值,再通过压测校准。自适应限制可以根据延迟和排队变化调整,但必须设置安全上下界。
6. 限流键决定公平性
全局限流保护整个服务,按租户、用户、API 或业务键限流则阻止单一来源占满容量。常见组合是:
- 入口全局上限。
- 租户配额和突发额度。
- 单接口或高成本操作上限。
- 实例本地并发保护。
限流键来自可信身份,不能只使用容易伪造或共享的 IP。多租户配额还要防止大量低频 key 让限流状态无限增长。
7. 分布式限流在精度与可用性间取舍
中心计数可以更准确执行全局配额,但每次请求远程访问限流服务会增加延迟,并形成新故障点。常见折中包括给各实例预分配 token、本地快速扣减,再周期补充;代价是短时间可能超出全局阈值。
中心服务不可用时,关键写接口可 fail-closed,普通读取则使用本地保守额度继续。无论哪种策略,本地并发上限都应独立存在。
8. 被限流请求尽快返回明确结果
在线请求通常返回 429 或明确业务错误,并在合适时提供 Retry-After。只有等待时间短于剩余 deadline 且队列有界时才排队。
限流阈值要根据压测、下游容量和 SLO 设置。错误率升高时临时降低放行量可以保护系统,但不能用限流掩盖长期容量不足。
9. 常见问题
9.1 令牌桶和漏桶的主要差异是什么
漏桶以固定速度处理队列,输出更平滑;令牌桶按固定速率积累额度,桶内有令牌时允许短时突发。两者都需要容量上限,选择取决于后端是否能承受突发和请求能否等待。
9.2 限制 QPS 后为什么线程池仍会满
请求耗时可能上升,使相同 QPS 对应更多并发任务;还可能有不同接口共享线程池。应同时限制在途并发、队列和下游连接,并观察服务时间。
10. 面试题
10.1 固定窗口、滑动窗口、漏桶和令牌桶如何选择
出现公司:字节跳动、阿里巴巴、滴滴
考察重点
- 窗口边界、状态成本、平滑输出与突发额度。
- 速率限制和并发限制的差异。
- 全局配额、本地保护与失败策略。
相关内容:第 1 节“固定窗口计数实现简单”至第 8 节“被限流请求尽快返回明确结果”。
参考回答
固定窗口实现简单但边界会产生突发;滑动日志精确但状态大,滑动计数用小窗口近似。漏桶通过有界队列按固定速率输出,适合需要平滑处理但会增加等待;令牌桶按固定速率补充令牌,桶容量允许受控突发,更适合在线流量。
算法还要和保护目标匹配。QPS 限制不能防止请求变慢后在途任务增多,所以要同时限制并发、队列和连接。集群可用中心配额或预分配 token 平衡精度与可用性,并按租户和高成本接口保证公平;中心故障时仍保留实例本地保护。