贝利信息

requests 如何实现带 jitter 的指数退避重试(不依赖 backoff 库)

日期:2026-01-23 00:00 / 作者:冷炫風刃
urllib3.Retry 通过自定义 backoff_func 实现带 jitter 的指数退避,公式为 min(backoff_max, (2 retry_count) backoff_factor random.uniform(0.5, 1.5)),需设 backoff_factor=0 避免叠加,默认返回值即 sleep 秒数。

requests 自带重试机制不支持 jitter,必须手动封装

requests 的 urllib3.Retry 能做指数退避,但所有重试间隔是确定的(如 1s、2s、4s),没有随机抖动(jitter)。生产环境直接用它容易触发服务端限流或雪崩,必须自己加 jitter —— 也就是在每次计算出的基础等待时间上乘一个 [0.5, 1.5) 之间的随机因子。

用 urllib3.Retry + 自定义 backoff_func 实现 jitter

urllib3.Retry 允许传入 backoff_factorbackoff_max,但它默认的退避逻辑是线性的(实际是指数,但无 jitter)。真正可控的方式是传入自定义的 backoff_func 参数,该函数接收重试次数 retry_count,返回应等待的秒数。

import random
import time
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def jittered_backoff(retry_count): base = (2 * retry_count) 0.5 # backoff_factor=0.5 max_wait = 60.0 return min(max_wait, base * random.uniform(0.5, 1.5))

retry_strategy = Retry( total=5, status_forcelist=[429, 500, 502, 503, 504], backoff_factor=0, # 必须设为 0,否则会叠加默认逻辑 backoff_max=60, backoff_func=jittered_backoff, )

adapter = HTTPAdapter(max_retries=retry_strategy) session = requests.Session() session.mount("http://", adapter) session.mount("https://", adapter)

requests.Session 不会自动 sleep,需在 backoff_func 中控制阻塞

很多人误以为 backoff_func 返回值会被 urllib3 自动用于 time.sleep() —— 实际上不是。urllib3 仅用它来决定是否重试(比如超时前还剩多少时间),真正的等待逻辑在它内部实现。但关键点是:**只有当 backoff_func 返回值 ≤ 剩余重试时间时,urllib3 才会 sleep 对应时

长**。所以你返回的值就是最终 sleep 秒数,不需要额外 time.sleep()

注意 time.monotonic() vs time.time() 与系统时钟漂移

urllib3 内部用 time.monotonic() 计算剩余等待时间,所以你的 backoff_func 返回值不会受系统时间回拨影响。这点不用额外处理,但如果你在自定义重试逻辑里手动 sleep,就得自己用 monotonic() 校验 —— 而用 backoff_func 方式就天然规避了这个问题。

真正容易被忽略的是:jitter 的随机源必须是线程安全的。如果 session 被多线程共用(比如在 FastAPI 的全局 client 里),random.uniform() 默认使用全局 random.Random 实例,在 CPython 中是线程安全的,但不保证跨平台;更稳妥的做法是每个重试策略绑定独立的 random.Random 实例,不过对绝大多数场景,直接用 random.uniform 已足够。