弹幕系统需兼顾连接稳定性、消息准确性和前端渲染流畅性。关键在退避重连策略、异步广播机制及transform+requestanimationframe渲染,缺一不可。

弹幕功能不是“连上 WebSocket 就能滚动”,而是连接稳不稳、消息发得准不准、前端动得卡不卡的综合结果。绝大多数线上弹幕系统出问题,都卡在重连策略、广播方式或渲染逻辑这三处。
WebSocket 连接必须带退避重连,不能只写 new WebSocket()
浏览器切后台、网络抖动、服务端滚动发布都会触发 onclose,而默认行为是断开即死。不加控制的 new WebSocket(url) 会导致页面卡住或反复报错 Failed to construct 'WebSocket': The URL's scheme must be either 'ws' or 'wss'(常见于 URL 拼错或协议未升级)。
- 首次断开后延迟
1000ms 重试,第二次2000,第三次4000,上限建议设为30000(30 秒),避免雪崩 - 不要只监听
onerror—— 它常不触发;重点处理onclose中的event.code !== 1000(如1006表示异常关闭) - URL 必须携带业务标识,例如
ws://danmu.example.com?videoId=789&clientId=abc123,否则服务端无法做房间隔离和限流 - 连接建立后立即发一条心跳探测(如
{"type":"ping","ts":Date.now()}),收到pong再开始接收弹幕,避免“连上了但没通”
服务端广播必须用 asyncRemote,不能遍历 session.sendText()
Spring Boot + @ServerEndpoint 默认是单线程模型,session.getBasicRemote().sendText() 是同步阻塞调用。连接数超 50 后,IO 等待会拖垮整个容器线程池,表现为你发一条弹幕,所有客户端延迟 2–5 秒才收到。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 强制改用
session.getAsyncRemote().sendText(),把序列化和写入交给容器线程池异步执行 - 广播前按
videoId分组,只推给当前房间的Session,别用全局ConcurrentHashMap<string session></string>存所有连接——内存泄漏风险高,且无法按业务维度清理 -
@OnMessage回调里禁止查库、同步过滤敏感词,应转为异步任务(如发到ThreadPoolTaskExecutor)或预加载规则到本地缓存 - Node.js 用户注意:
io.to(room).emit()安全,io.emit()是全量广播,误用会导致跨房间弹幕泄露
前端渲染必须用 transform + requestAnimationFrame,不能用 left/top + setInterval
用 left 或 top 配合 setInterval 移动 DOM,每帧都触发 layout → paint → composite,3 条弹幕就可能让低端 Android 设备掉帧,表现为弹幕突然跳变、卡顿、甚至暂停滚动。
- 每条弹幕 DOM 必须设固定高度(如
height: 24px),禁用line-height或padding动态撑高,否则重排不可控 - 位移必须用
transform: translateX()+ CSStransition,或更精准地用requestAnimationFrame控制每一帧位置 - 入场间隔需动态调节:根据屏幕高度计算最大并发弹幕数(如
Math.floor(window.innerHeight / 24)),再限制每秒最多新增10条,防止遮挡视频主体 - 时间轴同步靠前端自主决策:服务端只发
timestamp(相对于视频起始时间),前端用video.currentTime实时比对,不依赖服务端匹配——后者在高并发下极易成为瓶颈
真正难的不是“怎么显示一条弹幕”,而是当 2000 人同时发、每秒 300 条进房间时,连接不断、消息不丢、滚动不卡、时间不错位。这些点一旦漏掉一个,上线后就是用户投诉+运营喊你删功能。










