requests默认不支持带抖动的指数退避,需通过自定义urllib3.Retry子类(如JitteredRetry)重写get_backoff_time方法注入随机抖动,并挂载到HTTPAdapter;重试失败统一抛出RetryError而非原始异常,须显式捕获;手动循环更灵活但需自行管控超时、Retry-After头及异步适配。

requests 默认不支持指数退避重试,必须手动集成 retrying 逻辑
requests 本身只提供基础的 Session 和简单重试(如 urllib3.Retry),但它的内置重试不带抖动(jitter),且指数退避参数控制粒度粗、不可定制抖动范围。真要实现「带抖动的指数退避」,得绕过 requests.adapters.HTTPAdapter 的默认行为,用更底层的 urllib3.util.retry.Retry 配置,或自己封装重试循环。
用 urllib3.Retry 实现带抖动的指数退避(推荐)
这是最轻量、兼容性最好、且不引入额外依赖的方式。关键点在于:
- 使用 urllib3.util.retry.Retry 的 backoff_factor 启用指数退避
- 通过重写 get_backoff_time() 方法注入抖动逻辑
- 将自定义 Retry 实例挂载到 HTTPAdapter
示例代码片段:
import random
import time
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from requests import Session
<p>class JitteredRetry(Retry):
def <strong>init</strong>(self, jitter=0.2, *args, *<em>kwargs):
super().<strong>init</strong>(</em>args, **kwargs)
self.jitter = jitter</p><pre class="brush:php;toolbar:false;">def get_backoff_time(self):
backoff = super().get_backoff_time()
if backoff <= 0:
return 0
# 在 [backoff × (1−jitter), backoff × (1+jitter)] 区间内随机
return backoff * (1 + random.uniform(-self.jitter, self.jitter))session = Session() adapter = HTTPAdapter( max_retries=JitteredRetry( total=3, status_forcelist=[429, 500, 502, 503, 504], backoff_factor=1.0, jitter=0.3 # 30% 抖动幅度 ) ) session.mount("https://", adapter) session.mount("http://", adapter)
后续调用 session.get() / session.post() 即自动启用抖动退避
注意:backoff_factor=1.0 表示第 n 次重试前等待约 1 × 2^(n−1) 秒(再乘以抖动系数);jitter=0.3 让每次等待时间在理论值 ±30% 内浮动,有效缓解重试风暴。
requests.exceptions.RetryError 是重试失败的最终异常类型
当所有重试都耗尽后,requests 不会抛出原始网络异常(如 ConnectionError),而是统一包装为 requests.exceptions.RetryError。这点容易被忽略,导致错误处理漏掉重试失败场景。
常见误判写法:
try:
r = session.get(url)
except requests.exceptions.ConnectionError:
# ❌ 这里捕获不到重试耗尽后的异常
handle_failure()
正确做法是:
- 显式捕获
requests.exceptions.RetryError - 或更稳妥地,同时捕获
RetryError和其他可能未进入重试流程的异常(如 DNS 解析失败发生在首次请求前) - 注意:即使启用了重试,超时(
Timeout)仍可能直接抛出,不经过RetryError
手动重试循环比适配器更灵活,但需自行管理状态
如果你需要动态调整退避参数(比如根据响应头中的 Retry-After)、记录每次重试耗时、或在重试中插入日志/监控埋点,用 while 循环手动控制更合适。
核心要点:
- 用
time.sleep()控制间隔,别依赖Retry类的黑盒逻辑 - 每次 sleep 前计算抖动:例如
sleep(base_delay * (2 ** attempt) * random.uniform(0.5, 1.5)) - 务必设置最大尝试次数和总超时上限(避免无限卡住)
- 对
Retry-After响应头做优先级高于指数退避的处理
这种写法自由度高,但容易写出阻塞主线程、没做 cancelable、或抖动逻辑重复的代码——尤其在异步环境中,得换用 asyncio.sleep 并配合 async with 生命周期管理。
抖动不是锦上添花,而是分布式系统里避免重试雪崩的关键细节;很多线上故障就卡在「所有客户端在同一毫秒发起重试」这一步。别省略它,也别用固定 delay 模拟抖动。









