直接用aiohttp.ClientSession()发10000次请求会失败,因默认连接池小(limit=100)、DNS缓存未复用、TCP连接未有效复用,叠加服务端限流或本地端口耗尽,实际并发一两百即卡住或报Too many open files;需显式配置高水位连接池、禁用重定向、用Semaphore节流并发并调高系统文件描述符上限。

为什么直接用 aiohttp.ClientSession() 发 10000 次请求会失败
不是代码写错了,而是默认配置下连接池太小、DNS 缓存未复用、TCP 连接没复用,加上服务端限流或本地端口耗尽,实际并发一两百就卡住或报 ClientOSError: [Errno 24] Too many open files。关键不在“异步”本身,而在如何让底层 HTTP 连接真正高效复用。
必须调大连接池并禁用自动重定向
默认的 aiohttp.TCPConnector 只允许 100 个并发连接,且开启重定向会额外消耗连接和时间。每秒万级请求需要显式配置高水位连接池,并关闭非必要功能:
connector = aiohttp.TCPConnector(
limit=1000, # 同时最多打开 1000 个 TCP 连接
limit_per_host=1000, # 单 host 也允许 1000,避免被域名限制
keepalive_timeout=30, # 复用空闲连接最长 30 秒
enable_cleanup_closed=True,
)
session = aiohttp.ClientSession(
connector=connector,
timeout=aiohttp.ClientTimeout(total=5),
# 关键:禁用重定向,自己处理 302/301(否则跳转会吃掉额外连接)
allow_redirects=False,
)- 不设
limit_per_host会导致多个子域名(如api1.example.com和api2.example.com)被当作不同 host,各自只分到 100 连接,总并发上不去 -
keepalive_timeout设太短(如 0)会让连接频繁重建;设太长(如 300)则空闲连接占内存,30 秒是常见平衡点 - 务必关掉
allow_redirects,否则一次 302 跳转等于两次请求+两次连接开销
用 asyncio.Semaphore 控制并发节奏,别全扔进事件循环
把 10000 个 session.get() 全部 await asyncio.gather(...) 一起发,会瞬间打爆连接池和本地端口。正确做法是用信号量节流,维持稳定并发数(比如 500),再分批提交:
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
sem = asyncio.Semaphore(500) <p>async def fetch(url): async with sem: # 每次最多 500 个在跑 try: async with session.get(url, raise_for_status=True) as resp: return await resp.text() except Exception as e: return str(e)</p><p>tasks = [fetch(url) for url in urls[:10000]] results = await asyncio.gather(*tasks, return_exceptions=True)
- 信号量值(如 500)不是越大越好——要匹配
TCPConnector.limit,一般设为后者的 0.5~0.8 倍更稳 - 必须用
return_exceptions=True,否则一个请求出错整个gather就中断 - 别在循环里反复
await session.get(),那会退化成串行;必须先构造好所有 task,再统一gather
DNS 和 SSL 开销不能忽略:预解析 + 禁用证书验证(仅测试)
高频请求下,每次 DNS 查询(平均 ~20ms)和 TLS 握手(~50ms)会成为瓶颈。生产环境应确保 DNS 提前解析并缓存,开发压测可临时绕过:
立即学习“Python免费学习笔记(深入)”;
- 用
aiodns预解析域名:resolver = aiodns.DNSResolver(loop=loop); await resolver.gethostbyname("api.example.com", socket.AF_INET),之后所有请求复用 IP - SSL 验证在压测时可关(
ssl=False),但仅限内网或可信目标:session.get(url, ssl=False) - Linux 下记得调高文件描述符上限:
ulimit -n 65536,否则Too many open files会早于业务逻辑报错
真正卡住你的往往不是 Python 代码,而是操作系统网络栈、目标服务的反爬策略、以及你没意识到的 DNS/TLS 隐性延迟。每秒万次不是靠堆并发数,而是让每个连接尽可能少做无用功。

















