Retry-After 标头:重试前等待多长时间

Retry-After 是一个 HTTP 响应标头,告知应用在发送下一个请求之前等待多长时间。 该值为秒数或 HTTP 日期。 当 API 发送它时,它是“我何时可以重试?”的最可靠答案,因为它来自拒绝你请求的服务器。 有关详细信息,请参阅 RFC 9110,第 10.2.3 节。

Retry-After 的显示方式

标头有 2 种格式。 你的应用需要同时处理这两者。

Format Example 含义
Seconds Retry-After: 120 收到响应后,请等待 120 秒(2 分钟)。 该值是非负整数。
HTTP 日期 Retry-After: Fri, 31 Dec 1999 23:59:59 GMT 不要在此时间之前再次发送请求。 该日期始终以 GMT 表示。

服务器发送 Retry-After 时会附带以下状态代码:

Status Retry-After是什么意思 Source
429 Too Many Requests 发送新请求之前等待多长时间。 服务器可以包含它。 RFC 6585,第 4 节
503 Service Unavailable 服务预计不可用的时间。 服务器可以包含它。 RFC 9110 第 15.6.4 节
413 Content Too Large 如果该情况是暂时性的,服务器应说明它将在多久后结束。 RFC 9110 第 15.5.14 节
任何 3xx 重定向 跟踪重定向之前等待的最短时间。 RFC 9110 第 10.2.3 节

标头是可选的。 某些 API 改用自己的标头。 例如,GitHub 会通过 x-ratelimit-reset 告诉你你的限制何时重置。 有关详细信息,请参阅超出 GitHub API 速率限制。

如何处理 Retry-After

  1. 读取两种格式。 如果值为数字,则为秒。 否则,将其分析为日期并减去当前时间。 如果日期已过去,可以立即重试。
  2. 至少等待标头所示的时间。 过早重试通常会让你再收到一个429或503。 某些 API 在请求限制时会不断对请求进行计数,因此提前重试可能会延长等待时间。 例如,请参阅Microsoft Graph限制指南。
  3. 当缺少标头时,改用带抖动的退避策略。 在每个失败尝试后加倍等待,添加一个随机量,以便许多客户端不会在同一时刻重试,并限制等待。
  4. 限制重试次数。 尝试几次后,将错误返回到调用方。
  5. 检查重试是否有帮助。 当额度或支出限制用完时,某些 API 将返回 429 。 等待无法修复这些问题。 有关示例,请参阅 OpenAI insufficient_quota和credit_balance_exhausted。
function retryDelayMs(response, attempt) {
  const value = response.headers.get('retry-after');
  if (value) {
    const seconds = Number(value);
    const ms = Number.isNaN(seconds) ? Date.parse(value) - Date.now() : seconds * 1000;
    if (!Number.isNaN(ms)) {
      return Math.max(ms, 0);
    }
  }
  // No usable header: exponential backoff with jitter, capped at 30 seconds
  return Math.random() * Math.min(30_000, 1_000 * 2 ** attempt);
}

许多 SDK 为你处理 Retry-After,但只能处理到重试次数耗尽为止。 然后,代码将报错。

SDK 默认情况下,它的作用
.NET标准复原处理程序 对 408、429 和 5xx 响应最多重试 3 次,并采用指数退避和抖动。 它使用 Retry-After 作为延迟值,因为 ShouldRetryAfterHeader 默认为 true。
Microsoft Graph SDKs 在存在时使用 Retry-After,在不存在时回退到指数退避。 不会自动重试 JSON 批处理中的请求。
OpenAI Python SDK 对连接错误以及 409、408、429 和 5xx 响应重试 2 次,并采用短暂的指数退避。 设置max_retries以更改它。

检查 SDK 的文档,了解确切的策略,并测试最后一次重试失败后会发生什么情况。

如何测试应用处理 Retry-After

你在开发过程中很少会遇到一个 Retry-After,而且即使遇到了,你也无法控制它的值。 因此,你如何测试这一点决定了你是否能在用户之前发现这些 bug。

Approach 你找到的内容 您错过的内容
等待生产 真实故障 所有情况,直到用户遇到它
在测试中模拟 API,或让编码代理编写模拟 你的代码是否解析该标头 真正的 HTTP 客户端或 SDK 是否等待了足够长的时间,以及 API 实际发送了什么内容。 你的应用还需要一个仅用于测试的开关来访问模拟。
调用真正的 API,直到它对你进行限流 实际表现 无法按需触发限流响应,并且会占用实际配额
截获应用的实际流量并按需返回限速响应 你的实际 SDK 和重试策略是否会等待标头所示的那么长时间 你的应用无需做任何更改,因此它不会对你的代码进行隔离测试。 将单元测试保留用于该目的。

在你的应用中试试

Dev Proxy 会拦截你的应用向所选 API 发出的请求,并返回带有 Retry-After 标头的 429 响应,而你的应用会继续调用真实的 URL。 RetryAfterPlugin 会记住何时可以重试每个受到限制的请求。 如果你的应用在该时间之前调用同一 URL,Dev Proxy 会报告此情况并再次对请求限流。 插件仅跟踪 429 响应。

在 GenericRandomErrorPlugin 的错误文件中,将 @dynamic 响应的 Retry-After 值设置为 429,而开发代理将填充秒数并跟踪它。

若要试用,请下载使用这两个插件的预设,并使用它启动开发代理:

devproxy config get openai-throttling
devproxy --config-file "~dataFolder/configs/openai-throttling/.devproxy/devproxyrc.json"

然后像往常一样运行应用并观察其运行情况。 若要安装 Dev Proxy,请参阅 设置 Dev Proxy。

后续步骤

另请参阅