asyncio中aiohttp重试必须用await asyncio.sleep()而非time.sleep(),需封装独立协程、按状态码判断是否重试(如5xx或连接异常才重试)、采用指数退避(上限10s)并设置总超时(如asyncio.wait_for),raise_for_status=True仅抛异常不触发重试。

asyncio 里用 aiohttp 发请求时,重试逻辑不能直接套同步的 time.sleep
异步环境里调用 time.sleep(5) 会阻塞整个事件循环,所有并发请求都卡住。必须用 await asyncio.sleep(5) 替代。常见错误是写成 time.sleep() 后发现重试期间其他协程完全不执行。
实际重试建议封装成独立协程,避免在业务逻辑里混杂 await asyncio.sleep() 和异常捕获:
- 重试前先检查是否值得重试:比如
401或403一般不该重试,而502、503、504或网络断连(aiohttp.ClientConnectorError)才需要 - 指数退避比固定间隔更友好:第 1 次等 1s,第 2 次等 2s,第 3 次等 4s,上限建议设为 10s 防止拖垮下游
- 别忘了设置总超时:
asyncio.wait_for(task, timeout=30),否则重试 3 次 + 每次等 10s 可能导致单次请求耗时 30s 以上
aiohttp.ClientSession 的 raise_for_status=True 不会自动触发重试
这个参数只控制是否抛出 aiohttp.ClientResponseError,它本身和重试无关。很多人以为开了这个就“自动处理失败”,结果 5xx 响应直接崩掉,没走重试分支。
正确做法是显式检查状态码或捕获异常,并在 except 块里决定是否重试:
立即学习“Python免费学习笔记(深入)”;
try:
async with session.get(url) as resp:
resp.raise_for_status() # 这里抛 ClientResponseError
return await resp.json()
except (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError, asyncio.TimeoutError) as e:
if retry_count < max_retries:
await asyncio.sleep(backoff_delay)
return await fetch_with_retry(session, url, retry_count + 1, max_retries)
raise
except aiohttp.ClientResponseError as e:
if e.status in (502, 503, 504):
if retry_count < max_retries:
await asyncio.sleep(backoff_delay)
return await fetch_with_retry(session, url, retry_count + 1, max_retries)
raise用 tenacity 库简化重试逻辑,但要注意它的默认策略不兼容异步
tenacity 是 Python 里最成熟的重试库,但它开箱即用的是同步版本。直接装饰异步函数会报 RuntimeWarning: coroutine 'xxx' was never awaited。
必须用 tenacity.AsyncRetrying 并配合 await 调用:
- 安装后要确认版本 ≥ 8.0.0,老版本不支持异步
- 不要写
@retry(...),得写@AsyncRetrying(...).wraps,且被装饰函数必须是async def - 停止条件推荐
stop=stop_after_attempt(3),等待策略用wait=wait_exponential(multiplier=1, min=1, max=10) - 注意它不会自动处理
asyncio.TimeoutError,需手动加到retry_if_exception_type
重试时别重复构造 ClientSession,也别在每次重试里传新 session
每个 aiohttp.ClientSession 对应一个连接池,频繁新建 session 会导致端口耗尽、TIME_WAIT 堆积,甚至触发系统级限制(如 “Too many open files”)。常见错误是在重试函数里写 async with aiohttp.ClientSession() as session:。
正确姿势是让 session 由调用方传入并复用:
- session 应该在请求生命周期外创建,比如在 FastAPI 的
lifespan里初始化,或作为类实例属性持有 - 重试函数签名应为
async def fetch_with_retry(session: aiohttp.ClientSession, ...),而不是自己 new - 如果用了
tenacity,确保 session 是闭包变量或通过参数传入,不能在before/afterhook 里重建
重试本身不难,难的是在异步上下文里保持连接复用、退避合理、错误分类清晰——这几个点漏掉任意一个,线上就容易出现请求堆积或雪崩。


















