目录
Tenacity
Tenacity 是一个通用的 Python 重试库,是对
retrying库的改进版本。它通过装饰器的方式,为函数调用提供灵活、可配置的重试机制,是构建容错系统和提升服务健壮性的重要工具。
核心特性
- 声明式重试策略 :通过装饰器
@retry定义重试行为,代码侵入性低 - 丰富的停止条件:支持按次数、超时时间、自定义条件停止重试
- 灵活的等待策略:提供固定、指数、随机等多种等待间隔策略
- 精确的重试触发:可指定特定异常类型、返回值或自定义条件触发重试
- 完善的重试回调:支持重试前、重试后、错误发生时的回调钩子
工作原理
重试执行流程
是
否
否
是
是
否
调用被装饰函数
执行成功?
返回结果
匹配重试条件?
抛出异常
达到停止条件?
执行 before_sleep 回调
应用等待策略
执行 after_callback 回调
核心组件
Tenacity 的重试机制由以下核心参数协同工作:
| 参数类型 | 作用说明 | 常用值 |
|---|---|---|
stop |
停止条件 | stop_after_attempt(3), stop_after_delay(10) |
wait |
等待策略 | wait_fixed(1), wait_exponential(), wait_random() |
retry |
重试触发条件 | retry_if_exception_type(), retry_if_result() |
before_sleep |
睡眠前回调 | 记录日志、清理资源 |
after |
重试后回调 | 统计重试次数、发送告警 |
reraise |
异常重抛 | True 表示最终抛出原始异常 |
参数详解
stop - 停止条件
| 函数 | 参数 | 说明 |
|---|---|---|
stop_after_attempt(n) |
n: 最大尝试次数 |
最多执行 n 次(含首次) |
stop_after_delay(seconds) |
seconds: 总时长(秒) |
从首次调用开始计时,超时即停止 |
stop_when(condition) |
condition: 可调用对象 |
当条件函数返回 True 时停止 |
wait - 等待策略
| 函数 | 参数 | 说明 |
|---|---|---|
wait_fixed(seconds) |
seconds: 固定等待时间 |
每次重试前等待固定秒数 |
wait_exponential(multiplier, min, max) |
multiplier: 基础乘数 min: 最小等待秒数 max: 最大等待秒数 |
指数退避:第 i 次等待 min(2^i * multiplier, max) |
wait_random(min, max) |
min: 最小等待秒数 max: 最大等待秒数 |
随机等待,避免重试风暴 |
wait_chain(*wait_funcs) |
wait_funcs: 等待策略列表 |
按顺序依次使用不同策略 |
wait_combine(*wait_funcs) |
wait_funcs: 等待策略列表 |
组合多个策略,取最大值 |
retry - 重试触发条件
| 函数 | 参数 | 说明 |
|---|---|---|
retry_if_exception_type(exc_type) |
exc_type: 异常类或元组 |
仅当抛出指定异常时重试 |
retry_if_exception(lambda e: ...) |
可调用对象 | 自定义异常判断逻辑 |
retry_if_result(predicate) |
predicate: 结果判断函数 |
当返回值满足条件时重试 |
retry_if_not_result(predicate) |
predicate: 结果判断函数 |
当返回值不满足条件时重试 |
retry_unlikely_exception |
- | 仅重试"不太可能"的异常 |
retry_any(*retry_conditions) |
多个重试条件 | 任意条件满足即重试(或关系) |
retry_all(*retry_conditions) |
多个重试条件 | 所有条件都满足才重试(与关系) |
回调与错误处理
| 参数 | 类型 | 说明 |
|---|---|---|
before_sleep |
callable(retry_state) |
每次重试前(sleep 前)执行 |
after |
callable(retry_state) |
每次重试后执行 |
before |
callable(retry_state) |
每次重试前执行 |
on_error |
callable(retry_state) |
每次发生异常时执行 |
retry_error_callback |
callable(retry_state) |
最终重试失败后执行,设置后不抛出异常 |
reraise |
bool |
是否在最终失败时重抛原始异常(默认 True) |
stop 和 retry 是两个独立的概念:stop 控制重试的终止时机 ,retry 控制是否触发重试。
快速上手
安装
bash
pip install tenacity
示例
python
from tenacity import retry, stop_after_attempt
# @retry: 装饰器,为函数添加重试能力
# stop: 停止条件,控制何时终止重试
# stop_after_attempt(3): 最多尝试 3 次(首次调用 + 最多 2 次重试)
@retry(stop=stop_after_attempt(3))
def unreliable_function():
import random
if random.random() < 0.7: # 70% 概率失败
raise ValueError("随机失败")
return "成功!"
# 预期结果:最多重试 3 次,成功则返回 "成功!"
print(unreliable_function())
常见使用场景
1. 指数退避(推荐用于网络请求)
python
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
# stop_after_attempt(5): 最多尝试 5 次
stop=stop_after_attempt(5),
# wait_exponential: 指数退避等待策略
# multiplier: 基础乘数,第一次重试等待 2^0 * multiplier = 1 秒
# min: 最小等待时间(秒),防止等待过短
# max: 最大等待时间(秒),防止等待过长
wait=wait_exponential(multiplier=1, min=1, max=10)
)
def fetch_api(url):
response = requests.get(url)
response.raise_for_status()
return response.json()
等待时间:1s → 2s → 4s → 8s → 10s(上限)
2. 仅重试特定异常
python
from tenacity import retry, retry_if_exception_type, stop_after_attempt
@retry(
# 最多重试 3 次
stop=stop_after_attempt(3),
# retry_if_exception_type: 仅当抛出指定异常类型时才重试
# 可传入单个异常类或异常元组
retry=retry_if_exception_type(ConnectionError),
# reraise=True: 最终重试失败后,重新抛出原始异常
# 默认为 True,设为 False 则抛出 RetryError 包装的异常
reraise=True
)
def connect_database():
# 只有 ConnectionError 会触发重试
# 其他异常(如 ValueError)会直接抛出,不会重试
...
3. 根据返回值重试
python
from tenacity import retry, retry_if_result, stop_after_attempt, wait_fixed
def is_not_ready(response):
"""
判断函数返回值是否需要重试
返回 True: 触发重试
返回 False: 不重试,正常返回结果
"""
return response.get("status") == "pending"
@retry(
# 最多重试 10 次
stop=stop_after_attempt(10),
# retry_if_result: 当函数返回值满足指定条件时重试
# 传入一个 callable,接收函数返回值作为参数
# 返回 True 表示需要重试,返回 False 表示成功
retry=retry_if_result(is_not_ready),
# wait_fixed(2): 每次重试前固定等待 2 秒
wait=wait_fixed(2)
)
def check_task_status(task_id):
return api.get_task(task_id)
4. 带回调的完整示例
python
from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
before_sleep_log,
after_log,
retry_error_callback
)
import logging
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def log_retry_attempt(retry_state):
"""
before_sleep 回调函数
retry_state: RetryState 对象,包含重试状态信息
- attempt_number: 当前尝试次数(从 1 开始)
- outcome: 上次执行的结果(异常或返回值)
- outcome_timestamp: 上次执行的时间戳
- idle_for: 即将等待的时间
"""
print(f"第 {retry_state.attempt_number} 次重试,等待 {retry_state.idle_for} 秒")
def handle_retry_error(retry_state):
"""
retry_error_callback: 最终重试失败后的回调
可以用来记录日志、发送告警、返回降级结果等
"""
logger.error(f"重试 {retry_state.attempt_number} 次后仍失败")
return {"status": "error", "message": "服务暂时不可用"}
@retry(
# 最多重试 3 次
stop=stop_after_attempt(3),
# 指数退避:1s → 2s → 4s → 5s(上限)
wait=wait_exponential(multiplier=1, min=1, max=5),
# before_sleep: 每次重试前(即将 sleep 时)执行
before_sleep=log_retry_attempt,
# after: 每次重试后执行(无论成功失败)
after=after_log(logger, logging.INFO),
# retry_error_callback: 最终重试失败后执行的回调
# 设置后不会抛出异常,而是返回回调函数的返回值
retry_error_callback=handle_retry_error,
# reraise=True: 最终抛出原始异常(设置 retry_error_callback 后此参数无效)
reraise=True
)
def critical_operation():
# 业务逻辑
pass
优缺点对比
| 维度 | 优势 (Pros) | 局限 (Cons) |
|---|---|---|
| 易用性 | 装饰器语法简洁,声明式配置 | 复杂策略参数组合可能混乱 |
| 灵活性 | 策略组件可任意组合 | 某些高级场景需要自定义类 |
| 可维护性 | 重试逻辑与业务分离 | 过度使用会掩盖底层问题 |
| 性能 | 零运行时开销(无依赖) | 同步重试阻塞调用线程 |
| 生态 | 与 AsyncIO 兼容 | 回调函数签名不够直观 |
避坑指南与最佳实践
⚠️ 常见陷阱
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 无限重试 | 未设置 stop 条件导致死循环 |
必须 设置 stop=stop_after_attempt(3) 或 stop=stop_after_delay(10) |
| 重试风暴 | 所有客户端同时重试导致雪崩 | 使用 wait=wait_exponential(multiplier=1, min=1, max=10) + wait_random(min=1, max=3) 添加抖动 |
| 非幂等操作 | 重试导致数据重复 | 仅对幂等操作(如查询)使用重试,写操作慎用 |
| 异常丢失 | 默认不重抛异常难以调试 | 设置 reraise=True(默认),或使用 retry_error_callback 自定义错误处理 |
💡 最佳实践
- 网络请求 :始终使用
wait_exponential()+ 随机抖动 - 外部 API :设置合理的
stop_after_delay()避免长时间阻塞 - 生产环境:结合日志和监控,记录重试频率和成功率
- 异步代码 :使用
@asyncio.retry装饰器处理协程
与 AsyncIO 结合
python
import asyncio
import aiohttp
from tenacity import (
async_retry, # 异步版本的 retry 装饰器
stop_after_attempt,
wait_fixed
)
# async_retry: 用于异步函数的重试装饰器
# 用法与 @retry 完全相同,但只能装饰 async def 函数
@async_retry(
# 最多重试 3 次
stop=stop_after_attempt(3),
# 每次重试前等待 1 秒
wait=wait_fixed(1)
)
async def async_fetch(url):
# 模拟网络请求延迟
await asyncio.sleep(0.1)
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
# 使用方式:在异步上下文中运行
async def main():
result = await async_fetch("https://api.example.com")
return result
# 运行异步函数
asyncio.run(main())
常见问题
Q: Tenacity 和 retrying 库有什么区别?
A: Tenacity 是 retrying 的重构版本,修复了多个 bug,增加了更多等待策略和回调机制,且持续维护中。
Q: 如何查看实际重试了多少次?
A: 通过 retry_state.attempt_number 在回调中获取,或使用 retry.stop 的统计数据。
Q: 能否在运行时动态修改重试策略?
A: 不支持。每次调用装饰器时会创建独立的重试对象,策略在装饰时确定。