很多人以为:给 SpreadJS 协同服务套上负载均衡器,多服务器部署就自动通了。实际上,负载均衡器只管"把新连接分到哪个节点",节点之间的消息同步它一概不管。
我们从两节点对照实验开始,逐步验证 nginx + 三节点的完整链路。这篇文章面向准备接入多服务器协同的开发者,重点是怎样判断配置是否真的生效,而不是 LB 吞吐压测。
三条路径:连接、实时消息与文档状态
我们按以下职责梳理部署:
| 组件 | 负责什么 | 验证时看什么 |
|---|---|---|
| LB | 为新连接选择节点,代理后续流量 | 连接实际落在哪个节点,WebSocket 是否保持 |
| Socket.IO Redis adapter | 让各节点通过 Redis Pub/Sub 转发实时消息 | 另一节点的客户端是否收到消息 |
| 共享 OT 存储 | 保存文档状态和操作版本 | 新加入、重连后的版本和内容是否正确 |
OT 指 Operational Transformation,是协同编辑中处理并发操作的一类机制。这里的 OT 存储负责文档与操作历史;Redis Pub/Sub 是实时消息通道,不承担文档落库。

蓝色路径代理客户端连接,橙色路径通过 Redis 转发实时消息,绿色路径由协同节点读写共享 OT 存储。Redis 与文档存储之间没有直接写入路径。
WebSocket 建好后,后续编辑沿现有连接传输,LB 不会为每次编辑重新选择节点。共享数据库里有最新文档,也不能代替实时通知:连接在另一节点的用户仍需要收到操作广播。
对照实验:开启与关闭 adapter
我们先关闭 adapter,让两个客户端分别直连不同节点,加入同一房间,再由一端发送一条测试消息。随后只改变 adapter 配置,为两个节点接入同一 Redis。
| 观测项 | Adapter 关闭 | Adapter 开启 |
|---|---|---|
| 远端客户端收到的测试消息 | 0 条 | 1 条,payload 一致 |
| 独立 Redis 订阅者捕获的对应消息 | 0 条 | 1 条 |
| 发送端收到自己的消息 | — | 0 条 |
开启 adapter 后,独立 Redis 订阅者在目标房间 channel 上捕获到对应消息,内容与测试消息完全一致(约 125 字节)。这一步很关键——它证明消息确实通过 Redis Pub/Sub 跨节点转发,而不是走了其他隐式路径。
关闭 adapter 时,发送端没有报错或自动重试。因此,我们不会用“发送函数没有异常”作为跨节点投递成功的判据,必须检查远端客户端。
这里还有一个实际遇到的计数问题:消息成功到达远端客户端后,接收节点的 messagesSeen 仍没有增加。这个计数器只统计从本节点客户端进入的帧,而 Redis adapter 的远端投递没有经过同一钩子。我们最终用客户端收到的内容和独立 Redis 订阅记录交叉验证,避免把观测点选错当成消息未到。
完整链路:nginx 与三节点验证
两节点对照之后,我们加入 nginx 和三个协同节点。完整链路测试中,6 个保持连接的客户端分布在 3 个后端;跨节点消息收到 1 条且内容一致,Redis 捕获到对应消息,发送端和其他房间均未收到。针对运行中部署的 8 项断言通过,另一个自动启动整套环境的测试也通过 9 项断言。
浏览器验证分为两层。基础消息页面在不同节点间双向收发,连续运行 4 次通过;真实 SpreadJS 工作簿另有端到端验证,7 项断言通过,两个方向修改单元格都能同步。我们从节点的状态接口确认落点,避免两个客户端实际落在同一节点却被误当成跨节点测试。
这些实验确认投递和隔离机制,没有给出可用于生产验收的广播延迟分布或高并发吞吐数字。
两个容易踩的测试坑
坑一:Redis 不可达时,启动会卡在默认重连。我们为初始 Redis 连接增加了 5 秒超时,并在建立连接后做 PING 检测;失败则让节点直接启动报错,而不是无限等待。注意,这个检查只能发现启动时的连接问题,不能代替运行中的消息链路监控。
坑二:只反复建立立即关闭连接,测不出真实并发。验证 least_conn 分配时,我们改为保持多条长连接,并读取实际后端落点。验证跨节点编辑前,也先确认客户端确实位于不同节点,避免两个客户端落在同一节点却被误当成跨节点测试。
扩缩容与故障恢复验证
扩容实验中,我们启动了使用同一 Redis 和共享 OT 存储的新节点,已有节点没有重启。客户端直连新节点后,与旧节点双向编辑同步。这证明新节点能加入协同集群;实验没有自动修改 nginx 的 upstream 列表,入口接入仍要单独处理。
节点故障实验则强制停止承载客户端的进程,再检查重连和重连后的双向编辑。在两个客户端分别位于不同节点的一组中,受影响客户端记录到 1 次断开、1 次重连,另一客户端没有断开。我们验证的是故障后能恢复协同,没有测出通用恢复时限。
部署验收时,我们会依次核对:
- 每个节点的 Redis adapter 与共享 OT 存储配置正确,房间标识一致。
- LB 支持 WebSocket upgrade,连接超时符合业务需要,并能识别失效后端。
- 两个客户端明确位于不同节点,双向编辑及时到达,其他房间不收到消息。
- 停止一个节点后,客户端能重连,版本与内容恢复,后续双向编辑仍正常。
总结一下:多节点配置是否正确,不能只看"页面能打开"或"文档最终一致"。连接落点、实时投递、文档恢复这三条路径必须分别验证——缺了任何一条,都可能在生产环境爆雷。
参考资料
- SpreadJS 协同服务 多服务器部署官方文档
- Socket.IO Redis Adapter 官方文档