直接用aiohttp.ClientSession会崩,因未管控连接池、未控制并发粒度、未设置超时:默认limit=100且limit_per_host=10易触发429,不复用session导致文件描述符耗尽,无timeout则卡死事件循环。

为什么直接用 aiohttp.ClientSession 会崩?
不是并发数设得越高越好。很多新手一上来就把 asyncio.gather 塞满 1000 个任务,结果报 OSError: [Errno 24] Too many open files 或者目标服务器直接返回 429。根本原因是:没管连接池、没控并发粒度、没设超时。
-
aiohttp.ClientSession必须复用,不能每个请求都新建一个——否则 TCP 连接反复创建销毁,系统文件描述符迅速耗尽 - 默认连接池大小是 100,但这是全局上限;单域名默认只允许 10 个并发连接,容易被封 IP
- 没设
timeout的请求可能卡死整个事件循环,拖垮所有后续任务
aiohttp.TCPConnector 怎么调才不翻车?
连接池参数不是拍脑袋定的。它直接决定你能撑住多少并发、会不会被限流、有没有资源泄漏。
-
limit=0表示不限制总连接数——危险!建议设为1000(4 核机器上限) -
limit_per_host=100是关键:防止单域名连接打满,避免触发服务端反爬(比如 Cloudflare 的速率检测) -
keepalive_timeout=30必开:复用空闲连接,减少 TLS 握手开销;低于 5 秒基本等于没开 -
force_close=False别动,默认就是 False,改了反而破坏 keep-alive
怎么让 500 个监测 URL 真正“并发跑”,又不被封?
光靠连接池不够,必须在任务层加一道闸门。信号量(asyncio.Semaphore)才是控制实际并发数的主力。
- 设
semaphore = asyncio.Semaphore(50):同一时间最多 50 个请求在跑,其余排队等 - 必须用
async with semaphore:包裹整个session.get()调用,而不是只包await response.text() - 别把信号量塞进
ClientSession初始化里——它是任务级控制,不是连接级 - 如果监测的是不同域名(比如 200 个 API + 300 个前端页面),按域名分组+独立信号量更稳
失败请求怎么自动恢复,又不雪崩?
网络抖动、临时 503、DNS 超时太常见。裸写 try/except 不够,要带退避和状态判断。
立即学习“Python免费学习笔记(深入)”;
- 用
response.raise_for_status()主动抛异常,别只靠if resp.status != 200 - 重试只针对
aiohttp.ClientConnectorError、asyncio.TimeoutError这类网络错误;HTTP 404 或 403 就别重试了 - 指数退避:第一次等 1s,第二次 2s,第三次 4s —— 用
await asyncio.sleep(2 ** attempt) - 加
trace_id到请求头里(如{"X-Trace-ID": str(uuid.uuid4())}),出问题能快速定位是哪个 URL 卡住了
真实压测中,limit_per_host 和 semaphore 的组合值往往比理论最大并发更重要。调参时先从 20 开始,观察成功率和响应 P95,再逐步往上加——没人靠猜能把 1000 并发跑稳。


















