python异步重试必须用asyncio.sleep()而非time.sleep(),否则阻塞事件循环;推荐tenacity 8.0+,配合@retry、wait_exponential、stop_after_attempt及retry_if_exception_type实现健壮退避重试。

Python 中的异步任务重试不能靠 time.sleep() 硬等,必须用 asyncio.sleep() 配合异常捕获和状态控制,否则会阻塞整个事件循环。
用 tenacity 实现带退避策略的异步重试
tenacity 是目前最稳妥的异步重试方案,它原生支持 async def 函数,且能组合多种条件(如异常类型、返回值、耗时)。
- 安装:
pip install tenacity - 必须用
@retry装饰器配合retry_if_exception_type()或retry_if_result(),直接写try/except + await asyncio.sleep()容易漏掉取消传播 - 退避策略推荐
wait_exponential(multiplier=1, min=1, max=10):首次等 1s,失败后翻倍(2s → 4s → 8s),上限 10s,避免雪崩 - 注意设置
stop_after_attempt(3)或stop_after_delay(30),否则可能无限重试
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type import aiohttp <p>@retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), retry=retry_if_exception_type((aiohttp.ClientError, asyncio.TimeoutError)) ) async def fetch_data(url: str): async with aiohttp.ClientSession() as session: async with session.get(url, timeout=5) as resp: return await resp.json()</p>
手动实现时如何正确处理 asyncio.CancelledError
手动写重试逻辑时,如果任务被取消(比如父协程调用了 task.cancel()),await 会抛出 CancelledError —— 这不是业务异常,不能重试,必须立即传播。
- 在
except Exception:块里直接捕获会吞掉CancelledError,导致协程无法被真正取消 - 正确做法是显式捕获业务异常,其他异常原样抛出:
except (aiohttp.ServerDisconnectedError, asyncio.TimeoutError): - 如果用了
asyncio.wait_for()包裹重试块,它的超时也会触发TimeoutError,需和网络层的TimeoutError区分(后者属于aiohttp模块)
重试期间如何避免并发请求爆炸
多个协程同时失败并立即重试,可能瞬间打垮下游服务。单纯加 asyncio.sleep() 不够,因为不同协程的休眠时间若相同,会同步苏醒。
- 用 jitter(抖动):在退避时间上加随机偏移,例如
await asyncio.sleep(base_delay * (2 ** attempt) + random.uniform(0, 1)) - 对同一资源(如某 API 的某个 user_id)做简单内存级限流:用
asyncio.Lock或字典缓存最近失败时间,10 秒内不重复重试同一键 - 避免在重试装饰器里共享状态;
tenacity的before_sleep回调可用来记录日志或发告警,但别在里面改全局变量
真正的难点不在“怎么重试”,而在于“什么时候不该重试”:幂等性缺失的 POST 请求、已部分生效的支付回调、或上游明确返回 422/400 的业务错误,重试只会让问题更糟。重试逻辑必须和接口语义强绑定,而不是无脑套模板。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











