4. 常见限流算法(上):令牌桶算法原理、实现与适用场景

在模型风险管理系统中,API 网关需要应对突发流量、保护下游模型推理服务不被击穿。令牌桶算法(Token Bucket)是业界应用最广泛的限流算法之一,它允许一定程度的突发流量,同时保证长期平均速率可控,非常适合模型推理 API 的流量整形场景。

4.1 核心原理

令牌桶算法的基本思想是:系统以恒定速率向一个桶中放入令牌,每个请求必须从桶中获取一个令牌才能被处理。如果桶中令牌不足,请求被拒绝或排队等待。

关键参数:
  • 桶容量(bucket_capacity):桶最多能存放的令牌数,决定了突发流量的上限。
  • 令牌填充速率(refill_rate):每秒向桶中添加的令牌数量,决定了长期平均请求速率。
  • 当前令牌数(current_tokens):桶中实时可用的令牌数量。
令牌桶算法原理流程 令牌生成器 恒定速率 r 个/秒 令牌桶 容量 = C 当前令牌数 = 9 API 请求 有令牌? 放行处理 拒绝/排队

4.2 算法实现

令牌桶的实现通常有两种方式:定时填充和延迟计算。在高并发场景下,延迟计算方式性能更优,因为它避免了定时器的开销。

4.2.1 延迟计算实现(推荐)

延迟计算的核心思想是:在每次请求到达时,根据时间差计算应补充的令牌数,而不是依赖后台定时任务。

import time

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        """
        :param capacity: 桶容量(最大突发令牌数)
        :param refill_rate: 令牌填充速率(个/秒)
        """
        self.capacity = capacity
        self.refill_rate = refill_rate
        self.current_tokens = capacity  # 初始满桶
        self.last_refill_time = time.time()

    def allow_request(self, tokens=1):
        """
        判断是否允许请求通过
        :param tokens: 本次请求消耗的令牌数
        :return: True 表示放行,False 表示拒绝
        """
        now = time.time()
        # 计算从上一次填充到现在应补充的令牌数
        elapsed = now - self.last_refill_time
        refill_tokens = elapsed * self.refill_rate
        self.current_tokens = min(self.capacity, self.current_tokens + refill_tokens)
        self.last_refill_time = now

        # 判断是否足够令牌
        if self.current_tokens >= tokens:
            self.current_tokens -= tokens
            return True
        else:
            return False
实现要点:
  • 原子性:在多线程/协程环境下,current_tokens 和 last_refill_time 的读写需要加锁或使用原子操作。
  • 精度控制:time.time() 精度可能不够,生产环境建议使用 time.monotonic() 或高性能时钟。
  • 令牌消耗:模型推理请求的“成本”可能不同(如大模型请求消耗 2 个令牌,小模型消耗 1 个),可通过 tokens 参数实现差异化限流。

4.2.2 参数配置对比

参数 含义 模型风险场景示例
capacity 桶容量,决定最大突发请求数 模型上线初期,允许 100 个请求同时涌入进行压测
refill_rate 令牌填充速率,决定稳态 QPS 模型推理服务每秒最多处理 50 个请求
tokens 单次请求消耗令牌数 高风险模型(如信用评分)消耗 2 个令牌,低风险模型消耗 1 个

4.3 适用场景分析

令牌桶算法在模型风险管理中有三个典型应用场景:

  1. 模型推理 API 突发保护:允许客户端在短时间内突发请求(如批量数据回测),但长期平均速率受控,避免模型服务过载。
  2. 多模型共享网关:不同模型分配不同的桶容量和填充速率,实现租户级别的流量隔离。
  3. 模型版本切换流量控制:新版本上线时,通过调整令牌桶参数逐步放量,从 10% 流量平滑过渡到 100%。
避坑提示:
  • 桶容量设置过大:如果 capacity 远大于 refill_rate,突发流量可能瞬间打垮后端模型服务。建议 capacity = refill_rate × 允许突发秒数。
  • 时钟回拨问题:服务器时钟回拨会导致令牌桶瞬间被填满,引发流量洪峰。生产环境应使用单调递增时钟。
  • 分布式一致性:在分布式网关中,每个节点独立维护令牌桶会导致整体限流不准确。建议使用 Redis 等中心化存储实现分布式令牌桶。

4.4 与漏桶算法的对比

令牌桶和漏桶是两种最基础的限流算法,理解它们的差异有助于在模型风险管理中选择合适的方案。

对比维度 令牌桶算法 漏桶算法
突发处理 允许突发,桶容量越大突发能力越强 严格平滑,不允许突发
实现复杂度 中等,需维护时间戳和令牌计数 简单,队列 + 固定速率消费
适用场景 需要容忍突发流量的模型推理 API 流量必须严格平滑的场景(如数据库写入)
内存开销 低,仅需几个变量 中等,需要维护请求队列
令牌桶 vs 漏桶:流量整形效果对比 令牌桶(允许突发) 输入流量 令牌桶 输出流量 突发输入 平滑输出(有突发) 漏桶(严格平滑) 输入流量 漏桶 输出流量 突发输入 严格平滑输出

4.5 模型风险场景实战建议

  • 初始配置:建议 capacity = refill_rate × 2,允许 2 秒的突发流量,同时保证稳态速率。
  • 动态调整:结合模型监控指标(如推理延迟、内存使用率),当模型服务压力增大时自动降低 refill_rate。
  • 预热策略:模型刚启动时,令牌桶从空桶开始,避免冷启动阶段被限流。可设置初始令牌数为 capacity × 0.5。