必须用异步发送:spring boot 改用 session.getasyncremote().sendtext();go 用结构体封装+广播协程模型;swoole 用 task 进程异步执行;socket.io 需校验房间存在、关闭压缩、发送 ack 对齐时间。

Spring Boot 里用 @ServerEndpoint 群发弹幕会卡死?
直接遍历 webSocketSet 调用 session.getBasicRemote().sendText(),在连接数超 50 后就明显延迟、CPU 暴涨、甚至阻塞主线程。Spring Boot 内置的 WebSocket 容器(如 Tomcat)默认是同步 I/O,sendText() 会等网络写完才返回,而每个连接的 TCP 缓冲区、Nagle 算法、客户端网络抖动都会放大阻塞时间。
必须改用异步发送:
-
session.getAsyncRemote().sendText()把序列化和网络写交给容器线程池,不阻塞@OnMessage回调 - 发送前加 try-catch,因为异步失败不会抛异常到当前线程,而是触发
Future.setException(),需注册WriteHandler - 广播时先按
videoId或roomId分组,避免把 A 房间的弹幕推给 B 房间用户
Go(gorilla/websocket)怎么安全广播而不丢连接?
用 map[*websocket.Conn]bool 存连接看似简单,但并发读写会 panic;用 sync.Map 又无法在关闭时及时清理。真实场景下,连接断开、客户端切后台、心跳超时都可能让连接残留。
推荐结构体封装 + 广播协程模型:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 每个
Client包含inChan(收消息)、outChan(发弹幕)、closeChan(通知退出) - 所有连接统一注册进一个全局
clients map[string]*Client,key 是roomId:clientId,避免跨房间污染 - 单独起一个 goroutine 监听
join/leave/message三个 channel,集中做广播,不分散在每个连接的Read循环里 - 客户端 ping 超过 30 秒没回,主动
conn.Close()并从 map 删除,否则内存持续增长
PHP(Swoole)用 $server->push() 遍历所有连接,为什么高并发下掉帧?
不是 push() 本身慢,而是每次遍历 $server->connections 都要查一次 fd 表,且同步推送会串行等待每个 socket 的 write buffer 可写。1000 个连接,平均每个耗时 2ms,一轮广播就 2s —— 弹幕时间戳早就过期了。
关键优化点:
- 用
$server->task()把广播逻辑扔进 task 进程异步执行,主进程不卡 - 只对在线且加入指定
room_id的连接 push,别用$server->connections全量扫 - 弹幕消息体必须轻量:只传
{text, color, size, time},别带用户头像 URL 或富文本 HTML - 服务端不校验弹幕内容(如敏感词),放中间件或前置 API 做,WebSocket 层只管转发
Node.js(Socket.IO)用 io.to(room).emit() 就万事大吉?
不是。默认 socket.io 的 emit() 是 JSON 序列化 + base64 编码二进制,对纯文本弹幕来说冗余严重;而且如果房间名拼错(比如传了 "room_123 " 带空格),io.to() 不报错也不广播,弹幕就静默消失了。
实操要点:
- 发送前用
if (io.sockets.adapter.rooms.get(roomId)?.size > 0)显式检查房间是否存在且有人 - 用
socket.compress(false).emit()关闭压缩,弹幕消息太短,压缩反而更慢 - 不要在
connection事件里直接joinRoom(),先验证roomId和userId合法性,否则恶意请求可撑爆内存 - 服务端收到弹幕后,立刻回一个
ack给发送方(含服务端生成的id和serverTime),前端靠这个对齐时间轴










