Socket.IO 并发握手能力压测报告

2026-07-01 · 独立脚本验证,不依赖 blade-agent 真实代码

背景

预压测显示:50 并发正常,100 并发(50 用户 × 2 任务)成功率仅 39/100,失败全部是 Socket.IO 连接握手失败。 压测期间 CPU 峰值仅 37%、内存 8.9/12.9G、8020 端口连接数峰值只到 68——不是硬件资源瓶颈。

同事的初步结论:根因是 blade-agent 后端单进程单 asyncio 事件循环处理 Socket.IO 握手的能力上限。 本报告通过独立最简脚本验证这个假设。

测试方法

服务端(3 个变体)

均为 python-socketio AsyncServer + ASGI + uvicorn 单进程,与 blade-agent 配置一致

  • 最简服务端 — connect 处理器为空,纯测协议栈吞吐
  • 异步 IO 服务端 — connect 中 await asyncio.sleep(0.05),模拟 50ms 异步 DB/auth 查询
  • 同步阻塞服务端 — connect 中 time.sleep(0.05),模拟 50ms 同步文件/DB 操作冻住事件循环

客户端(2 个)

  • Python asyncio — python-socketio AsyncClient,完整 Socket.IO v4 协议栈
  • Go zishang520 — github.com/zishang520/socket.io 客户端库,完整 Socket.IO v4 协议栈
并发级别:50 / 100 / 200 / 500
连接超时:15 秒
传输方式:WebSocket(与生产环境一致)

测试结果

服务端 客户端 50 100 200 500
最简服务端 Python asyncio 100% 100% 100% 100%
Go zishang520 14% 14% 8% 13%
+50ms 异步 IO Python asyncio 100% 100% 100% 100%
+50ms 同步阻塞 Python asyncio 2% 1% 0% 2%

成功率对比

Python 客户端握手延迟 (ms)

延迟明细(成功连接)

场景 并发 min p50 p95 max 总耗时
最简 + Python 50 20ms 25ms 25ms 26ms 0.03s
100 60ms 69ms 72ms 73ms 0.08s
200 81ms 98ms 100ms 101ms 0.11s
500 240ms 270ms 294ms 297ms 0.32s
异步 IO + Python 50 79ms 81ms 83ms 83ms 0.08s
100 110ms 116ms 119ms 120ms 0.12s
200 129ms 141ms 150ms 150ms 0.15s
500 263ms 294ms 316ms 318ms 0.33s
同步阻塞 + Python 500 3194ms 3194ms 9797ms 9797ms 15.01s

结论

"单进程 asyncio 事件循环处理 Socket.IO 握手的能力上限" — 不成立

纯 Socket.IO 协议栈(python-socketio AsyncServer + ASGI)在单进程单事件循环下可轻松处理 500 并发握手, 成功率 100%,最大延迟仅 297ms。即使 connect 中有 50ms 异步 IO 也完全不影响。 Socket.IO 本身不是瓶颈。

!

真正的瓶颈:事件循环中的同步阻塞调用

一旦 connect 处理器中存在 同步阻塞操作(同步文件读写、同步 DB 查询、CPU 密集计算), 仅 50ms 的阻塞就能让 50 并发成功率暴跌至 2%——完美复现了预压测中 39% 成功率的现象。 同步调用冻住了事件循环,所有其他连接请求排队串行等待。

Go zishang520 客户端库自身存在并发问题

即使面对最简的空 connect 服务端,github.com/zishang520/socket.io 客户端在 50 并发下成功率仅 14%, 绝大部分连接超时。如果压测工具使用了该库,需注意失败可能来自客户端侧。

排查建议

  1. 1 排查 blade-agent 的 connectsession:subscribe 处理链路, grep 同步阻塞调用:time.sleep、同步 open()sqlite3 同步查询、subprocess.run
  2. 2 确认压测客户端使用的 Socket.IO 库——如果是 Go zishang520,需排除客户端侧瓶颈,建议换用 Python 或 JS 客户端对比验证
  3. 3 对已知的同步阻塞操作:短耗时操作改为 await asyncio.to_thread() 卸载到线程池; 长耗时操作考虑异步库替代(如 aiosqliteaiofiles
  4. 4 如需更高并发上限,可考虑 uvicorn 多 worker(--workers N),但前提是先消除同步阻塞——否则多进程只是线性提升,不解决根因

测试环境:macOS Darwin 25.3.0 · Python 3.9 + python-socketio 5.16.3 + uvicorn 0.39.0 · Go 1.22 + zishang520/socket.io v3.0.0