先用 websocket + redis + 状态广播模型跑通最小闭环,再按需扩展——90% 的协同场景无需自研“状态同步引擎”。

直接说结论:不用“构建引擎”,先用 WebSocket + Redis + 状态广播模型跑通最小闭环,再按需加 OT/CRDT、离线队列或集群路由——90% 的协同场景根本不需要自研“引擎”。
为什么别一上来就写“状态同步引擎”
所谓“引擎”,常被默认为要支持操作变换、版本向量、冲突自动合并、离线重放……但真实项目里,多数协同需求只是“谁改了标题/光标在哪/当前选中哪个节点”。强行套用 CRDT 或 OT,反而会把 onmessage 里的三行逻辑膨胀成十几个类、一堆序列化规则和难以 debug 的合并异常。
常见错误现象:WebSocket 收到消息后直接 setState,结果多人同时拖拽流程图节点,界面瞬间错乱;或者用 localStorage 存本地操作日志,却忘了跨标签页时不同页面的 localStorage 是隔离的。
- 先确认你的“状态”是否真的需要最终一致:比如聊天室的已读状态,允许短暂不一致比强一致更合理
- 判断同步粒度:是整个文档对象全量替换(
JSON.stringify后发),还是只传变更路径({op: 'set', path: ['nodes', 'a123', 'x'], value: 150}) - 注意平台限制:微信小程序的
WebSocket不支持二进制帧,ArrayBuffer要转Uint8Array再toString(),否则收不到
WebSocket 连接层必须处理的三件事
原生 WebSocket API 极简,但生产环境绕不开重连、心跳、上下文绑定。UNIAPP、React Native、Electron 各端的 onclose 触发时机和错误码还不一样。
- 重连不能立即发起:用指数退避,第一次等 1s,第二次 2s,第三次 4s,上限设为 30s,避免服务端雪崩
- 心跳必须双向:客户端定时发
{"type":"ping"},服务端收到必须回{"type":"pong"};只单边发没用,Nginx 默认 60s 断空闲连接 - 每个连接要绑定业务上下文:比如用户 ID、文档 ID、设备类型,不能只存
socket.id—— Node.js 的ws库里得手动挂socket.userId = xxx,否则广播时不知道该推给谁
示例(Node.js + ws):
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.type === 'auth') {
socket.userId = msg.userId;
socket.docId = msg.docId;
}
});
状态广播怎么避免“自己改自己又收一遍”
这是最常踩的坑:用户 A 修改标题,消息经服务器广播给所有客户端,A 自己的页面也收到了,useState 又执行一次,导致重复渲染甚至逻辑错乱。
- 方案一(推荐):服务端过滤发送目标,不把消息回推给来源客户端。需要你在
send前比对socket.userId和消息里的from字段 - 方案二:客户端加防抖标识,比如发出去的消息带
clientId: 'web-abc123',收到时检查msg.clientId !== localClientId才处理 - 千万别用时间戳或随机数做去重:时钟不同步、并发高时会漏判
注意:如果用了 Redis Pub/Sub 做多进程广播,订阅者无法区分消息来源,只能靠客户端过滤。
跨端状态不一致时优先查这三处
UNIAPP 编译到 H5 和小程序后,看似同一份代码,但状态同步失败往往卡在底层差异上。
- H5 的
WebSocket支持bufferedAmount,可用来节流;小程序不支持,得自己用队列 +setTimeout控制发送频率 - 小程序
onMessage回调里不能直接调用uni.showToast,必须用nextTick或setTimeout包一层,否则报错not in page context - Android 端 WebSocket 在后台被系统回收后,
onClose可能不触发,得配合App.onHide主动 close,再在onShow重建连接
真正复杂的点从来不在“怎么同步”,而在于“怎么让不同平台、不同网络条件、不同生命周期的客户端,在任意时刻都能以可预期的方式响应同一个消息”。协议越简单,越容易覆盖这些边缘case。










