这类问题我又碰上了一次。python 3.11 用 asyncio.gather 一次发 1000 个 aiohttp 请求调 embedding 接口,跑到十几秒报 OSError: [Errno 24] Too many open files,机器的 nofile 上限是 1024。

第一反应是「aiohttp 的 connector limit 默认 100,但 DNS 解析还会额外占 fd」,验证下来不成立。

真正麻烦的地方在于,几个原因的现象几乎一样。每个请求各建一个连接,没有复用连接池;没有用 Semaphore 限制同时在飞的协程数;异常路径里 response 没释放,连接一直挂着。

上来先看当前进程的 fd 上限与已用数量

python -c "import resource,os;print(resource.getrlimit(resource.RLIMIT_NOFILE),len(os.listdir('/proc/self/fd')))"

给 gather 套一层 50 的并发闸门

python -c "import asyncio;sem=asyncio.Semaphore(50);print(sem)"

显式限定连接池大小再压一次

python -c "import aiohttp;c=aiohttp.TCPConnector(limit=50,limit_per_host=10);print(c.limit)"

剩下的就是用 ss 统计进程的 ESTABLISHED 连接数

ss -tanp | Select-String 8000 | Measure-Object

排查用的命令都是临时敲的,没有写成脚本,事后才整理。

手上没有现成的复现脚本,就照着线上请求手工拼了一个。

改完先别撤,确认这几项。1000 条请求全部返回,峰值 fd 稳定在 260 左右,ESTABLISHED 连接不超过 50,日志里不再出现 Too many open files。

提醒一点。gather 本身不控并发,它只是把协程一起点着,真正的闸门要自己加,连接池大小和 Semaphore 取同一个值最省心。报错文案容易误导,真正的原因往往在相邻那一层的日志里。

作者 9823189