调用大模型 API 时,返回 429 并不代表服务挂了,而是你的请求频率或 token 消耗超过了服务商限制。直接立刻重试通常会继续失败,还会把账单和延迟一起拉高。正确做法是先看响应头,再用指数退避加抖动重试,同时从工程上降低调用频率。
为什么会触发 429
不同厂商的限流维度不一样,但常见指标不外乎三类:
- Requests Per Minute(RPM):单位时间请求数。比如 60 次/分钟,并发一高就触发。
- Tokens Per Minute(TPM):输出+输入 token 总量。长文本摘要或代码生成容易踩这条线。
- 并发数 / Burst:同一秒内的并发峰值。有些厂商会拒绝突发流量。
另外,429、503、529 含义不同:429 是限流;503 是服务器过载;529 是 Anthropic 侧过载。处理逻辑可以共用退避,但 429 通常带 Retry-After 头,优先级最高。
退避重试:不要硬等 1 秒
最朴素的方案是固定 1 秒重试 10 次,这在并发场景下基本无效。推荐指数退避 + 随机抖动(jitter),避免所有客户端在同一时刻再次冲击。
import random, time
def retry_with_backoff(func, max_retries=5, base_delay=1.0, max_delay=30.0):
for attempt in range(max_retries):
try:
return func()
except RateLimitError as e:
wait = min(base_delay * (2 ** attempt), max_delay)
wait = wait * (0.8 + random.random() * 0.4) # jitter ±20%
time.sleep(wait)
raise
如果响应头里有 Retry-After,优先用它;否则按指数退避计算。最大等待时间建议控制在 30–60 秒,超过后应抛异常或走降级逻辑,而不是无限等下去。
| 策略 | 优点 | 缺点 |
|---|---|---|
| 固定 1s 重试 | 实现简单 | 高并发下继续触发 429 |
| 指数退避 | 快速脱离限流窗口 | 多客户端可能同频重试 |
| 指数退避 + jitter | 打散重试峰值 | 代码多几行 |
踩坑实录:8 个线程把接口打挂了
我做过一个批量生成摘要的脚本,开了 8 个线程并发调 OpenAI。前 100 条速度很快,但从第 101 条开始连续 429。我以为是服务不稳定,就在 catch 里固定睡 1 秒再重试 10 次,结果不仅继续失败,延迟还从 2 秒涨到 20 秒,账单因为重试次数翻倍。后来改成指数退避 + 0.8–1.2 倍抖动,最大等待 30 秒,并把并发数降到 3,429 基本消失。这个坑说明:限流是服务端规则,客户端必须主动让路。
工程化降频手段
只靠重试是被动的, healthier 的做法是减少请求量:
- Token 桶 / 信号量:客户端主动限制 RPM,比如用
asyncio.Semaphore(3)或ratelimit库。 - 缓存 Embedding:同一批文本的向量结果用 Redis 缓存,避免重复调用。
- 合并请求:把多条短 prompt 合并成一次调用,提高单请求利用率。
- 任务队列:用 Celery、RQ 或 Bull 把请求排队,让 worker 按固定速率消费。
- 连接池复用:避免每次新建 TCP 连接,减少握手开销和瞬时并发。
常见问题
Q:429 和 503/529 处理方式一样吗?
A:都可以退避重试,但 429 通常有 Retry-After;503/529 是服务端过载,等待时间可能更长,建议设置上限并触发降级。
Q:指数退避最大等多久合适?
A:一般 30–60 秒。超过后建议抛异常或返回缓存/简化结果,不要无限阻塞用户。
Q:jitter 范围设多大?
A:通常 ±20% 足够打散峰值;如果客户端数量非常多,可以放宽到 ±50%。
你调大模型 API 时被 429 坑过吗?欢迎在 fenij.com 留言,分享你的重试策略。