asyncio.create_task()非必须,应避免盲目创建任务;需用wait_for设超时、semaphore限频、禁用websockets默认心跳、捕获recv异常并显式close、元数据外存而非python对象引用。

asyncio.create_task() 是必须的,但别在连接建立时就盲目调用
千万级连接不是靠堆任务数量实现的,而是靠复用事件循环和避免阻塞。每个 WebSocket 连接对应一个协程,但直接在 accept 后立刻 asyncio.create_task(handle_client(websocket)) 会导致任务调度器过载——尤其当连接瞬时涌入时,任务对象本身有内存开销,且 Python 的 asyncio.Task 默认不设取消超时,积压未调度任务会拖慢整个 loop。
实操建议:
- 用
asyncio.wait_for()包裹 handler,设置硬性超时(如120秒),防止异常协程长期挂起 - 对新连接做简单准入控制:比如基于 IP 哈希限频、或用
asyncio.Semaphore控制并发 accept 数(例如只允许每秒最多500个新连接进入 handler) - 不要在 handler 内部再嵌套
create_task去处理心跳或消息分发——改用单个长生命周期协程配合asyncio.Queue消费
用 websockets 库时,务必关闭 ping_timeout 和 ping_interval
默认情况下,websockets 服务端会每 20 秒发一次 ping,并等待 20 秒 pong 响应。千万连接下,这会产生海量定时器和 socket 系统调用,CPU 和内核调度压力陡增,且多数客户端(尤其是移动 App)根本不会回 pong,导致大量连接被误判为断连并触发清理逻辑。
实操建议:
- 初始化 server 时显式禁用:
ping_interval=None, ping_timeout=None - 心跳交给业务层:让客户端定期发
{"type": "ping"},服务端用普通消息处理逻辑响应,这样可统一走消息队列、支持按需开关、也方便埋点统计活跃度 - 若必须保活,用更轻量的
transport.get_extra_info("sockname")配合select.epoll级检测(需自定义 transport)
recv() 必须配合异常捕获和连接状态检查
websocket.recv() 在连接异常关闭时会抛出 ConnectionClosedError 或 ConnectionClosedOK,但这些异常不是“错误”,而是正常连接终止信号。如果没捕获,协程会崩溃退出,导致连接泄露(task 消失但底层 socket 未 close);更糟的是,某些代理或 NAT 设备会静默丢包,recv() 可能永久挂起,吃光所有可用协程栈。
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
实操建议:
- 总是用
try/except (ConnectionClosedError, ConnectionClosedOK, asyncio.TimeoutError)包裹recv() - 在
except块里显式调用await websocket.close(code=1000),确保 socket 资源释放 - 加一层读超时:
await asyncio.wait_for(websocket.recv(), timeout=60),避免单连接卡死整个协程 - 不要依赖
websocket.closed属性判断状态——它可能滞后于实际 TCP 断连,应以 recv 异常为准
连接元数据不能存 Python 对象引用,得用整数 ID + 外部存储
每个连接绑定用户 ID、房间号、权限等级等信息很常见,但如果把这些数据作为属性挂在 WebSocket 实例上(比如 ws.user_id = 12345),在千万连接规模下,Python 对象头 + 引用计数 + 字典哈希表会带来不可忽视的内存膨胀(实测单连接多存 3 个 str 属性,内存增长约 128 字节,千万连接就是 1.28GB)。
实操建议:
- 给每个连接分配一个递增
intconn_id(用itertools.count()),所有业务元数据通过conn_id查外部缓存(如 Redis Hash 或共享内存 mmap) - 避免在 handler 协程中做同步 Redis 查询——改用
aioredis或redis-py的 async client,并设置连接池大小上限(比如minsize=10, maxsize=50) - 连接断开时,仅删 Redis 中的
conn_id映射,不要试图遍历 Python 对象图做清理
真正难的不是写出让一万个连接跑起来的代码,而是让系统在连接数从十万跳到八百万时,GC 不抖动、loop 不延迟、FD 不耗尽——这些都藏在 timeout 设置、异常分支、资源释放路径的细节里。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










