4. 常见限流算法详解(下):令牌桶算法、漏桶算法

在上一节中,我们深入剖析了计数器算法与滑动窗口算法的实现原理与边界问题。本节将聚焦于两种在金融低延迟系统中应用最广泛的“桶”模型算法——令牌桶算法与漏桶算法。理解它们在应对突发流量与平滑流量时的本质差异,是设计高可用限流架构的关键。

4.1 令牌桶算法(Token Bucket)

令牌桶算法允许一定程度的突发流量,同时保证长期的平均速率。其核心思想是:系统以恒定速率向一个桶中放入令牌,每个请求必须从桶中获取一个或多个令牌才能被处理。如果桶中没有足够的令牌,请求将被拒绝或等待。

4.1.1 核心参数与工作原理

参数 说明 典型值(金融场景)
桶容量(burst) 桶中最多能存放的令牌数,决定了允许的最大突发流量 1000 ~ 10000
令牌生成速率(rate) 每秒钟向桶中添加的令牌数量,决定了长期平均速率 100 ~ 5000 QPS
当前令牌数(tokens) 桶中实时剩余的令牌数量 动态变化
最后填充时间(lastRefillTime) 上次计算令牌数量的时间戳,用于计算应补充的令牌 毫秒级时间戳

算法流程如下:

  1. 令牌补充:每次请求到达时,根据当前时间与上次补充时间的差值,计算应补充的令牌数(delta = (now - lastRefillTime) * rate / 1000),并将令牌数更新为 min(capacity, tokens + delta)。
  2. 令牌消耗:如果当前令牌数 ≥ 请求所需令牌数(通常为1),则消耗对应令牌,允许请求通过;否则拒绝请求。
  3. 突发处理:当桶满时,多余的令牌被丢弃,但桶中已有的令牌可以支撑瞬时高流量。
性能关键点:令牌桶算法的核心操作是“时间差计算 + 令牌数更新”,时间复杂度为 O(1)。在金融低延迟场景中,推荐使用 预计算 + 懒更新 策略,即不在定时器中补充令牌,而是在每次请求时按时间差批量补充,避免额外的线程调度开销。

4.1.2 令牌桶算法流程图

令牌桶算法流程图 令牌桶算法流程图 请求到达 计算时间差 delta 补充令牌:tokens = min(cap, tokens + delta) tokens ≥ 1 ? 消耗令牌 tokens -= 1 请求通过 令牌不足 拒绝请求 关键设计要点 • 令牌补充采用“懒更新”方式,仅在请求到达时计算,避免定时器开销。 • 桶容量决定了突发流量的大小,需根据上游系统容量谨慎设置。 • 令牌消耗操作需保证原子性,推荐使用 CAS 或原子变量实现。

4.2 漏桶算法(Leaky Bucket)

漏桶算法与令牌桶算法相反,它强制一个恒定的输出速率,不允许突发。其核心思想是:将请求看作水滴,桶底部有一个固定大小的漏洞,水以恒定速率流出。当桶满时,新进入的水滴会溢出(即请求被拒绝)。

4.2.1 核心参数与工作原理

参数 说明 典型值(金融场景)
桶容量(capacity) 桶中最多能容纳的请求数,相当于等待队列长度 100 ~ 500
漏出速率(leakRate) 每秒钟从桶中流出的请求数,即处理速率 100 ~ 5000 QPS
当前水量(water) 桶中当前积压的请求数 动态变化
上次漏水时间(lastLeakTime) 上次计算漏水量的时间戳 毫秒级时间戳

算法流程如下:

  1. 漏水计算:每次请求到达时,根据当前时间与上次漏水时间的差值,计算应漏出的水量(delta = (now - lastLeakTime) * leakRate / 1000),并将水量更新为 max(0, water - delta)。
  2. 入桶判断:如果当前水量 + 1 ≤ 桶容量,则允许请求入桶(水量+1),请求被排队等待处理;否则请求被拒绝。
  3. 恒定输出:无论入桶速率如何,出桶速率始终为 leakRate,实现绝对平滑。
注意:漏桶算法在金融系统中常用于保护下游数据库或第三方接口,这些场景对请求速率的波动极其敏感。但漏桶算法会引入额外的排队延迟,不适合对延迟要求极高的交易链路。

4.2.2 漏桶算法流程图

漏桶算法流程图 漏桶算法流程图 请求到达 计算时间差 delta 漏水:water = max(0, water - delta) water + 1 ≤ cap ? 请求入桶 water += 1 排队等待处理 桶已满 请求溢出(拒绝) 关键设计要点 • 漏桶算法强制恒定输出速率,适合保护对速率波动敏感的数据库或第三方接口。 • 桶容量决定了最大排队深度,需根据业务容忍的延迟时间设置。 • 漏水计算同样采用“懒更新”策略,避免定时器开销。

4.3 突发流量 vs 平滑流量:性能差异对比

两种算法在应对不同流量模式时表现出截然不同的特性。下表从多个维度进行对比:

对比维度 令牌桶算法 漏桶算法
突发流量处理 允许突发,桶中积攒的令牌可一次性消耗 不允许突发,输出速率恒定
平滑流量处理 长期平均速率受控,但瞬时速率可能波动 输出绝对平滑,无任何波动
延迟特性 低延迟,令牌足够时请求立即通过 引入排队延迟,延迟与当前水量成正比
资源利用率 较高,可充分利用空闲时段处理突发 较低,即使系统空闲也按恒定速率处理
适用场景 API网关、交易系统、需要应对秒杀/抢购的场景 数据库保护、第三方接口限流、对抖动敏感的下游
实现复杂度 中等,需处理令牌补充与消耗的原子性 中等,需处理漏水计算与入桶判断

4.3.1 流量模式对比图

令牌桶 vs 漏桶 流量模式对比 令牌桶 vs 漏桶 流量模式对比 令牌桶算法 输入流量: 输出流量: • 输入有高有低,输出可突发(绿色块高度代表速率) • 允许短时间超过平均速率 • 适合需要应对突发流量的场景 漏桶算法 输入流量: 输出流量: • 输入有高有低,输出恒定(绿色块高度一致) • 强制平滑,不允许任何突发 • 适合保护对速率波动敏感的下游 总结:令牌桶允许突发,适合API网关;漏桶强制平滑,适合数据库保护

4.4 金融场景选型建议

  • 交易核心链路(订单、支付、风控):推荐使用 令牌桶算法。这些场景需要应对秒杀、行情波动等突发流量,同时要求低延迟。令牌桶可以在系统空闲时积攒令牌,在突发时快速释放,最大化资源利用率。
  • 数据库/缓存保护:推荐使用 漏桶算法。数据库连接池、Redis 实例等对请求速率波动非常敏感,漏桶的恒定输出特性可以有效防止“惊群效应”和连接池打满。
  • 第三方接口限流:根据第三方接口的 SLA 选择。如果第三方允许突发,使用令牌桶;如果第三方强制 QPS 上限且不接受突发,使用漏桶。
实战经验:在金融系统中,我们通常将令牌桶算法用于 API 网关层,将漏桶算法用于 服务间调用层。网关层需要快速响应突发流量,而服务间调用需要保护下游资源。两种算法可以组合使用,形成多层限流架构。