根本原因是操作系统文件描述符限制(如linux默认1024)与aiohttp默认连接池配置(limit=100、limit_per_host=30)共同导致socket耗尽,引发clientconnectorerror或too many open files;需复用session、显式配置tcpconnector、配合semaphore节流并分层设超时。

为什么直接用 aiohttp.ClientSession 发 1000 个并发会报错?
常见现象是抛出 ClientConnectorError 或 Too many open files,根本原因是操作系统对单进程文件描述符(包括 socket)数量有限制,而每个 aiohttp 连接默认占用一个 socket。Python 默认未调大 ulimit,Linux 下通常只有 1024,实际能建的并发连接远低于此(DNS 解析、TLS 握手、TIME_WAIT 等都会额外占资源)。
解决思路不是硬堆并发数,而是控制连接生命周期和复用率:
- 复用同一个
aiohttp.ClientSession实例(不要每次请求都新建) - 显式设置
connector限制最大连接数和每 host 并发数 - 避免在循环里用
await session.get(...)不加节制地发请求,需配合asyncio.Semaphore或asyncio.gather批量控制 - 确认目标服务是否真能承受 QPS=1000 —— 很多 Web 框架默认单 worker 只能处理几十并发,压测前先查后端瓶颈
如何配置 aiohttp.TCPConnector 避免连接耗尽?
关键参数不是“开得越大越好”,而是匹配你的场景:如果压测单个域名,重点调 limit_per_host;如果是分散到多个子域名或 IP,可适当提高 limit。默认值(100/100)在千级并发下明显不足。
示例配置:
import aiohttp import asyncio <p>connector = aiohttp.TCPConnector( limit=1000, # 总连接池大小 limit_per_host=100, # 每个 host 最大连接数(防止单点打爆) keepalive_timeout=30, # 复用空闲连接最长等待时间(秒) pool_recycle=3600, # 强制回收连接(防止 DNS 变更后 stale) ) session = aiohttp.ClientSession(connector=connector) </p>
注意:pool_recycle 是 aiohttp 3.9+ 才支持的参数,旧版本需手动定期重建 session;keepalive_timeout 过短会导致频繁重连,过长则可能卡住失效连接。
怎么安全地发起每秒 1000 次请求而不崩掉自己?
不能靠“一秒内扔 1000 个 await”——这会瞬间冲垮连接池和事件循环调度。正确做法是用固定窗口 + 平滑速率控制:
- 用
asyncio.Semaphore(100)限制同时活跃请求数(比如设为 100),再用asyncio.sleep(0.1)控制每 100ms 启一批,平均下来就是 1000 QPS - 或用
asyncio.gather(*tasks, return_exceptions=True)批量提交,但 tasks 列表长度必须受控(如每次最多 50 个) - 务必加超时:
timeout=aiohttp.ClientTimeout(total=5),否则慢响应会拖垮整个队列 - 记得处理异常:网络错误、HTTP 5xx、JSON 解析失败等都要
try/except,否则一个失败请求可能让整个协程组中断
简单节流示例:
sem = asyncio.Semaphore(100)
<p>async def fetch(url):
async with sem:
try:
async with session.get(url, timeout=5) as resp:
return await resp.text()
except Exception as e:
return f"error: {e}"</p><h1>每 0.1 秒触发一批 100 个请求</h1><p>async def run_batch(urls):
tasks = [fetch(url) for url in urls]
return await asyncio.gather(*tasks, return_exceptions=True)
</p>
为什么开了并发还达不到标称 QPS?
真实吞吐受多个隐性因素制约:DNS 查询阻塞(尤其未开启 resolver 缓存)、TLS 握手延迟(可启用 ssl=False 测试纯 HTTP)、目标服务 TCP backlog 拥塞、本地网卡丢包、甚至 Python GIL 在大量回调时的调度抖动。
排查建议:
- 先用
curl -o /dev/null -s -w "%{http_code}\n" http://target/单请求测试基线延迟 - 用
aiohttp发 10 并发看是否接近 10× 单请求延迟,若差距大,说明客户端有瓶颈 - 检查
netstat -an | grep :80 | wc -l看 ESTABLISHED 连接数是否卡在 connector.limit 附近 - 禁用日志:
logging.getLogger("aiohttp").setLevel(logging.WARNING),DEBUG 日志会严重拖慢速度
真正稳定的千 QPS 压测脚本,往往需要结合连接池调优、速率限流、错误熔断和结果采样,而不是单纯堆并发数。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











