HTTP 429 是 LLM API 里信息量最低的报错:三种本质完全不同的故障共用这一个状态码,而三种故障的修法各不相同。重试代码只能修其中一种;对第二种,重试只是推迟必然的失败;对第三种,重试纯属浪费。这篇是错误码速查页背后的 429 深挖:怎么分辨你撞上的是哪一种,唯一值得重试的那种该怎么写生产级重试,以及让 429 从源头减少的结构性改法。
一个状态码,三种故障
| 实际发生了什么 | 限制在哪一层 | 修法 |
|---|---|---|
| 上游供应商限流(RPM/TPM 超限) | 模型厂商的基础设施 | 有上限的指数退避 + 抖动;有 Retry-After 就照做 |
| 你自己给 Key 配的限额 | 你的 Key 设置(rateLimit / tokenLimitTpm) | 改仪表盘设置——重试代码无济于事 |
OpenAI 的 insufficient_quota | 官方 OpenAI API 的账单账户 | 解决账单问题;重试毫无意义 |
误判方向的代价是双向的。把自己配的 Key 限额当上游问题,你会写出一套对着永远不动的天花板反复冲撞的重试逻辑;把 insufficient_quota 当瞬时限流,你会对着一个没有余额的账户白白重试几个小时。
成因一:上游供应商在限流
模型厂商都有每分钟请求数和每分钟 token 数的上限,负载高峰时还会主动用 429(以及 Anthropic 风格的 529 过载响应)甩流量——哪怕你自己的请求频率没变。这是唯一一种客户端重试算对症下药的情况,但必须是有边界的重试,三个要素缺一不可:
- 带上限的指数退避。 每次失败等待时间翻倍,但要有封顶——不封顶的话,第五次重试可能为一个几秒就恢复的限流干等几分钟。
- 抖动(jitter)。 每次延迟加随机量。没有抖动,同一批失败的客户端会在同一时刻集体重试,同步的重试波会再次触发限流。
- 放弃路径。 固定次数之后把错误抛出去。无限循环不是韧性,是一队越攒越陈旧的任务。
响应里带 Retry-After 头时照做——服务端已经明确告诉你容量什么时候恢复,这时还用退避去猜只会拖长故障时间。下面是 Python 版本,用标准 OpenAI 客户端指向 Router One:
import os
import random
import time
from openai import OpenAI, RateLimitError
client = OpenAI(
base_url="https://api.router.one/v1",
api_key=os.environ["ROUTER_ONE_API_KEY"],
)
MAX_ATTEMPTS = 5
BASE_DELAY = 1.0 # 秒
MAX_DELAY = 30.0 # 封顶:最长等这么久
def chat_with_backoff(**kwargs):
for attempt in range(1, MAX_ATTEMPTS + 1):
try:
return client.chat.completions.create(**kwargs)
except RateLimitError as err:
if attempt == MAX_ATTEMPTS:
raise # 放弃路径:抛出错误,不要无限循环
retry_after = err.response.headers.get("retry-after")
if retry_after is not None:
# 服务端已明确说了容量何时恢复——不需要抖动
time.sleep(min(float(retry_after), MAX_DELAY))
else:
delay = min(BASE_DELAY * 2 ** (attempt - 1), MAX_DELAY)
# full jitter:把同步失败的客户端错开
time.sleep(random.uniform(0, delay))
response = chat_with_backoff(
model="gpt-5.5",
messages=[{"role": "user", "content": "ping"}],
)
五次尝试、30 秒封顶、带抖动的延迟、尊重 Retry-After、最后干净地失败。整个模式就这些——忍住别把它写得更"聪明"。
成因二:撞上了你自己配的限额
Router One 的 API Key 带可选的 key 级控制:rateLimit(请求频率)和 tokenLimitTpm(每分钟 token 数),外加 maxSpend 消费硬上限。它们存在的意义是让泄漏的 key 或失控的循环抽不干你的钱包——每客户一把 key 的转售模式重度依赖这套控制。但你六周前设的限额,在撞上它的客户端看来和上游 429 一模一样。
识别特征:这类 429 是确定性的。流量一过配置的阈值就开始报,而且不管几点、不管调哪个模型,每次都在同一个请求频率上复现。退避参数调得再精细也改变不了天花板——修法在 Dashboard → API Keys,调高或移除那把 key 的限额。(如果 key 停摆时报的是 402,那是 maxSpend 上限或钱包余额的问题——钱的事,不是速度的事,速查页写了两者的区别。)
调高限额之前先问一句:为什么会撞上?如果是某个后台任务把生产流量共用的 key 打满了,更好的修法是再开一把带独立限额的 key,把吵闹的负载隔离出去、单独可见。
成因三:insufficient_quota——套着 429 外衣的账单问题
如果你直连官方 OpenAI API,错误体里写着 insufficient_quota,请立刻停止重试。状态码虽然是 429,但它跟限流毫无关系:你的预付账单额度用光了,这个"限制"不会在几秒、几分钟后重置——账单不动它永远不动。每一次重试都是白打的请求,退避只是让你浪费得慢一点。
这个报错来自 OpenAI 平台,不是网关。insufficient_quota 修复指南完整讲了成因和解决路径,包括绑了支付方式但额度依然为零的情况。
怎么判断撞上的是哪一种
靠客户端症状猜不可靠,请求 trace 才可靠。在 Router One 上,Dashboard → Logs 展示每个请求的最终记录——状态、模型、Token、花费、延迟与路由决策——可按模型和时间范围过滤。诊断归结为三步:
- 先读错误体。
insufficient_quota会自报家门。看到这个字符串就是账单问题——走成因三。 - 再查 Key 配置。 如果出错的 key 设了
rateLimit或tokenLimitTpm,把配置的阈值和日志里的实际请求频率对一下。对上了就是成因二,修法是改设置。 - 最后看模式。 上游限流(成因一)是突发的、和负载高峰相关——429 集中在特定时间窗,然后自行消散。自己配的限额则是平直、可预测的。
同一份每请求记录还能回答后续问题——那段故障窗口的重试白烧了多少 token——这就进入成本追踪的范畴了。
重试风暴:天真的重试如何把限流越搞越糟
限流是系统满载的信号。天真的响应——立刻重试、无限重试——把这个信号变成了放大器:每个被拒的请求变成两个,再变成四个,总到达率恰好在系统最需要降压的时刻不断爬升。这就是重试风暴,它能把服务在原始高峰早已过去之后继续按在水下。有上限的尝试次数和抖动(见上)是重试侧的防御。
结构性的修法是并发整形:用信号量或固定 worker 池限制同时在途的请求数,而不是每条任务起一个并发、让退避去收拾残局。8 个 worker 消化 10,000 条任务的队列,产生的是一条平稳可预测、天然低于限流线的请求速率;同样 10,000 条任务一次性并发发出,得到的是一堵 429 墙加一波同步重试潮。退避是安全带,并发上限才是把车速控制在能活命的区间。
网关故障转移在哪一层帮上忙
走 Router One 调用,在错误到达你之前多了一层:符合条件的 429 或 529,在存在另一个提供同一请求模型的健康供应商时,可以在那条路由上重试。限定词很重要——并非每个 429 都可重试(你自己配的 key 限额、insufficient_quota 这类账单故障都不在候选之列),故障转移也不是零停机保证:没有兼容路由完成请求时,应用仍会收到错误。所以客户端的退避循环无论如何都要保留;故障转移的判定逻辑另有专页。
至于 Router One 自己的限流口径:正常付费用量没有低固定上限——但滥用防护、账户级保护限制和上游约束仍然存在。如果正当的负载撞上了天花板,那是找支持团队聊的事,不是架构问题。
常见问题
怎么判断 429 来自我的 Key 限额还是上游供应商? 查 Dashboard → Logs 的请求 trace 和 Key 配置。如果出错的 key 设了 rateLimit 或 tokenLimitTpm,且日志里的请求频率正好对上阈值,那就是你自己配的限额——去仪表盘改。上游 429 是突发的、跟负载高峰相关、会自行消散;自己配的限额则在同一请求频率上确定性地复现。
每个 429 都应该重试吗? 不。只有限制真的会重置的情况——上游限流——才值得用有上限的指数退避加抖动去重试。自己给 key 配的 rateLimit / tokenLimitTpm 触发的 429,设置不改就不会消失;OpenAI 的 insufficient_quota 是账单额度耗尽,任何重试节奏都填不回余额。
Router One 会对我的请求加自己的限流吗? 正常付费用量没有低固定上限。滥用防护、账户级保护限制和上游供应商约束仍可能生效。key 级的 rateLimit 和 tokenLimitTpm 是可选控制项——只对你主动配置过的 key 生效。
只做指数退避够吗? 必要但不结构性。退避处理的是单个失败请求,拦不住一个无界批量任务从源头制造 429。用信号量或固定 worker 池限制在途并发,让总请求速率从构造上就低于限流线,退避留作安全网。
网关故障转移能让 429 彻底消失吗? 不能。符合条件的 429 或 529 在存在另一个提供同一请求模型的健康供应商时可以重试,这能吸收相当一部分上游限流事件——但并非每个 429 都可重试,这也不是零停机保证。应用仍应准备好用有上限的退避处理最终返回的 429。
排查清单
429 出现时:先读错误体(insufficient_quota 等于账单问题),再查 key 配置的限额(仪表盘一改就好),最后才当作真正的上游限流处理——有上限的退避加抖动、尊重 Retry-After、并发封顶。其余状态码见错误码速查页,Key 配置的完整说明见 API 文档。换一个 base URL,你就有了让诊断从猜测变成查表的请求 trace。