aiohttp高并发崩溃主因是连接池配置与网络行为脱节:默认limit=100看似充足,但未设limit_per_host、keepalive_timeout、ttl_dns_cache,且force_close误配或漏用async with导致连接泄漏,叠加dns解析慢、响应体未释放等隐性瓶颈集中爆发。

并发过高本身不直接导致崩溃,真正出问题的是连接池配置与实际网络行为脱节——aiohttp.TCPConnector 默认不限制连接数,但系统文件描述符、目标服务器并发限制、DNS解析开销、响应体未释放这些隐性瓶颈会集中爆发。
为什么 aiohttp.TCPConnector 默认配置扛不住 50+ 并发
默认 limit=100 看似够用,但问题不在数字本身:
- 没设
limit_per_host→ 所有请求打向同一域名时,可能瞬间建立 80+ TCP 连接,触发目标站反爬或连接拒绝 - 没配
keepalive_timeout→ 连接空闲后不及时复用,反复建连耗尽本地端口(OSError: [Errno 24] Too many open files) - 没开
ttl_dns_cache→ 每个请求都重查 DNS,单域名解析延迟可达 200ms+,协程卡在解析阶段,连接池被“假性占满” -
force_close=True(默认为False)被误设 → 关闭连接复用,彻底废掉 HTTP/1.1 pipeline 效率
async with session.get(url) 漏掉这一步,连接就永远不归还
这是最隐蔽的泄漏点:不使用 async with,或用了但没读完响应体,连接就不会从池中释放。
- 只写
session.get(url)→ 返回ClientResponse对象,但连接仍被占用,直到响应体被读取或超时关闭 - 写了
await response.text()但没加async with→ 异常时连接无法自动归还 - 用
response.content.read()但没await→ 协程挂起,连接卡死,后续所有请求排队等待 - 正确姿势必须是:
async with session.get(url) as response: await response.text()
并发控制信号量和连接池参数必须协同,不能各自为政
asyncio.Semaphore(50) 只控“发起请求数”,而 TCPConnector(limit=20) 控“同时活跃连接数”。两者不匹配,就会出现大量协程在等连接,内存暴涨。
- 信号量设 100,连接池 limit=100 → 表面一致,但 DNS 解析慢、TLS 握手卡顿会让大量协程堵在“连接建立前”,实际连接数远低于 100,吞吐反而下降
- 推荐组合:
Semaphore(50)+TCPConnector(limit=30, limit_per_host=10),留出缓冲空间 - 必须加
connector = aiohttp.TCPConnector(ttl_dns_cache=300),避免每请求一次 DNS 查询 - 禁用
pool_pre_ping类探测逻辑(如 SQLAlchemy 的),aiohttp 层面靠keepalive_timeout和 OS TCP keepalive 更轻量可靠
真正压垮系统的,往往不是并发数字本身,而是连接生命周期管理里那几行被忽略的 async with、那个没设的 limit_per_host、以及 DNS 缓存开关——它们藏在代码角落,却决定整个爬虫是稳定跑通,还是五分钟内静默崩塌。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











