3. 令牌桶算法详解

令牌桶算法是限流领域最经典的算法之一,也是模型验证 API 网关中默认采用的限流策略。它通过一个“桶”来平滑突发流量,同时保证长期平均速率可控,非常适合大语言模型推理场景中请求到达时间不均匀、但单次推理耗时相对稳定的特点。

3.1 算法工作机制

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

令牌桶算法工作机制 令牌生成器 速率 r 个/秒 放入令牌 令牌桶 容量 = b T T T T T T T 溢出丢弃 用户请求 获取令牌 令牌 充足? 是 处理请求 否 拒绝/排队

算法执行流程分为三个关键步骤:

  1. 令牌填充:后台定时器以速率 r 向桶中添加令牌,桶满则丢弃多余令牌。
  2. 令牌消费:每个请求到达时,尝试从桶中取出 1 个令牌(或按权重取多个)。
  3. 准入判断:取到令牌则放行请求;取不到则拒绝或排队等待。
关键理解:令牌桶允许短时间内的突发流量——只要桶中还有累积的令牌,就可以一次性处理多个请求。这是它与漏桶算法最本质的区别。

3.2 参数配置详解

令牌桶有两个核心参数,直接决定了限流效果:

参数 符号 含义 配置建议
速率(rate) r 令牌生成的速率,单位:个/秒 根据模型推理 QPS 上限设定,通常取峰值负载的 80%
容量(capacity) b 桶中最多可存放的令牌数 根据允许的突发请求量设定,一般取 r × 1~5 秒

3.2.1 速率(r)的确定

速率决定了系统的长期平均吞吐量。对于模型验证 API,建议按以下方式计算:

  • 基准值:单实例模型推理的最大 QPS × 实例数 × 0.8(预留 20% 安全余量)。
  • 动态调整:结合模型推理耗时波动,建议设置速率不超过实测峰值的 85%。
  • 多模型场景:不同模型应独立配置速率,避免轻量模型被重型模型拖累。

3.2.2 容量(b)的确定

容量决定了突发容忍度。容量越大,系统能承受的瞬时流量尖峰越高:

  • 保守配置:b = r × 1(仅允许 1 秒的突发,适合对延迟敏感的在线服务)。
  • 均衡配置:b = r × 3(允许 3 秒突发,适合大多数模型验证场景)。
  • 宽松配置:b = r × 5~10(允许较大突发,适合批量推理或离线验证)。
注意:容量设置过大可能导致后端模型服务瞬间过载。建议结合模型推理的并发上限(max_concurrency)来约束 b 的最大值,避免令牌桶“过度承诺”。

3.3 适用场景

令牌桶算法在模型验证 API 中有以下典型应用场景:

场景 推荐度 说明
在线推理 API(实时) ★★★★★ 能平滑突发流量,同时保证响应延迟稳定
批量验证任务 ★★★★☆ 允许短时间提交大量请求,但需控制桶容量防止后端过载
多租户隔离 ★★★★★ 每个租户独立令牌桶,互不影响
模型 A/B 测试 ★★★★☆ 不同模型版本分配不同速率,控制流量比例
严格匀速请求 ★★☆☆☆ 需要严格匀速的场景建议使用漏桶算法

3.4 优缺点分析

令牌桶算法优缺点对比 ✅ 优点 支持突发流量,不浪费空闲资源 长期平均速率可控,符合 SLA 要求 实现简单,性能开销极低(O(1)) 可灵活调整速率和容量,适应性强 支持按权重消费(多令牌/请求) ❌ 缺点 无法保证严格匀速,突发可能冲击后端 参数(r, b)需要根据业务调优 长时间空闲后突发可能超过后端承载 分布式场景需协调令牌同步 内存占用随桶容量线性增长(可忽略)

3.5 代码实现示例

以下是一个轻量级令牌桶的 Python 实现,可直接用于模型验证 API 的限流中间件:

import time
import threading

class TokenBucket:
    def __init__(self, rate: float, capacity: int):
        """
        :param rate: 令牌生成速率(个/秒)
        :param capacity: 桶容量(最大令牌数)
        """
        self.rate = rate
        self.capacity = capacity
        self.tokens = capacity  # 初始装满
        self.last_refill = time.monotonic()
        self.lock = threading.Lock()

    def _refill(self):
        """按时间间隔补充令牌"""
        now = time.monotonic()
        elapsed = now - self.last_refill
        new_tokens = elapsed * self.rate
        self.tokens = min(self.capacity, self.tokens + new_tokens)
        self.last_refill = now

    def consume(self, tokens: int = 1) -> bool:
        """
        尝试消费 tokens 个令牌
        :return: True 表示允许请求,False 表示限流
        """
        with self.lock:
            self._refill()
            if self.tokens >= tokens:
                self.tokens -= tokens
                return True
            return False

# 使用示例:速率 10 QPS,桶容量 30(允许 3 秒突发)
bucket = TokenBucket(rate=10, capacity=30)

# 模拟请求
for i in range(50):
    if bucket.consume():
        print(f"请求 {i+1}: 通过")
    else:
        print(f"请求 {i+1}: 限流")
    time.sleep(0.05)  # 模拟请求间隔
生产环境建议:上述实现适用于单进程场景。在分布式部署中,建议使用 Redis 或 etcd 实现分布式令牌桶,确保多实例间的令牌同步。

3.6 参数配置速查表

根据不同的模型验证 API 场景,推荐以下参数配置模板:

场景类型 速率 r(QPS) 容量 b 突发容忍时间 适用模型
高并发在线推理 100 100 1 秒 轻量模型(如 BERT-base)
中等负载验证 20 60 3 秒 中等模型(如 GPT-2)
批量离线验证 5 50 10 秒 大模型(如 LLaMA-70B)
多租户隔离(每租户) 10 30 3 秒 混合模型
调优口诀:速率看平均,容量看突发;先保守后放宽,监控反馈再调整。建议上线初期将容量设为速率的 1~2 倍,观察后端负载和延迟曲线后再逐步放宽。