首页 > Technology > 正文

大模型 API 429 限流?这样重试最稳

fenij 2026-08-26 33 Technology

调用大模型 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 连接,减少握手开销和瞬时并发。
🗂️
缓存 Embedding
🚦
客户端限流
📦
合并请求
🔄
队列消费

常见问题

Q:429 和 503/529 处理方式一样吗?
A:都可以退避重试,但 429 通常有 Retry-After;503/529 是服务端过载,等待时间可能更长,建议设置上限并触发降级。

Q:指数退避最大等多久合适?
A:一般 30–60 秒。超过后建议抛异常或返回缓存/简化结果,不要无限阻塞用户。

Q:jitter 范围设多大?
A:通常 ±20% 足够打散峰值;如果客户端数量非常多,可以放宽到 ±50%。

你调大模型 API 时被 429 坑过吗?欢迎在 fenij.com 留言,分享你的重试策略。

相关完整手册

系统化的排查与配置思路,建议顺手收藏这几篇完整手册: