跨服websocket单聊需redis hash+pub/sub实现:hash存储用户-节点映射,pub/sub转发消息;须校验映射有效性、添加from_node/message_id防重放与环路,并在登录时原子更新映射。

跨服 WebSocket 单聊在 Go 集群中无法靠 gorilla/websocket 自动实现——它根本不知道“对方用户在哪台机器上”,调用 conn.WriteMessage() 只会发到本机连接,跨节点消息必然丢失。
为什么本地 map[string]*Client 在集群里失效
单聊依赖“用户名 → 连接实例”的映射,但这个 map 是进程内变量,只存当前节点的连接。A 用户连 node-1,B 用户连 node-2,node-1 的 clients["B"] 为空,sendToUser("B", msg) 直接静默失败。
- 常见错误现象:
hub.sendToUser("B", msg)返回无报错,但 B 客户端收不到;日志里只看到“已调用”,没触发实际写入 - 根本原因:没有跨节点的会话位置注册机制,服务端不具备寻址能力
- 并发安全陷阱:即使单机,
map读写也必须加sync.RWMutex,原生gorilla/websocket示例没包含这层保护
Redis Hash + Pub/Sub 是跨服单发的最小可行组合
不用强一致存储也能跑通单聊,关键是把“查位置”和“投递”拆开:先查用户在哪台节点,再往对应节点频道发消息。Redis Hash 存映射,Pub/Sub 做轻量通知通道。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 上线时写入:
HSET user_node_map "u123" "ws-02"(配合过期时间,如EXPIRE user_node_map:u123 300) - 发消息前查询:
HGET user_node_map "u123",拿到"ws-02"后向频道pubsub:node:ws-02PUBLISH - 各节点启动时
SUBSCRIBE pubsub:node:ws-02,收到后从本地clientsmap 查u123并调用WriteMessage() - 注意:
Redis Pub/Sub是 fire-and-forget,不保证送达;若需 100% 投递(如金融类私信),必须换Redis Stream+ consumer group + ACK
单聊消息结构必须带明确路由字段
客户端发来的原始消息如果只含 {"type":"private","to":"alice","content":"hi"},服务端无法区分这是“本机投递”还是“跨服转发”。必须在解析后补全上下文,否则下游节点无法还原意图。
- 建议消息体增加
from_node和message_id字段,例如:{"type":"private","from":"bob","to":"alice","from_node":"ws-01","message_id":"msg_abc123","content":"hi"} -
from_node用于审计与环路检测(避免某节点误订阅自己频道又重复转发) -
message_id是去重关键:下游节点收到后先查本地已处理 ID 缓存(可用map[string]struct{}+ 定时清理),命中则丢弃 - 不要依赖 JSON 中的
to字段直接做 Redis 查询——必须由服务端校验登录态、权限后再执行HGET,防止客户端伪造
心跳与节点下线导致的映射脏数据
用户断网、浏览器关闭、节点宕机都会让 user_node_map 中的记录变成“幽灵条目”,下次发消息仍会投递到已不存在的节点,造成永久丢失。
- 每个节点需定期用
HEXISTS user_node_map "u123"+PTTL校验映射有效性,无效则HDEL - 节点启动时注册自己的服务标识到
redis set nodes:active,并维持心跳(如每 10sPEXPIRE);其他节点可监听该 set 变化,及时清理关联的user_node_map条目 - 最危险的坑:用户重连时未主动
HDEL旧映射,新连接覆盖写入前,存在短暂窗口期,两条连接被同时认为有效——必须在登录 handler 里先HGET再HDEL再HSET
跨服单聊真正难的不是代码量,而是状态一致性边界——Redis Hash 的 TTL、Pub/Sub 的不可靠性、节点间时钟漂移、客户端异常重连,这些细节叠加起来,会让 95% 的实现在线上跑一周后开始丢消息。别迷信“加个 Redis 就好了”,得亲手压测断网重连、随机 kill 节点、模拟网络分区,才能确认那条私聊消息到底有没有抵达对方屏幕。










