4. 令牌桶算法详解:原理、参数配置与API网关实现
令牌桶算法(Token Bucket Algorithm)是API流量整形中最经典、应用最广泛的限流算法之一。它通过一个“桶”来平滑突发流量,同时保证长期平均速率不超过设定阈值,非常适合模型推理API这种既有突发请求、又需要控制整体调用量的场景。
4.1 核心原理
令牌桶算法的核心思想是:系统以恒定速率向一个容量有限的桶中放入令牌,每个请求到达时必须从桶中获取一个或多个令牌才能被处理;如果桶中令牌不足,请求将被排队等待或直接拒绝。
关键机制: 桶的容量决定了允许的突发请求量,令牌放入速率决定了长期平均请求速率。
4.2 关键参数配置
令牌桶算法有两个核心参数,它们共同决定了流量整形效果:
| 参数 | 符号 | 含义 | 配置示例 | 对模型API的影响 |
|---|---|---|---|---|
| 令牌放入速率 | r | 每秒向桶中放入的令牌数量,决定长期平均请求速率 | r = 100 令牌/秒 | 控制模型推理的长期QPS上限,防止过载 |
| 桶容量(突发容量) | b | 桶最多能容纳的令牌数,决定允许的突发请求量 | b = 200 令牌 | 允许短时间内的请求高峰,提升用户体验 |
参数配置黄金法则: 对于模型推理API,建议将 r 设置为模型单实例能稳定处理的QPS的80%,b 设置为 r 的1.5~3倍,以平衡稳定性和突发处理能力。
4.3 算法工作过程详解
令牌桶算法在每个时间步的执行逻辑可以用以下伪代码精确描述:
// 令牌桶状态
capacity = b // 桶容量
tokens = b // 当前令牌数,初始为满
last_time = now() // 上次更新时间
// 每次请求到达时执行
function allow_request():
// 1. 计算时间差,补充令牌
now = current_time()
elapsed = now - last_time
tokens = min(capacity, tokens + elapsed * r)
last_time = now
// 2. 判断是否放行
if tokens >= 1:
tokens = tokens - 1
return true // 放行
else:
return false // 拒绝或排队
4.4 在API网关中的实现架构
在实际的API网关(如Kong、APISIX、Nginx + Lua)中,令牌桶算法通常作为插件或中间件实现,部署在模型API的前端。以下是典型的实现架构:
4.5 主流网关的令牌桶实现对比
| 网关/框架 | 实现方式 | 存储后端 | 动态配置 | 适用场景 |
|---|---|---|---|---|
| Kong | rate-limiting 插件(支持令牌桶模式) | 本地内存 / Redis | 支持(通过Admin API) | 云原生、微服务架构 |
| APISIX | limit-req 插件(基于令牌桶) | 本地内存 / etcd | 支持(热加载) | 高性能、Kubernetes集成 |
| Nginx + Lua | lua-resty-limit-traffic 库 | 共享内存(lua_shared_dict) | 需自定义 | 自建网关、高度定制 |
| Spring Cloud Gateway | RequestRateLimiter 过滤器 | Redis(基于Reactive Redis) | 支持(通过配置中心) | Java技术栈、Spring生态 |
4.6 实际配置案例:模型推理API限流
假设你有一个基于GPT的文本生成模型API,单实例稳定处理能力为 50 QPS,允许短时突发到 100 QPS。以下是Kong网关的令牌桶配置示例:
# Kong 声明式配置 (declarative config)
_format_version: "3.0"
services:
- name: gpt-inference
host: model-internal.example.com
port: 8080
protocol: http
routes:
- name: generate-route
paths:
- /v1/generate
methods:
- POST
plugins:
- name: rate-limiting
config:
minute: 3000 # 每分钟3000次,对应50 QPS
hour: 180000
policy: local # 使用本地内存
fault_tolerant: true
limit_by: consumer # 按消费者限流
# 令牌桶模式参数(通过自定义插件或扩展实现)
# rate: 50 # 令牌放入速率 r = 50 令牌/秒
# burst: 100 # 桶容量 b = 100 令牌
⚠️ 常见陷阱: 不要将桶容量 b 设置得过大(如 b > 5r),否则会导致长时间的高突发流量压垮后端模型服务。对于模型推理场景,建议 b 不超过 r 的 3 倍,且必须配合后端熔断机制使用。
4.7 令牌桶 vs 漏桶算法
在流量整形领域,令牌桶常与漏桶算法(Leaky Bucket)对比。下表总结了二者的核心差异:
| 对比维度 | 令牌桶算法 | 漏桶算法 |
|---|---|---|
| 处理突发 | 允许突发(桶容量决定突发量) | 强制平滑,不允许突发 |
| 输出速率 | 平均速率 ≤ r,瞬时可高于 r | 恒定速率输出,严格等于配置值 |
| 适用场景 | 模型推理API(允许短时高峰) | 数据库写入、日志收集(需严格限速) |
| 实现复杂度 | 中等(需维护时间戳和令牌计数) | 较低(队列 + 固定速率消费) |
| 内存开销 | 低(仅存储令牌数和时间戳) | 高(需要请求队列) |
选型建议: 对于大多数模型推理API,令牌桶算法是首选。它既能保护后端模型不被过量请求压垮,又能容忍用户侧的合理突发,提供更好的交互体验。如果业务要求绝对的请求速率平滑(如计费系统),则考虑漏桶算法。