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 Sessionclass JitteredRetry(Retry): def init(self, jitter=0.2, *args, *kwargs): super().init(args, **kwargs) self.jitter = jitter
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 模拟抖动。









