一、先划清资料边界

本文所依据的官方事实资料为空,因此不会给出任何具体的超时秒数、重试次数、限流阈值或错误码清单——这些数字必须回到官方文档核对后才适合写入生产配置。以下内容是客户端重试的通用工程方法,属于工程分析,不代表官方承诺。涉及接口行为的判断,请以官方文档为准。

二、先分类,再重试

客户端遇到的失败大体分三类:

  1. 连接类失败:DNS 解析失败、TCP 建连超时、TLS 握手失败。重试收益取决于网络是否抖动,通常可以重试。
  2. 读写超时:连接已建立,但迟迟没有响应数据。对生成类接口,长响应是常态,必须区分“服务仍在产出”和“连接已经僵死”。
  3. 限流类错误: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 重试一层、业务代码再重试一层”的叠加放大。

五、幂等性:重试可能等于重复计费

这是最容易被忽略的部分。带副作用的请求被重试时,存在“服务端已成功、客户端未收到响应”的窗口,重试会导致重复执行。

工程上可以做的:

  • 只对明确幂等的操作自动重试;非幂等的写入型调用应限制或禁止自动重试。
  • 为每次逻辑请求生成稳定的请求标识,重试时复用同一标识,便于服务端或自身去重层识别。
  • 在业务侧持久化幂等键与“发起—成功”状态,重试前先查状态。
  • 把“客户端超时”和“服务端失败”当成两种不同事件:前者结果未知,后者结果已知为失败。

六、日志与观测

重试必须可见,否则线上问题会被退避逻辑掩盖:

  • 记录每次重试的次数、原因(连接 / 读超时 / 限流)与等待时长。
  • 记录最终结果:成功于第几次,或最终失败于哪一类错误。
  • 按错误类型分别打点,避免所有失败混合成一个“接口错误率”。
  • 区分客户端侧错误与服务端侧错误,前者应从连接池、超时配置入手排查。

七、落地清单

  1. 拆分超时:connect 短、read 长、pool 显式设置。
  2. 只对可重试错误重试:连接失败、读超时、429、5xx。
  3. 指数退避加抖动,设置次数上限与重试预算。
  4. 非幂等请求不自动重试,或引入幂等键。
  5. 每次重试都留日志,最终错误类型可归因。
  6. 所有具体阈值以官方文档为准,先核对再上线。