如何将WebSocket应用改造为无状态微服务并实现水平扩展?
无状态微服务改造与WebSocket房间水平扩展方案
一、核心思路:状态外置+精准路由
要实现无状态化,核心是把房间状态、连接映射从服务实例内存迁移到分布式共享存储,同时通过网关/服务发现组件基于共享存储的信息进行精准路由,彻底摆脱实例本地状态的依赖。
二、无状态化改造步骤
- 房间元数据外置:把房间ID、成员列表、第三方流关联信息等核心状态存入Redis(Hash结构最适合),示例如下:
所有服务实例对房间状态的读取、更新都直接操作Redis,本地不再缓存任何持久化状态。# Redis键值示例 room:{room_id} -> { "instance_id": "service-node-01", "members": ["userA", "userB"], "stream_config": {"tcp_addr": "xxx.xxx.xxx.xxx:8080"} } - 连接映射外置:将用户ID、房间ID与连接实例的对应关系也存入Redis,用字符串键值对存储:
这样任何服务实例都能通过用户ID或房间ID查到对应的连接信息。connection:{user_id} -> "{room_id}:{instance_id}"
三、实例路由与房间状态定位逻辑
1. 用户创建房间(用户A)
- 用户A向网关发送创建房间请求;
- 网关从服务注册中心选取一个可用实例(可按轮询、最少连接等负载均衡策略);
- 网关将房间ID与选中的实例ID写入Redis的房间元数据;
- 网关返回房间ID和实例的WebSocket连接地址给前端,前端直接与该实例建立WebSocket连接;
- 实例接收连接后,更新Redis中房间的成员列表和用户的连接映射。
2. 用户加入房间(用户B)
- 用户B向网关发送加入房间请求,携带目标房间ID;
- 网关直接从Redis的房间元数据中查到该房间对应的实例ID;
- 网关返回该实例的WebSocket连接地址给前端,前端与该实例建立连接;
- 实例接收连接后,更新Redis中房间的成员列表和用户的连接映射。
四、第三方TCP数据转发处理
有两种可靠方案可选:
方案1:直接路由到目标实例
- 第三方服务在推送数据前,先调用查询接口(或直接访问Redis),传入房间ID获取对应的实例ID;
- 第三方服务将二进制数据通过TCP直接发送到该实例;
- 实例收到数据后,遍历该房间内的所有WebSocket连接,将数据推送给用户。
方案2:消息队列解耦
- 第三方服务将二进制数据发送到消息队列,以房间ID作为消息的主题(Topic)或键(Key);
- 每个服务实例订阅自己负责的房间对应的主题;
- 实例收到队列中的数据后,推送给对应房间的WebSocket用户。
这种方案更适合高并发场景,即使实例临时不可用,数据也会存在队列中,不会丢失。
五、故障转移与高可用处理
- 实例心跳检测:每个服务实例每隔3-5秒向Redis更新心跳记录(
instance:{instance_id} -> {last_heartbeat: 1699999999}); - 异常实例清理:网关或专门的监控服务定期扫描Redis中的实例心跳记录,清理超过10秒未更新的实例;
- 房间重新分配:对异常实例关联的所有房间,重新从服务注册中心选取可用实例,更新Redis中的房间-实例映射,并通过WebSocket主动通知房间内的用户重新连接到新实例。
内容的提问来源于stack exchange,提问作者user6342701
相关产品推荐
相关产品推荐

