asyncio.run() 无法直接支撑数万连接,因事件循环缺乏限流、socket未复用、阻塞操作未剥离;须用 asyncio.start_server() 等内置管理器,调优系统参数(somaxconn、file-max)、设置 backlog、避免同步调用,并将耗时操作卸载至线程池或异步驱动。

asyncio.run() 不能直接扛住数万连接
直接用 asyncio.run() 启动一个 async def main(),再在里面用 asyncio.create_task() 拉起几万个协程——这会立刻爆内存或卡死。根本原因不是协程轻量,而是事件循环本身没做连接限流、socket 资源未复用、DNS 解析/SSL 握手等阻塞操作没剥离。
实操建议:
- 必须用
asyncio.start_server()或aiohttp.web.Application这类内置连接管理器,它们自带 backlog 队列和连接复用逻辑 - 避免在协程里写
time.sleep()、requests.get()、sqlite3.connect()等同步阻塞调用 - DNS 查询务必换成
await loop.getaddrinfo()或aiohttp内置解析器,否则每个连接都会卡住事件循环
连接数上万时,TCP socket 参数必须调优
Linux 默认的 net.core.somaxconn(128)和 fs.file-max(通常 8192)会直接掐断新连接,错误现象是客户端收到 ConnectionRefusedError 或服务端日志里反复出现 Resource temporarily unavailable。
实操建议:
- 启动前执行:
sudo sysctl -w net.core.somaxconn=65535、sudo sysctl -w fs.file-max=100000 - Python 侧显式设置 socket 选项:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - 用
ulimit -n 100000提升当前 shell 进程的文件描述符上限
处理数万连接时,别自己写 accept 循环
有人试图用 loop.create_server() + 手动 sock.accept() + loop.create_task() 处理每个 client,这会导致事件循环被 accept 阻塞、新连接排队延迟飙升,且无法利用 asyncio 内部的 epoll/kqueue 批量就绪通知机制。
正确做法是交由 asyncio 原生 server 管理:
import asyncio <p>async def handle_client(reader, writer): data = await reader.read(1024) writer.write(b"OK") await writer.drain() writer.close()</p><h1>自动处理连接队列、背压、超时</h1><p>server = await asyncio.start_server(handle_client, '0.0.0.0', 8000, backlog=2048) </p>
其中 backlog=2048 是关键——它控制内核等待连接队列长度,不设或设太小(如默认 100)会导致高并发下丢连接。
协程里做耗时操作必须拆解或 offload
哪怕只是解析一个 JSON 或校验 JWT,如果单次耗时超过 1ms,在数万并发下就会累积成明显延迟。现象是 asyncio.wait_for() 频繁超时、监控显示 event loop 卡顿(loop stats 中 executing 时间占比过高)。
实操建议:
- JSON 解析用
ujson或orjson,它们比标准库快 3–5 倍且不阻塞循环 - 密码学操作(如 bcrypt.verify)必须用
loop.run_in_executor()扔进线程池,别用asyncio.to_thread()(Python 3.9+ 才有,且默认线程数仅 4) - 数据库查询优先选异步驱动(
asyncpg、aiomysql),而不是用run_in_executor包装psycopg2
真正难的从来不是“怎么写 async 函数”,而是判断哪一行代码实际占用了事件循环时间——这点容易被忽略,但决定了你能不能稳住 2w 连接。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











