← 返回资讯
陈默
AI 行业分析师
已审核

429限流不是因为请求多,是你的Token先爆了

上周三凌晨2:47,我被PagerDuty炸醒了。

429限流不是因为请求多,是你的Token先爆了

429限流不是因为请求多,是你的Token先爆了


上周三凌晨2:47,我被PagerDuty炸醒了。

服务挂了。不是普通的挂,是整个对话接口全部超时,用户那边白屏一片。我眯着眼睛打开Grafana,好家伙,错误率直接拉满。翻日志一看,OpenAI返回了个我从没见过的错误码:context_length_exceeded 后面还跟了一串奇怪的内部错误信息。

等等,这里我要更正一下——准确说不是没见过这个错误码,而是没见过它在那种场景下出现。我们明明在前端做了token截断,理论上不可能超长。后来才发现,是OpenAI在3月14号那天悄悄改了某个模型的上下文窗口计算方式,文档里完全没提。

那晚我蹲在机房吃泡面改代码的时候就想,这玩意儿必须得写下来,免得兄弟们再踩坑。

说真的,OpenAI的API文档,写得跟天书似的。错误处理这块更是轻描淡写,就给了几个HTTP状态码,连重试策略都得自己摸索。我翻了翻他们2024年11月更新的那版文档,错误处理部分还是那几段话,跟两年前一模一样。经过这半年生产环境的毒打,我算是把该踩的坑都踩遍了。

先说说最常见的几种错误类型,这都是真金白银换来的教训。嗯...这个比较复杂,我尽量讲清楚。

第一个坑:429限流

很多人以为429就是简单的“请求太多等会儿再试”。天真了。

OpenAI的限流分两个维度:RPM(每分钟请求数)和TPM(每分钟token数)。我们最开始只盯着RPM,结果有次批量处理长文档,RPM才到限额的60%就开始疯狂429。查了半天,傻眼了——TPM爆了。那些文档一个请求就干掉几万token,TPM限额直接击穿。

更恶心的是,这限流有滞后性。你看到429的时候,可能已经超限好几秒了。我们当时的处理方式是维护一个滑动窗口计数器,提前预估token消耗,快到阈值就主动降速。代码大概长这样:

PYTHON
class OpenAIRateLimiter:
 def __init__(self):
 self.request_window = deque(maxlen=60)
 self.token_window = deque(maxlen=60)
 
 async def acquire(self, estimated_tokens):
 now = time.time()
 while self.request_window and self.request_window[0] < now - 60:
 self.request_window.popleft()
 
 if len(self.request_window) >= 3500: # 留500的buffer
 wait_time = self.request_window[0] + 60 - now
 await asyncio.sleep(wait_time)
 
 self.request_window.append(now)

这个方案跑了大概三个月,效果还行。但我得说实话,遇到突发流量还是会偶尔翻车。

第二个坑:5xx重试

有次半夜被报警叫醒,一看日志全是502 Bad Gateway。我当时想都没想就开始无限重试。

结果呢?

重试请求把服务彻底打挂了。因为OpenAI那边本来就负载高,我这一通重试等于变相DDOS。后来跟DevOps的哥们复盘,他说了句让我记到现在的话:“你这不是在重试,你是在补刀。”

现在我们的策略是:5xx错误最多重试3次,但必须用指数退避加上随机抖动。关键是要区分哪些错误值得重试。500、502、503可以重试,但504超时就得看情况了——如果已经等了30秒还没返回,再重试大概率还是超时。据我了解,大部分团队都是这么干的,但具体参数得根据自己的业务调。

贴一段我们生产环境用的重试配置:

PYTHON
retry_config = {
 'max_retries': 3,
 'backoff_factor': 2.0,
 'jitter': True,
 'retry_on_status': [429, 500, 502, 503],
 'max_delay': 60,
 'retry_on_timeout': True,
 'timeout': 30
}

第三个坑:连接池耗尽

这是我最惨痛的一次教训。大概去年12月,我们的服务平时QPS也就100左右,配了200个连接池,我觉得绰绰有余。但有天下午3点多,OpenAI突然开始响应变慢,从平时的2秒涨到15秒。

雪崩。

连接池迅速耗尽,新的请求拿不到连接直接抛异常。那场面,就像双十一零点抢茅台,全堵在门口了。后来学乖了,加了三个保护机制:信号量控制并发数、断路器模式、合理超时。代码我就不全贴了,核心的断路器长这样:

PYTHON
class CircuitBreaker:
 def __init__(self, failure_threshold=5, timeout=60):
 self.failure_count = 0
 self.last_failure_time = None
 self.state = 'CLOSED'
 
 async def call(self, func, *args, **kwargs):
 if self.state == 'OPEN':
 if time.time() - self.last_failure_time > self.timeout:
 self.state = 'HALF_OPEN'
 else:
 raise Exception('Circuit breaker is OPEN')
 
 try:
 result = await func(*args, **kwargs)
 if self.state == 'HALF_OPEN':
 self.state = 'CLOSED'
 self.failure_count = 0
 return result
 except Exception as e:
 self.failure_count += 1
 if self.failure_count >= self.failure_threshold:
 self.state = 'OPEN'
 self.last_failure_time = time.time()
 raise e

一些实际数据

我们团队维护着一个日调用量500万+的服务,我拉了最近三个月的错误分布:

加了完善的重试和熔断机制后,服务可用性从99.2%提升到了99.7%。别看只提升了0.5个百分点,对于付费用户来说,这意味着每月少遇到3.6小时的故障。老板终于不找我谈话了。

一些踩出来的经验

1. 别在生产环境用默认超时。OpenAI的Python SDK v1.6.0默认超时是600秒,你敢信?600秒!用户早跑了。我们现在根据接口类型设了不同超时:聊天补全30秒,嵌入15秒,微调相关60秒。

2. 错误日志要记完整上下文。出问题时才发现,光记个错误码屁用没有。现在我们的日志会记录:request_id、重试次数、耗时、token消耗、原始错误信息。定位问题快多了。推荐用structlog,比标准logging好用不少。

3. 准备降级方案。我们维护了一个简单的规则引擎,当OpenAI不可用时自动切到Anthropic或者开源模型。虽然效果差一些,但至少服务不会完全不可用。这个方案大概花了两个迭代才稳定下来。

4. 成本监控别忘了。重试机制会增加成本,有次我们发现某天的费用暴涨300%,一查是有个bug导致大量请求重试了十几次。现在每次重试都会打点,超过阈值就告警。说到这儿,想起一个事。去年11月OpenAI那次大规模故障,持续了将近3个小时,我们这边重试逻辑疯狂运转,结果把当月的API预算提前干掉了三分之一。老板问我为什么费用这么高,我说“AI太聪明了,学会了自己花钱”,差点被开除。后来财务那边专门出了个规定,API费用异常波动超过50%必须4小时内上报。

总结

OpenAI API的错误处理真不是加个try-catch就完事。限流、重试、熔断、降级,都得考虑进去。最重要的是,别太信任官方文档,很多边界情况得自己摸索。我现在养成了个习惯,每次遇到新的错误码就往团队的踩坑文档里记一笔。半年下来,那个文档已经快成一本小书了。新人入职第一周就是读这个文档,比看官方文档管用多了。

你们在生产环境遇到过什么奇葩的错误吗?特别是那种文档里根本没提到的。我最近在整理一个开源的重试工具库,把这些经验都固化进去,大概下个月能发第一个版本,有兴趣的可以关注下。


#OpenAI #API错误处理 #生产部署 #重试机制 #后端开发 #经验分享

547
9120 阅读
2 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年06月27日 19:00

陈默

AI 行业分析师

前某大厂 AI 实验室研究员,关注大模型技术演进和商业化落地。写过 200+ 篇行业分析,擅长从产品视角拆解技术趋势。

读者评论 2

张工 1周前
写得很实在,特别是实测对比那部分,跟我自己的使用感受一致。
回复 点赞 (12)
前端工程师 1周前
代码示例很清晰,直接用到项目里了。
回复 点赞 (6)