aiohttp默认连接池在50+并发时易溢出,因未控并发、重复创建session、域名分散及keepalive_timeout过长;须配置tcpconnector四参数并用boundedsemaphore协同限流。

ClientSession 不该在循环里反复创建,TCPConnector 参数不能靠默认值,asyncio.Semaphore 必须和连接池参数协同使用——这三件事不做,光调大 limit 只会让溢出来得更晚、更难排查。
为什么aiohttp默认连接池在50+并发时就容易溢出
默认 TCPConnector(limit=100, limit_per_host=100) 看似宽松,但实际极易打满,常见原因有:
- 没用
asyncio.BoundedSemaphore控制并发请求数,大量协程同时调用session.get(),瞬间占满连接池 - 每个请求都新建
ClientSession实例,旧会话没close()就丢弃,连接泄漏而非复用 - 目标域名分散(如
api.a.com、api.b.com),limit_per_host失效,总连接数仍卡在limit上 -
keepalive_timeout过长(默认 15 秒),空闲连接堆积不释放,进一步挤占可用槽位
必须显式配置TCPConnector的四个关键参数
不要依赖默认值。初始化 ClientSession 时,务必传入定制的 TCPConnector:
-
limit=100:全局最大空闲+活跃连接总数,生产环境建议设为 100;本地调试可设为 20 -
limit_per_host=30:单主机(含端口、协议)最多保持 30 连接,防止单站点吃光整个池子 -
keepalive_timeout=30:连接空闲 30 秒后自动关闭,避免长连接滞留;低于 15 秒可能增加 TLS 握手开销 -
force_close=False:设为True会禁用连接复用,完全失去池化意义
示例:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
connector = aiohttp.TCPConnector(
limit=100,
limit_per_host=30,
keepalive_timeout=30,
force_close=False
)
async with aiohttp.ClientSession(connector=connector) as session:
# 所有请求复用该连接池
必须用BoundedSemaphore控制并发请求数
TCPConnector.limit 管的是“能开多少连接”,asyncio.BoundedSemaphore 管的是“同一时刻最多发几个请求”。两者缺一不可:
- 信号量数量建议 ≤
limit_per_host × 主机数;保守起见,直接设为limit_per_host(比如 30) - 必须在
async with semaphore:块内发起请求,不能只在外面await semaphore.acquire()却忘了release() - 若用
asyncio.gather()并发调用,必须提前把所有任务包进信号量上下文,否则无效
最容易被忽略的泄漏点:response没正确关闭
session.get(url) 返回的是 aiohttp.ClientResponse 对象,它底层持有连接。如果不用 async with response: 或手动调 response.release(),连接就不会归还池中:
- 错误写法:
resp = await session.get(url); text = await resp.text()—— 连接泄漏 - 正确写法:
async with session.get(url) as resp: text = await resp.text() - 即使抛异常,
async with也能确保连接释放;手动resp.close()不够,必须resp.release()或用上下文管理器
这个细节在日志里不会报错,但连接数会缓慢上涨,几小时后才突然溢出。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










