HTTP 重试为什么要有上限:从 429 到指数退避

举报
yd_239005675 发表于 2026/10/12 07:15:49 2026/10/12
【摘要】 本文从 HTTP 状态码和方法语义出发,讲解 429 处理、幂等保护、指数退避、随机抖动和重试预算,给出可检查的伪代码流程。

在调用云服务、数据库或内部 HTTP 接口时,临时网络抖动和服务过载都可能让一次请求失败。很多程序的第一反应是“失败就再试一次”,但没有边界的重试会把一个小故障放大成请求风暴。更稳妥的做法是先判断错误是否值得重试,再设置次数、总耗时和退避等待。

1. 先区分失败类型

连接超时、连接被重置、网关返回 502 或 503,通常可能是暂时性故障;但 400、401、403、404 等响应往往需要修正参数、凭证或资源地址,盲目重试不会解决问题。客户端应保留状态码、请求方法和服务端返回的 request id,方便定位真正原因。

429 Too Many Requests 表示服务端认为客户端在给定时间内发送了过多请求。此时最有价值的线索是 Retry-After 响应头:它可能给出等待秒数,也可能给出一个 HTTP 日期。客户端应优先遵循服务端给出的等待时间,并继续受本地总超时限制。相关状态码定义见 RFC 6585 第 4 节:

2. 重试和请求语义有关

HTTP 规范把 GET、HEAD、OPTIONS 和 TRACE 定义为安全方法;PUT 和 DELETE 不是安全方法,但具有幂等语义。幂等意味着同一请求执行一次或多次,预期的服务端效果相同,并不表示请求一定没有副作用。

POST 默认不具备幂等语义。比如“创建订单”请求已经在服务端成功,但客户端因为响应超时而重试,可能生成两笔订单。要重试这类操作,需要业务提供幂等键、请求去重或查询确认机制。不要因为看到 5xx 就自动再次提交可能产生费用、扣库存或创建资源的请求。

方法语义和幂等性的定义可以参考 IETF 的 HTTP Semantics:

3. 指数退避还要加入随机抖动

固定间隔重试会让同时失败的一批客户端在同一时刻再次请求。指数退避可以让等待时间逐步增加,例如第 1 次等待 0.5 秒,第 2 次等待 1 秒,第 3 次等待 2 秒,再设置一个最大等待上限。实际等待时间还应加入随机抖动,让客户端分散到不同时间点恢复。

一个简单的计算方式是:

base = 0.5
cap = 8.0
delay = min(cap, base * (2 ** attempt))
wait = random.uniform(0, delay)

attempt 从 0 开始时,第一次重试的最大等待为 0.5 秒。随机数只负责分散请求,不能替代最大次数和总截止时间。生产代码还要处理系统时钟、取消信号和线程睡眠被打断的情况。

4. 把重试预算写成明确规则

可以为一次业务调用定义三层限制:最多重试 3 次,整个调用最多持续 10 秒,单次等待不超过 8 秒。任何一层触发,都应该返回最后一个可诊断的错误,而不是继续循环。

同时要设置连接超时和读取超时。只有设置总超时而没有阶段超时,连接池耗尽时仍可能长时间占用线程;只有设置读取超时而没有总预算,多个重试阶段叠加后也会超出调用方预期。重试日志应记录尝试次数、等待时长、状态码和最终结果,但不要把 Authorization、Cookie 或完整请求体写入日志。

5. 一个可检查的伪代码流程

deadline = monotonic() + 10
for attempt in range(4):
    response = send_once(connect_timeout=2, read_timeout=5)
    if response.ok:
        return response
    if not retryable(response, method, request_has_idempotency_key):
        raise RequestError(response)
    if monotonic() >= deadline:
        raise TimeoutError("retry budget exhausted")
    retry_after = parse_retry_after(response.headers)
    delay = retry_after if retry_after is not None else backoff(attempt)
    sleep(min(delay, max(0, deadline - monotonic())))
raise TimeoutError("retry count exhausted")

伪代码里有两个容易遗漏的细节:第一,等待前再次检查 deadline,避免最后一次睡眠越过业务截止时间;第二,服务端 Retry-After 也要经过本地上限裁剪,不能让异常响应把客户端挂起很久。

6. 用指标验证重试是否真的有用

至少观察请求成功率、重试率、每次尝试的状态码分布、最终耗时和放弃原因。如果重试率升高但成功率没有改善,说明重试可能只是在制造额外压力。对 429、502、503 分开统计,才能判断是配额问题、网关问题还是上游依赖故障。

重试的目标是把短暂故障留在系统边界内,给服务端和客户端都留下恢复空间。明确的错误分类、幂等保护、指数退避、随机抖动和可观测预算,组合起来才是一套可控的容错策略。

写作说明:本文由 AI 辅助编写,示例为独立构造的通用技术说明。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。