3. 令牌桶算法详解
令牌桶算法是限流领域最经典的算法之一,也是模型验证 API 网关中默认采用的限流策略。它通过一个“桶”来平滑突发流量,同时保证长期平均速率可控,非常适合大语言模型推理场景中请求到达时间不均匀、但单次推理耗时相对稳定的特点。
3.1 算法工作机制
令牌桶的核心思想是:系统以固定速率向桶中放入令牌,每个请求必须从桶中获取一个(或多个)令牌才能被处理。如果桶中令牌不足,请求被拒绝或排队等待。
算法执行流程分为三个关键步骤:
- 令牌填充:后台定时器以速率 r 向桶中添加令牌,桶满则丢弃多余令牌。
- 令牌消费:每个请求到达时,尝试从桶中取出 1 个令牌(或按权重取多个)。
- 准入判断:取到令牌则放行请求;取不到则拒绝或排队等待。
关键理解:令牌桶允许短时间内的突发流量——只要桶中还有累积的令牌,就可以一次性处理多个请求。这是它与漏桶算法最本质的区别。
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 优缺点分析
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 倍,观察后端负载和延迟曲线后再逐步放宽。