KernelRanch
返回文章列表

遇到 429 后该等多久?解析 Retry-After 并检查剩余预算

2026-11-13 #http#api#resilience

收到 HTTP 429 时,Retry-After 可能是秒数,也可能是 HTTP 日期。先把提示转换成等待时长,再与本次操作剩余预算比较。服务端要求等 60 秒而调用方只剩 2 秒时,应停止本次重试或交给有独立预算的异步任务。

1. 用固定时间验证等待决策

保存为 retry_after_lab.py,用 Python 运行。例中约定非法或缺失头部使用本地退避,并为下一次请求保留两秒。这里的日期解析使用 Python 邮件日期解析器做容错,只接受带时区的结果。

from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import re


def retry_delay(header, received_at):
    if header is None:
        return None
    value = header.strip(" \t")
    if re.fullmatch(r"[0-9]+", value):
        digits = value.lstrip("0")
        return float(digits or "0") if len(digits) <= 12 else float("inf")

    try:
        target = parsedate_to_datetime(value)
    except (ValueError, OverflowError):
        return None
    if target.tzinfo is None:
        return None
    return max(0.0, (target - received_at).total_seconds())


def plan_retry(header, received_at, remaining, backoff):
    hint = retry_delay(header, received_at)
    delay = max(backoff, hint) if hint is not None else backoff
    return delay if delay + 2 <= remaining else None


now = datetime(2026, 11, 13, 12, tzinfo=timezone.utc)
cases = [
    ("seconds", "5", 5),
    ("date", "Fri, 13 Nov 2026 12:00:04 GMT", 4),
    ("past date", "Fri, 13 Nov 2026 11:59:00 GMT", 1),
    ("invalid", "-1", 1),
    ("missing", None, 1),
    ("over budget", "60", None),
]
for label, header, expected in cases:
    delay = plan_retry(header, now, remaining=8, backoff=1)
    assert delay == expected
    print(f"{label}: {'stop' if delay is None else f'wait {delay:g}s'}")
seconds: wait 5s
date: wait 4s
past date: wait 1s
invalid: wait 1s
missing: wait 1s
over budget: stop

2. 把等待放回请求生命周期

RFC 6585 允许 429 附带这个头,RFC 9110 同时定义了 503 上的等待含义。收到日期形式时,用接收响应的墙钟换算等待时长。执行等待和衡量总预算时用单调时钟,以免系统校时改变截止时间。样本中的超大秒数转换为无穷大,在有限预算下会停止重试。

重试入口还需确认操作可重放。读请求可以按服务合同重试,写请求则需要按具体接口确认幂等保证,例如 130 的幂等键。多客户端同时退避时,可在服务端等待下限之外加入随机抖动,并在等待后重算剩余预算。记录状态码、解析结果和停止原因,能够分辨服务端要求延后与客户端预算耗尽。

小结

把 Retry-After 当作重试的等待下限。是否真的重试还取决于操作语义和剩余预算。

数据来源与检索时间

来源URL检索日期对应内容
RFC 9110 §10.2.3https://www.rfc-editor.org/rfc/rfc9110.html#section-10.2.32026-11-13Retry-After 的两种格式及 503 语义
RFC 6585 §4https://www.rfc-editor.org/rfc/rfc6585.html#section-42026-11-13429 可携带 Retry-After
Python email.utilshttps://docs.python.org/3.11/library/email.utils.html#email.utils.parsedate_to_datetime2026-11-13日期解析及无时区返回值

核验说明

  • Windows、Python 3.11.9 固定接收时间,核对六种决策分支。示例只做计算,未执行网络请求或真实等待。