单机撑住5000+活跃WebSocket连接的关键在于规避三处陷阱:用list存连接导致内存泄漏(需改用set并捕获异常清理)、for-loop串行广播引发延迟(应改用asyncio.gather限流并发)、混入同步调用阻塞事件循环(所有IO/CPU密集操作必须异步化或线程池处理)。

单机撑住 5000+ 活跃 WebSocket 连接不难,但卡在 active_connections.append(websocket)、for conn in connections: await conn.send_text() 或混入 time.sleep() 这几处,就必然内存暴涨、广播延迟飙升、连接假死。
为什么用 list 存连接会内存泄漏
常见写法是定义全局 active_connections = [],每次 await websocket.accept() 后 append(websocket);问题在于:客户端异常断开(关浏览器、断网)时,websocket.send_text() 会抛出 WebSocketDisconnect 或 OSError,而代码若没捕获,该连接对象就永远留在列表里 —— 对象引用不释放,Python GC 不回收,内存持续上涨。
必须改用 set:它天然去重,且删除操作平均 O(1)。每次广播前必须 try/except 包裹 send_text(),捕获 WebSocketDisconnect 和通用 Exception,并在 except 块中执行 connections.discard(websocket)。不要依赖 finally 清理——异常可能发生在 send 之前(比如网络缓冲区满),finally 未必执行。
广播消息不能 for-loop + await 串行发
当连接数超 200,for conn in connections: await conn.send_text() 是严重瓶颈。每个 await 都要等前一个完成,2000 个连接 × 平均 5ms 发送耗时 = 10 秒纯等待,用户感知就是“消息发出去没反应”。
立即学习“Python免费学习笔记(深入)”;
改用 asyncio.gather(*[conn.send_text() for conn in connections], return_exceptions=True)。return_exceptions=True 很关键:避免单个连接失败导致整个广播中断。但注意别无限制并发 —— 10000 个并发 send_text() 可能压垮 socket 缓冲区,建议加限流:asyncio.Semaphore(50) 控制同时发送数。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
async def websocket_endpoint() 里绝对不能出现同步阻塞
所有耗时操作都必须异步化,否则卡死整个事件循环,所有连接一起变卡。这不是“慢”,而是“全停”。
- 禁止
time.sleep(),改用await asyncio.sleep() - 禁止
requests.get(),改用aiohttp.ClientSession或httpx.AsyncClient - 禁止
json.loads(big_json_str)这类 CPU 密集型操作在协程中直接调用,应丢给loop.run_in_executor() - 未加
await的协程调用(如漏写await db.query())会导致逻辑静默失效,连接卡住
Uvicorn 启动参数直接影响长连接稳定性
默认配置下,Uvicorn 的 workers 数、limit-concurrency、ws-ping-interval 等参数不调优,单机很难稳定跑满 3000+ 连接。
推荐启动命令示例:
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2 --limit-concurrency 2048 --ws-ping-interval 30 --ws-ping-timeout 10 --timeout-keep-alive 5
其中 --limit-concurrency 是关键:它限制每个 worker 处理的并发请求数(含 WebSocket 连接),防止内存被撑爆;--ws-ping-interval 和 --ws-ping-timeout 联合控制心跳探测,避免连接因中间设备(NAT、代理)静默断连后残留。
真正容易被忽略的是:WebSocket 连接生命周期和 HTTP 请求完全不同,它不按“请求-响应”模型流转,很多中间件(如日志、鉴权装饰器)若没做异步适配或连接级缓存清理,会在连接维持数小时后突然引发 RuntimeError: Event loop is closed 或 Task was destroyed but it is pending! —— 这类错误不会立刻报出,但会在高负载下集中爆发。


















