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 块里决定是否重试:
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 <h3>用 <code>tenacity</code> 库简化重试逻辑,但要注意它的默认策略不兼容异步</h3><p><code>tenacity</code> 是 Python 里最成熟的重试库,但它开箱即用的是同步版本。直接装饰异步函数会报 <code>RuntimeWarning: coroutine 'xxx' was never awaited</code>。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts"><img
src="https://img.php.cn/upload/skill/000/000/081/179231049778675.jpg" alt="361SCAN security check your skill python scripts" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts" class="overflowclass">361SCAN security check your skill python scripts</a>
<p class="overflowclass">扫描并分析已安装技能,用于查看指定技能目录的名称、描述和详情,或列出所有已安装的技能。</p>
</div>
<a rel="nofollow" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>必须用 <code>tenacity.AsyncRetrying</code> 并配合 <code>await</code> 调用:</p>
- 安装后要确认版本 ≥ 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 里重建
重试本身不难,难的是在异步上下文里保持连接复用、退避合理、错误分类清晰——这几个点漏掉任意一个,线上就容易出现请求堆积或雪崩。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










