一、先划清资料边界
本文所依据的官方事实资料为空,因此不会给出任何具体的超时秒数、重试次数、限流阈值或错误码清单——这些数字必须回到官方文档核对后才适合写入生产配置。以下内容是客户端重试的通用工程方法,属于工程分析,不代表官方承诺。涉及接口行为的判断,请以官方文档为准。
二、先分类,再重试
客户端遇到的失败大体分三类:
- 连接类失败:DNS 解析失败、TCP 建连超时、TLS 握手失败。重试收益取决于网络是否抖动,通常可以重试。
- 读写超时:连接已建立,但迟迟没有响应数据。对生成类接口,长响应是常态,必须区分“服务仍在产出”和“连接已经僵死”。
- 限流类错误:HTTP 429,以及通常建议一并处理的 5xx。429 表示服务端明确要求降速,重试必须带退避,否则只会加重拥塞。
关键区别在于:超时告诉你“不知道发生了什么”,429 告诉你“发生了什么,请慢一点”。前者重试是探测,后者重试是排队。若响应携带 Retry-After(HTTP 标准头),应优先遵循它,而不是套用自己的固定间隔。
三、把超时拆开配置
单一 timeout 值会把“建连慢”和“生成慢”混为一谈。以 httpx 为例,超时对象通常可分别设置 connect / read / write / pool(具体字段名以所用版本文档为准):
- connect:可以设短一些,快速失败并进入重试。
- read:对长生成接口应显著放宽,否则会把正常的长输出误判为故障。
- write:请求体通常很小,可保持默认。
- pool:连接池等待超时,最容易被忽略;高并发下它会伪装成“接口变慢”。
连接池本身也应显式配置,例如 max_connections 与 max_keepalive_connections。池过小会让请求在池内排队,日志上表现为读超时,实际瓶颈却在客户端。
四、退避、上限与重试预算
通用做法是指数退避加抖动:
- 基础间隔随重试次数指数增长,避免固定间隔形成同步冲击。
- 加入随机抖动,防止多个实例在同一时刻齐步重试。
- 设置最大重试次数上限,避免单个错误被放大成流量风暴。
- 更稳妥的是“重试预算”:为一段时间窗口设定总重试上限,而不是给每个请求固定次数。
若使用 OpenAI 兼容 SDK,其客户端通常提供 timeout 与 max_retries 两个入口(名称与语义以所用版本文档为准),可在构造客户端时统一注入;若自行封装 HTTP 层,则重试只应实现一处,避免“SDK 重试一层、业务代码再重试一层”的叠加放大。
五、幂等性:重试可能等于重复计费
这是最容易被忽略的部分。带副作用的请求被重试时,存在“服务端已成功、客户端未收到响应”的窗口,重试会导致重复执行。
工程上可以做的:
- 只对明确幂等的操作自动重试;非幂等的写入型调用应限制或禁止自动重试。
- 为每次逻辑请求生成稳定的请求标识,重试时复用同一标识,便于服务端或自身去重层识别。
- 在业务侧持久化幂等键与“发起—成功”状态,重试前先查状态。
- 把“客户端超时”和“服务端失败”当成两种不同事件:前者结果未知,后者结果已知为失败。
六、日志与观测
重试必须可见,否则线上问题会被退避逻辑掩盖:
- 记录每次重试的次数、原因(连接 / 读超时 / 限流)与等待时长。
- 记录最终结果:成功于第几次,或最终失败于哪一类错误。
- 按错误类型分别打点,避免所有失败混合成一个“接口错误率”。
- 区分客户端侧错误与服务端侧错误,前者应从连接池、超时配置入手排查。
七、落地清单
- 拆分超时:connect 短、read 长、pool 显式设置。
- 只对可重试错误重试:连接失败、读超时、429、5xx。
- 指数退避加抖动,设置次数上限与重试预算。
- 非幂等请求不自动重试,或引入幂等键。
- 每次重试都留日志,最终错误类型可归因。
- 所有具体阈值以官方文档为准,先核对再上线。