公开笔记

Agent 分布式

覆盖 LLM 厂商 RPM + TPM 双重限流规则、令牌桶/漏桶/滑动窗口三大限流算法、分布式 Redis + Lua 限流实现,详解限流核心逻辑、开发实践建议、算法对比选型,包含429错误处理、自适应限流等落地优化方案。

发布于 更新于

流量控制

LLM API 限流

LLM 厂商(如 OpenAI、Anthropic、Google、Groq 等)为了保护后端模型服务器不被打崩,几乎都采用了双重限流策略

  • RPM(Requests Per Minute):每分钟最多允许发起多少个请求(Request)
  • TPM(Tokens Per Minute):每分钟总共允许处理的 Token 数量(输入 + 输出 Token 之和)

即使你并发开 100 个请求,只要总 Token 数或请求数超过限制,就会立刻返回 HTTP 429 Too Many Requests,并且账户可能被短暂封禁(几秒到几分钟不等,严重时会更久)。

为什么需要同时限制 RPM 和 TPM?

  1. RPM 主要防止“请求风暴”(短时间内疯狂发很多小请求)。
  2. TPM 主要防止“Token 消耗爆炸”(一个请求输入 + 输出就几十万 Token,把模型算力吃光)。

两者必须同时控制,否则很容易被绕过。

常用的两种限流算法实现

1. 令牌桶算法(Token Bucket) —— 最常用、最推荐

核心思想

  • 系统有一个“桶”,里面预先装满了令牌(token)。
  • 每发起一个请求,就从桶里拿走相应数量的令牌。
  • 令牌会按照固定速率(rate)持续生成(比如每分钟生成 10,000 个 TPM 令牌)。
  • 如果桶里没有足够的令牌,就拒绝请求(429)。

优点

  • 允许一定的突发流量(桶满了可以一次性消费)。
  • 实现简单,性能高。
  • 天然支持 RPM + TPM 双重限流(用两个桶分别控制)。

实际实现方式

  • 单机:用一个带时间衰减的计数器 + 原子操作。
  • 分布式:推荐用 Redis + Lua 脚本 实现原子性。

伪代码示例(Redis 令牌桶)

-- Lua 脚本(原子执行)
local rpm_key = KEYS[1]
local tpm_key = KEYS[2]
local now = tonumber(ARGV[1])
local rpm_limit = tonumber(ARGV[2])
local tpm_limit = tonumber(ARGV[3])
local request_tokens = tonumber(ARGV[4])   -- 本次请求预计消耗的 token 数

-- RPM 检查(每个请求消耗 1 个)
local rpm_tokens = redis.call('GET', rpm_key) or rpm_limit
if rpm_tokens < 1 then
    return 0  -- RPM 超限
end

-- TPM 检查
local tpm_tokens = redis.call('GET', tpm_key) or tpm_limit
if tpm_tokens < request_tokens then
    return 0  -- TPM 超限
end

-- 扣减
redis.call('DECR', rpm_key)
redis.call('DECRBY', tpm_key, request_tokens)

-- 设置过期时间(通常 60 秒)
redis.call('EXPIRE', rpm_key, 60)
redis.call('EXPIRE', tpm_key, 60)

return 1  -- 允许通过

实际生产中会配合令牌自动补充逻辑(用当前时间计算应该补充多少令牌)。

2. 漏桶算法(Leaky Bucket)

核心思想

  • 把所有请求放入一个“漏桶”中。
  • 漏桶以固定速率向外漏水(处理请求)。
  • 如果桶满了,后续请求直接被丢弃或排队。

特点

  • 流量输出非常平滑,适合对后端压力要求极稳定的场景。
  • 不允许突发,即使有令牌也不能一次性消费太多。
  • 在 LLM 场景中使用较少(因为 OpenAI 本身允许一定突发)。

3. 滑动窗口算法(Sliding Window) —— 精度最高

核心思想

  • 记录最近 60 秒内每一个请求发生的时间和消耗的 Token 数。
  • 每次请求时,计算当前窗口内(过去 60 秒)的总请求数和总 Token 数是否超过限制。

优点

  • 限流最精确,不会出现令牌桶那种“窗口交界处突然放行大量请求”的问题。
  • 符合大多数 LLM 厂商实际的限流逻辑(他们通常就是滑动窗口或近似滑动窗口)。

缺点

  • 内存消耗较高(需要存很多时间戳 + token 数)。
  • 需要用 Redis + Lua 或 RedisTimeSeries 等实现高性能。

推荐实现(分布式高精度):

  • Redis Sorted Set(zset)存储请求时间戳 + token 消耗。
  • 每次请求时:
    1. 清理窗口外(60 秒前)的数据。
    2. 计算当前窗口内总请求数和总 Token 数。
    3. 判断是否超限。
    4. 插入当前请求记录。

实际开发建议

  1. 同时维护两个限流器
    • 一个 RPM 限流器(每个请求消耗 1)
    • 一个 TPM 限流器(根据 prompt_tokens + max_tokens 或实际返回的 total_tokens 动态扣)
  2. 预估 Token 消耗
    • 请求前用 tiktoken / anthropic tokenizer 估算输入 Token。
    • 请求后用 API 返回的 usage 字段精确扣除输出 Token。
  3. 指数退避 + 重试(Exponential Backoff)
    • 遇到 429 时,等待 retry-after 头指定的时间,或用 1s → 2s → 4s → … 退避。
  4. 分布式环境强烈推荐 Redis + Lua
    • 保证原子性,避免竞态条件。
    • 支持多实例、多进程共享限流。
  5. 更高级做法
    • 结合 自适应限流:根据当前剩余配额动态调整并发度。
    • 使用 优先级队列:重要请求走高优先级桶。
    • 多账户轮询(多 Key 负载均衡)时,每个账户独立做令牌桶。

总结

算法实现难度突发流量支持精度推荐场景分布式支持
令牌桶优秀大多数 LLM 应用(推荐)优秀
漏桶需要极致平滑流量良好
滑动窗口一般最高对精度要求极高场景优秀
← 返回 Notes

Contact

Contact Me

Leave a message here. The form sends directly from the browser to a form delivery service and then to my email.

Messages are delivered to lzx744008464@gmail.com.