3. API限流基础概念:四大算法原理与对比

在催收触达系统中,API限流是防止上游渠道(短信、语音、邮件)被瞬间打爆、保障系统稳定性的第一道防线。本章深入剖析四种经典限流算法的核心原理、适用场景及在催收场景中的选型建议。

3.1 计数器算法(Counter Algorithm)

计数器算法是最简单直观的限流实现。它将时间划分为固定窗口(如1秒),在每个窗口内维护一个计数器,当请求到达时计数器加1;若计数器超过阈值,则拒绝后续请求;窗口结束时计数器归零。

核心特征: 实现简单,内存占用极低,但存在“窗口临界突变”问题——在窗口切换瞬间可能涌入两倍于阈值的请求。
// 伪代码示例:计数器限流
class CounterLimiter:
    def __init__(self, limit=100, window_ms=1000):
        self.limit = limit          # 窗口内最大请求数
        self.window_ms = window_ms  # 窗口大小(毫秒)
        self.counter = 0
        self.window_start = now()

    def allow(self):
        if now() - self.window_start >= self.window_ms:
            self.counter = 0
            self.window_start = now()
        self.counter += 1
        return self.counter <= self.limit

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

滑动窗口算法是对计数器算法的改进。它将时间窗口细分为多个小格(如1秒窗口分为10个100ms小格),每个小格独立计数。窗口随着时间平滑滑动,丢弃过期小格,统计当前窗口内所有小格的累计值。

催收场景优势: 滑动窗口能有效抑制计数器算法的“窗口边界突发”问题,使限流曲线更平滑,适合对突发流量敏感的催收渠道(如语音外呼)。
// 伪代码示例:滑动窗口限流
class SlidingWindowLimiter:
    def __init__(self, limit=100, window_ms=1000, slots=10):
        self.limit = limit
        self.window_ms = window_ms
        self.slot_ms = window_ms // slots
        self.slots = [0] * slots
        self.current_slot = 0

    def allow(self):
        self._advance_window()
        total = sum(self.slots)
        if total >= self.limit:
            return False
        self.slots[self.current_slot] += 1
        return True

    def _advance_window(self):
        now_slot = (now() // self.slot_ms) % len(self.slots)
        while self.current_slot != now_slot:
            self.current_slot = (self.current_slot + 1) % len(self.slots)
            self.slots[self.current_slot] = 0

3.3 漏桶算法(Leaky Bucket Algorithm)

漏桶算法将请求视为水滴,以恒定速率从桶底漏出(处理请求),桶容量有限。当请求注入速度超过漏出速度时,桶中积水增加;若桶已满,则新请求被丢弃。漏桶强制输出为平滑的恒定速率。

注意: 漏桶算法对突发流量不友好——即使渠道空闲,也无法加速处理积压请求。在催收场景中,若渠道支持短时突发(如短信网关),漏桶可能造成资源浪费。
// 伪代码示例:漏桶限流
class LeakyBucketLimiter:
    def __init__(self, capacity=100, leak_rate=10):
        self.capacity = capacity      # 桶容量
        self.leak_rate = leak_rate    # 每秒漏出速率
        self.water = 0                # 当前水量
        self.last_leak = now()

    def allow(self):
        self._leak()
        if self.water >= self.capacity:
            return False
        self.water += 1
        return True

    def _leak(self):
        elapsed = now() - self.last_leak
        self.water = max(0, self.water - self.leak_rate * elapsed)
        self.last_leak = now()

3.4 令牌桶算法(Token Bucket Algorithm)

令牌桶算法以恒定速率向桶中放入令牌,每个请求需消耗一个令牌才能通过。桶有最大容量限制。与漏桶不同,令牌桶允许一定程度的突发——当桶中积累足够令牌时,可一次性处理多个请求。

催收场景推荐: 令牌桶算法是催收触达系统中最常用的限流方案。它既能平滑长期流量,又能容忍短时突发,完美匹配“催收高峰期批量发送”的业务特征。
// 伪代码示例:令牌桶限流
class TokenBucketLimiter:
    def __init__(self, capacity=100, refill_rate=10):
        self.capacity = capacity        # 桶容量
        self.refill_rate = refill_rate  # 每秒令牌补充速率
        self.tokens = capacity          # 当前令牌数
        self.last_refill = now()

    def allow(self):
        self._refill()
        if self.tokens < 1:
            return False
        self.tokens -= 1
        return True

    def _refill(self):
        elapsed = now() - self.last_refill
        self.tokens = min(self.capacity, self.tokens + self.refill_rate * elapsed)
        self.last_refill = now()
四大限流算法核心原理对比 计数器算法 固定时间窗口 计数器累加 超阈值则拒绝 窗口结束归零 ⚠ 窗口临界突变 可能2倍流量 滑动窗口算法 窗口细分为小格 每个小格独立计数 窗口随时间滑动 丢弃过期小格 ✅ 平滑限流 消除窗口突变 漏桶算法 恒定速率漏出 桶满则丢弃 强制平滑输出 不支持突发 ⚠ 无法利用空闲 资源浪费风险 令牌桶算法 恒定速率放令牌 请求消耗令牌 支持短时突发 桶容量限制 ✅ 平滑+突发 催收场景首选 算法选型总结 • 计数器:实现最简单,适合对精度要求不高的场景(如日调用总量限制) • 滑动窗口:精度较高,适合需要平滑限流的场景(如语音外呼并发控制) • 漏桶:强制恒定速率,适合下游处理能力固定的场景(如邮件发送队列) • 令牌桶:兼顾平滑与突发,是催收触达系统最推荐的限流方案 💡 生产环境建议:令牌桶 + 滑动窗口组合使用,令牌桶控制平均速率,滑动窗口防止窗口边界突发

3.5 四大算法综合对比

对比维度 计数器 滑动窗口 漏桶 令牌桶
实现复杂度 极低 低 中 中
内存占用 O(1) O(n)(n为小格数) O(1) O(1)
突发处理能力 差(窗口边界突发) 较好 无(强制平滑) 好(允许短时突发)
流量平滑度 差 中 极好(恒定速率) 好(长期平滑)
适用催收场景 日调用总量限制 语音并发控制 邮件发送队列 短信/语音/邮件通用
典型阈值设置 1000次/秒 100次/秒(10小格) 容量200,漏率50/秒 容量200,填充50/秒

3.6 催收场景选型建议

  • 短信渠道: 推荐令牌桶算法。短信网关通常支持短时批量提交,令牌桶的突发能力可充分利用渠道吞吐量。
  • 语音外呼: 推荐滑动窗口算法。语音通道对并发敏感,滑动窗口能精确控制每秒并发数,避免运营商限呼。
  • 邮件发送: 推荐漏桶算法。邮件服务商通常有严格的每秒发送上限,漏桶的恒定速率特性可避免被判定为垃圾邮件。
  • 日总量控制: 推荐计数器算法。实现简单,只需在Redis中维护一个Key,过期时间设为24小时即可。
生产环境最佳实践: 催收触达系统通常采用“双层限流”架构——第一层使用令牌桶控制平均速率,第二层使用滑动窗口防止窗口边界突发。两层配合可实现“长期平滑、短期可控”的理想限流效果。
催收场景限流算法选型决策树 催收触达请求 需要突发能力? 需要严格平滑? 令牌桶算法 短信/语音通用 滑动窗口 语音并发控制 漏桶算法 邮件发送队列 计数器算法 日总量限制 💡 决策依据:根据渠道特性(是否支持突发、平滑要求、并发限制)选择最匹配的算法

3.7 本章小结

  • 计数器算法: 实现最简单,但存在窗口边界突发问题,适合粗粒度限制。
  • 滑动窗口算法: 通过细粒度分格消除边界突变,适合精确并发控制。
  • 漏桶算法: 强制恒定输出速率,适合下游处理能力固定的场景。
  • 令牌桶算法: 兼顾平滑与突发,是催收触达系统最推荐的限流方案。
  • 生产建议: 采用“令牌桶 + 滑动窗口”双层架构,实现长期平滑、短期可控的限流效果。