3. 常见限流算法:原理与数学推导

在金融压力测试场景中,限流算法的选择直接决定系统在高并发下的行为特征。本节从数学原理出发,深入剖析四种经典限流算法:计数器算法、滑动窗口算法、漏桶算法与令牌桶算法,并给出各自的适用边界与数学推导。

3.1 计数器算法(Counter Algorithm)

原理:将时间划分为固定大小的窗口(如 1 秒),每个窗口内维护一个计数器。请求到达时计数器加 1,若计数器超过阈值则拒绝;窗口结束时计数器归零。

数学推导:

  • 设窗口大小为 \( T \)(单位:秒),阈值上限为 \( N \)(单位:请求数)。
  • 在任意窗口 \( [k \cdot T, (k+1) \cdot T) \) 内,累计请求数 \( C(t) \) 满足:
C(t) = Σ_{i=1}^{m} 1_{t_i ∈ [kT, (k+1)T)}
  • 限流条件:\( C(t) \leq N \),否则拒绝后续请求。
  • 临界突变问题:若在窗口末尾(如 0.99s)突发 N 个请求,紧接着下一窗口开头(1.00s)又突发 N 个请求,则瞬时 QPS 可达 \( 2N \),远超阈值。
金融场景警示:计数器算法在窗口边界处存在“流量毛刺”,不适合对突发敏感的交易接口(如下单、撤单)。实测中,2N 的瞬时流量可能触发下游数据库连接池打满。

3.2 滑动窗口算法(Sliding Window Algorithm)

原理:将固定窗口细分为多个子窗口(如 1 秒分为 10 个 100ms 子窗口),每个子窗口独立计数。滑动窗口以当前时间为终点,向前取一个完整窗口长度,统计所有子窗口的累计请求数。

数学推导:

  • 设窗口总长度 \( T \),子窗口数量 \( n \),每个子窗口长度 \( \Delta t = T / n \)。
  • 当前时间 \( t \) 对应的滑动窗口区间为 \( [t - T, t) \)。
  • 累计请求数:
C_slide(t) = Σ_{j=0}^{n-1} count(子窗口_{t - (j+1)Δt}^{t - jΔt})
  • 限流条件:\( C_slide(t) \leq N \)。
  • 精度与粒度:子窗口数 \( n \) 越大,滑动窗口越平滑,但内存开销线性增长。当 \( n \to \infty \) 时,滑动窗口趋近于精确的令牌桶。
对比维度 计数器算法 滑动窗口算法
窗口边界毛刺 严重(2N 瞬时流量) 轻微(取决于子窗口数)
内存复杂度 O(1) O(n)
适用场景 非关键路径、监控告警 交易接口、风控校验
滑动窗口算法 vs 计数器算法:窗口边界对比 计数器窗口 [0, 1s) 计数器窗口 [1s, 2s) 边界毛刺:2N 瞬时流量 边界毛刺:2N 瞬时流量 子窗口 × 5 滑动窗口总长度 T(当前时间回溯) 滑动窗口通过子窗口细粒度统计,消除边界毛刺;子窗口数 n 越大,精度越高,内存开销线性增长。

3.3 漏桶算法(Leaky Bucket Algorithm)

原理:将请求视为水滴,漏桶以恒定速率漏水(处理请求)。桶有最大容量,超出容量的请求被丢弃。漏桶强制输出速率恒定,适合保护下游系统。

数学推导:

  • 设桶容量 \( C \)(单位:请求数),漏水速率 \( r \)(单位:请求/秒)。
  • 当前桶内水量 \( W(t) \) 满足微分方程:
dW(t)/dt = λ(t) - r
  • 其中 \( λ(t) \) 为到达速率。限流条件:\( W(t) \leq C \)。
  • 稳态解:当 \( λ(t) > r \) 持续时,桶满后丢弃率为 \( (λ - r) / λ \)。
  • 关键特性:输出速率恒为 \( r \),与输入突发无关。但无法应对“瞬时高吞吐”需求(如缓存预热)。
金融实践建议:漏桶算法适用于对后端数据库、消息队列等有严格速率限制的场景。例如,风控评分接口要求下游数据库写入不超过 500 TPS,漏桶可保证输出绝对平滑。

3.4 令牌桶算法(Token Bucket Algorithm)

原理:系统以恒定速率向桶中放入令牌,每个请求消耗一个令牌。桶有最大令牌数上限。若桶中有令牌则放行,否则拒绝。令牌桶允许一定程度的突发流量。

数学推导:

  • 设桶容量 \( B \)(最大令牌数),令牌生成速率 \( r \)(令牌/秒)。
  • 当前令牌数 \( T(t) \) 满足:
dT(t)/dt = r - μ(t)
  • 其中 \( μ(t) \) 为消耗速率。限流条件:\( T(t) \geq 1 \) 时放行。
  • 突发能力:若桶初始满(\( T(0) = B \)),则允许瞬时突发 \( B \) 个请求,之后输出速率被限制为 \( r \)。
  • 稳态吞吐:长期平均速率不超过 \( r \),但短时峰值可达 \( B + r \cdot \Delta t \)。
特性 漏桶算法 令牌桶算法
输出速率 严格恒定 平均恒定,允许突发
突发处理 丢弃超出桶容量的请求 允许消耗累积令牌
适用场景 数据库写入、日志上报 API 网关、缓存读取
数学复杂度 低(线性微分方程) 中(带积分项)
漏桶算法 vs 令牌桶算法:结构对比 漏桶算法 桶容量 C r 恒定速率输出 令牌桶算法 桶容量 B T T T r 令牌/秒 请求消耗令牌 漏桶输出严格恒定;令牌桶允许突发(桶满时可一次性消耗 B 个令牌),更适合 API 网关场景。

3.5 算法选型对比总结

算法 输出平滑度 突发支持 内存开销 金融推荐场景
计数器 低(边界毛刺) 无 O(1) 非关键监控、日志采样
滑动窗口 中(取决于子窗口数) 有限 O(n) 交易接口、风控校验
漏桶 高(严格恒定) 无 O(1) 数据库写入、消息队列
令牌桶 高(平均恒定) 有(B 个突发) O(1) API 网关、缓存读取
选型陷阱:在金融压力测试中,切勿直接使用计数器算法做核心交易限流。滑动窗口和令牌桶是生产环境的主流选择。漏桶适合保护后端存储,但会引入额外延迟,需结合超时机制使用。