Tenacity 原理与基本使用

目录

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)

stopretry 是两个独立的概念: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 自定义错误处理

💡 最佳实践

  1. 网络请求 :始终使用 wait_exponential() + 随机抖动
  2. 外部 API :设置合理的 stop_after_delay() 避免长时间阻塞
  3. 生产环境:结合日志和监控,记录重试频率和成功率
  4. 异步代码 :使用 @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: 不支持。每次调用装饰器时会创建独立的重试对象,策略在装饰时确定。


参考资料

相关推荐
Myosotis5131 小时前
作业 第三次
开发语言·python
cuber膜拜1 小时前
PyBreaker 原理与基本使用
服务器·网络·python·pybreaker
学Linux的语莫2 小时前
模型转为RKNN格式
python·深度学习·机器学习
Albert Edison2 小时前
【Python】文件
android·服务器·python
未来之窗软件服务2 小时前
服务器运维(三十三)日志分析ssh日志工具—东方仙盟
运维·服务器·ssh·仙盟创梦ide·东方仙盟
沉睡的无敌雄狮2 小时前
可编程数字人落地实践:某省广电用矩阵跃动API重构工作流(选题→政策图谱→方言音色→审稿水印),附Python调度代码
人工智能·python·重构·排序算法·kmeans
梦雨羊2 小时前
搭建服务器进行测试
linux·运维·服务器
3GPP仿真实验室2 小时前
【Matlab源码】6G候选波形:MIMO-OFDM-IM 增强仿真平台
开发语言·网络·matlab
junior_Xin2 小时前
Flask框架beginning4
python·flask