tenacity库是python中最稳妥的重试方案,原生支持异步、指数退避+jitter、异常白名单过滤及总耗时控制,避免手动实现导致的雪崩、超时失控和非幂等重试等问题。

直接用 tenacity 库,别手写指数退避——自己实现容易漏掉 jitter、最大重试时间、异常类型过滤等关键细节。
为什么不用 time.sleep() 手动循环重试
手动 sleep 容易犯三类错:一是没加 jitter 导致请求雪崩(所有实例在同一时刻重试),二是没设最大总耗时,三是把非幂等异常(比如 ValueError)也重试了。指数退避不是简单地 sleep(1), sleep(2), sleep(4),它需要随机偏移、上限截断和异常白名单。
- 必须区分可重试异常(如
requests.exceptions.ConnectionError)和不可重试异常(如json.JSONDecodeError) - 基础间隔建议从
0.1秒起,每次 ×2,但最大单次等待不超过10秒 - 加上
jitter后,实际等待是min(base * 2**n + random.uniform(0, 1), max_delay)
用 tenacity 实现带 jitter 的 HTTP 请求重试
tenacity 是目前最稳妥的 Python 重试库,原生支持异步、多种停止/等待策略、异常过滤和结果判断。下面这个例子针对 requests 调用:
from tenacity import retry, stop_after_attempt, wait_exponential_jitter, retry_if_exception_type import requests <p>@retry( stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=0.1, max=10), retry=retry_if_exception_type(( requests.exceptions.ConnectionError, requests.exceptions.Timeout, requests.exceptions.HTTPError )) ) def fetch_data(url): resp = requests.get(url, timeout=5) resp.raise_for_status() return resp.json()</p>
注意:wait_exponential_jitter 默认 jitter 范围是 0–1 秒,initial=0.1 表示第一次失败后等待约 0.1–1.1 秒,第二次约 0.2–1.2 秒,依此类推。
自定义重试条件:不只是看异常类型
有些 API 返回 200 但 body 里是错误码,这时得靠返回值判断是否重试。用 retry_if_result 或 retry_if_exception 配合 lambda:
from tenacity import retry_if_result
<p>def is_error_response(result):
return result.get("code") == "TEMPORARY_UNAVAILABLE"</p><p>@retry(
retry=retry_if_result(is_error_response),</p><h1>其他参数同上</h1><p>)
def call_api():
return requests.get("<a href="https://www.php.cn/link/9ee7af963c19e8e67bb1baefaab4837b">https://api.example.com/v1").json</a>()</p>
这里 retry_if_result 只在函数有返回值时触发;如果函数抛异常,就走 retry_if_exception_type 分支。两个条件可以同时存在,tenacity 会做逻辑或判断。
容易被忽略的坑:超时设置要分层
很多人只设了 requests.timeout,但没控制整个重试过程的总耗时。比如 5 次重试,最后一次等待可能长达 10 秒,加上请求本身 5 秒超时,总耗时可能逼近 60 秒。正确做法是叠加 stop_after_delay:
- 用
stop=(stop_after_attempt(5) | stop_after_delay(30))表示“最多 5 次或总共不超过 30 秒” - HTTP 客户端层(如 requests)的
timeout应设为单次请求上限,通常 ≤ 10 秒 - 不要依赖
signal.alarm做全局超时——它在多线程/async 下不可靠
真正的难点不在“怎么加重试”,而在于“哪些情况不该重试”和“重试到什么程度必须放弃”。这两个边界不厘清,加再多退避也没用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











