WebSocket + RabbitMQ 高并发关键在连接管理、消息路由和状态同步三环;漏任一环则丢消息或假在线。

直接说结论:WebSocket + RabbitMQ 不是“加在一起就高并发”,关键在连接管理、消息路由和状态同步这三处,漏掉任一环,流量一上来就丢消息或假在线。
为什么不能直接用 WebSocket 推送业务事件
业务服务(比如订单创建、点赞)如果直接调用 session.getBasicRemote().sendText() 推送,会立刻暴露三个硬伤:
- WebSocket 服务器变成业务强依赖——订单服务一抖,推送就断;
- 连接数暴涨时,
Session对象堆积在堆内存里,GC 压力大,容易 OOM; - 用户离线时无法自动降级——你得自己查 Redis 判断在线状态,再决定存库还是丢弃,逻辑散落在各处。
真正该做的,是让业务服务只管发事件,其他事交给 RabbitMQ 和独立消费者。
RabbitMQ 队列设计必须区分“在线推”和“离线存”
别用一个 notify_queue 通吃所有消息。实际压测发现,混用会导致消费者处理延迟飙升,因为“查在线状态 → 推送 → 存 Redis”这个链路里,Redis 查找是瓶颈,而它本不该和 MQ 消费绑死。
推荐拆成两层队列:
-
notify_online_queue:只放已确认在线的用户消息,消费者直连 WebSocket 服务调用sendMessage(),不查 Redis; -
notify_offline_queue:存放待补推消息,由另一个消费者写入user_unread:10086这类 Redis Sorted Set,score 为时间戳; - 上线通知走单独 exchange:
online_event_exchange,绑定到online_status_queue,触发时刷新用户在线映射表(online_user:10086 → ws-server-2)。
这样,95% 的在线推送不碰 Redis,离线消息也不拖慢实时路径。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
WebSocket 服务端必须做连接亲和性与心跳穿透
多个 WebSocket 实例(比如部署了 4 个 pod)共享同一个 RabbitMQ,但用户连接只落在某一个实例上。如果 RabbitMQ 把消息发给错的实例,session 就找不到,消息直接丢。
解法不是靠“广播”,而是用一致性哈希路由:
- 用户 ID 经
Math.abs(userId.hashCode()) % 4算出归属实例编号; - RabbitMQ 生产者发消息前,根据该编号发到对应 routing key,比如
notify.user.2; - 每个 WebSocket 实例只监听自己的 routing key,避免无效消费;
- 心跳包(ping/pong)必须透传到 RabbitMQ 的
online_event_exchange,否则 Redis 里的在线状态过期,补推逻辑就失效。
没做亲和性时,我们实测 10 万连接下消息丢失率达 12%;加上路由后,压测 72 小时无丢失。
前端重连和消息去重必须由客户端承担
很多人以为“服务端保证一次投递”就能解决重复问题,其实不行。WebSocket 断连重连时,服务端无法判断上次的 ack 是否到达,RabbitMQ 的 manual ack 又没法跨连接生效。
所以去重逻辑必须下沉到前端:
- 每条消息带服务端生成的
msg_id(UUID 或 Snowflake); - 前端用
Map缓存最近 5 分钟收到的msg_id,重复则直接忽略; - 重连成功后,主动发
{"type":"sync","last_msg_id":"xxx"},服务端只补推last_msg_id之后的消息; - 别依赖
messageId字段——RabbitMQ 的messageId是 AMQP 协议字段,Java 客户端默认不生成,且不保证全局唯一。
这个细节最容易被跳过,但线上真实场景中,用户切后台再切回来、WiFi 切 4G,都会触发重连风暴。

















