3、速率限制基础:令牌桶、漏桶、固定窗口、滑动窗口算法的原理与对比

在分布式系统尾延迟压测中,速率限制(Rate Limiting)是防止系统过载、保障服务质量的核心手段。不同的速率限制算法在突发处理能力、内存开销、边界平滑度上存在显著差异。本节将深入剖析四种经典算法的原理,并通过对比表格和流程图帮助建立直观理解。

3.1 令牌桶算法(Token Bucket)

令牌桶算法是最常用的速率限制算法之一,它允许一定程度的突发流量,同时保证长期平均速率不超过阈值。

  • 核心原理:系统以固定速率向桶中放入令牌,每个请求消耗一个令牌。桶有最大容量,超出部分令牌丢弃。
  • 关键参数:rate(令牌生成速率,即平均 QPS)、burst(桶容量,即最大突发请求数)。
  • 行为特征:当桶中有令牌时,请求可立即通过(允许突发);当桶空时,请求被拒绝或排队等待。
  • 适用场景:允许短时间流量尖峰的网络接口、API 网关、微服务限流。
压测关注点:令牌桶的突发能力在压测中容易掩盖真实瓶颈。如果压测流量在桶容量范围内波动,后端可能感知不到限流效果,导致尾延迟在突发窗口内急剧恶化。
令牌桶算法流程 令牌生成器 rate 个/秒 令牌桶 容量 = burst 当前令牌数 (动态变化) 请求消耗 通过(消耗令牌) 拒绝(桶空) 关键特征: • 允许突发:只要桶中有令牌,请求即可立即通过,不受瞬时速率限制。 • 长期平滑:令牌生成速率 rate 决定了长期平均 QPS,桶容量 burst 决定了最大突发规模。

3.2 漏桶算法(Leaky Bucket)

漏桶算法与令牌桶相反,它强制将请求流整形为恒定速率输出,不允许任何突发。

  • 核心原理:请求进入桶中,桶底部以固定速率“漏出”请求进行处理。桶满时新请求被丢弃。
  • 关键参数:leak_rate(漏出速率,即处理速率)、bucket_size(桶容量,即最大排队长度)。
  • 行为特征:输出速率严格恒定,输入突发被缓冲或丢弃。没有“借令牌”或“预支”机制。
  • 适用场景:需要严格平滑输出流量的场景,如数据库写入限流、消息队列消费速率控制。
压测对比:漏桶算法在压测中会直接暴露排队延迟。当输入速率超过漏出速率时,请求在桶中排队,尾延迟会随队列深度线性增长。这是模拟真实过载场景的理想选择。

3.3 固定窗口算法(Fixed Window)

固定窗口算法实现简单,将时间划分为固定长度窗口,每个窗口内限制请求总数。

  • 核心原理:以固定时间间隔(如 1 秒)划分窗口,每个窗口维护一个计数器。请求到达时计数器加 1,超过阈值则拒绝。
  • 关键参数:window_size(窗口大小,如 1s)、limit(窗口内最大请求数)。
  • 行为特征:实现简单,内存开销极低(仅需一个计数器+时间戳)。但存在“边界突刺”问题:窗口切换瞬间可能允许两倍于阈值的请求通过。
  • 适用场景:对精度要求不高、资源受限的简单限流场景。
边界突刺问题:假设窗口大小为 1s,阈值为 100。如果在第 0.999s 和第 1.001s 各发送 100 个请求,由于分属两个窗口,实际通过 200 个请求,瞬时速率达到阈值的 2 倍。这在压测中会导致不可预测的尾延迟抖动。

3.4 滑动窗口算法(Sliding Window)

滑动窗口算法通过将窗口细分为更小的子窗口(slot),解决了固定窗口的边界突刺问题。

  • 核心原理:将时间窗口划分为 N 个等长子窗口(如 1s 窗口划分为 10 个 100ms slot)。每个 slot 独立计数,滑动窗口的总请求数为当前窗口内所有 slot 计数之和。
  • 关键参数:window_size(总窗口大小)、slot_count(子窗口数量)、limit(窗口内最大请求数)。
  • 行为特征:边界平滑,精度随 slot 数量增加而提升。内存开销为 O(N),N 为 slot 数量。
  • 适用场景:需要精确限流、防止边界突刺的生产环境,如 API 网关、负载均衡器。
滑动窗口 vs 固定窗口:边界突刺对比 固定窗口(边界突刺) 窗口 1 (100 req) 窗口 2 (100 req) 边界处 200 req 通过 滑动窗口(平滑) 10 个 slot,每个 100ms 窗口平滑滑动,无突刺 滑动窗口优势: • 固定窗口在边界处允许两倍阈值流量通过,滑动窗口通过细粒度 slot 滚动计数消除此问题。 • 滑动窗口精度由 slot 数量决定:slot 越多,内存开销越大,但限流越平滑。

3.5 四种算法对比总结

特性 令牌桶 漏桶 固定窗口 滑动窗口
突发处理 允许突发(桶容量内) 不允许突发(严格整形) 允许边界突刺(缺陷) 允许突发(窗口内平滑)
输出速率 平均速率 = rate,瞬时可突发 严格恒定 = leak_rate 窗口内平均,边界处翻倍 窗口内平均,边界平滑
内存开销 O(1)(计数器+时间戳) O(1)(计数器+时间戳) O(1)(计数器+时间戳) O(N)(N 个 slot 计数器)
实现复杂度 中等 低 极低 中等
边界突刺 无(桶机制天然平滑) 无(输出恒定) 严重(窗口切换时翻倍) 无(slot 滚动消除)
尾延迟影响 突发时延迟低,桶空时延迟高 排队延迟随队列深度线性增长 边界突刺导致不可预测抖动 平滑,延迟可预测
推荐压测场景 模拟突发流量冲击 模拟恒定负载过载 不推荐(精度不足) 模拟真实生产限流

3.6 压测场景下的算法选择建议

  • 模拟突发流量:使用令牌桶算法,设置 burst 参数模拟瞬间流量尖峰,观察后端系统的排队和降级行为。
  • 模拟恒定过载:使用漏桶算法,设置 leak_rate 低于压测发送速率,观察请求排队延迟和丢弃率。
  • 生产环境限流:优先选择滑动窗口算法,兼顾精度和实现复杂度。固定窗口仅用于资源受限或精度要求不高的场景。
  • 压测工具实现:大多数压测工具(如 wrk、vegeta、locust)内置令牌桶限流器。如需精确控制,可自行实现滑动窗口限流器。
压测最佳实践:在尾延迟压测中,建议同时使用两种限流算法:
1. 压测端使用令牌桶控制发送速率,允许突发以模拟真实流量模式。
2. 被测系统端使用滑动窗口限流保护后端,避免边界突刺导致误判。
这种组合可以更真实地反映生产环境中的限流行为。