WebSocket负责连接维持与即时触达,RabbitMQ负责流量缓冲与解耦分发,二者需通过Redis协同用户在线状态,否则易导致消息丢失、重复推送或连接泄漏。

直接上结论:WebSocket 负责「连接维持与即时触达」,RabbitMQ 负责「流量缓冲与解耦分发」,两者不能互相替代,硬凑在一起却没做状态协同,反而会放大消息丢失、重复推送、连接泄漏等风险。
WebSocket 连接必须绑定用户在线状态,否则 RabbitMQ 消费后无处投递
常见错误现象是:用户已下线,但 RabbitMQ 消费者仍在往已断开的 WebSocketSession 推送消息,导致 IllegalStateException: The session has been closed 或静默失败。
- 必须用 Redis 存储用户 ID →
sessionId映射(建议用 Hash 结构,key 为ws:online:user:{uid}) - 在
WebSocketHandler.afterConnectionEstablished()中写入,afterConnectionClosed()中删除 - RabbitMQ 消费者在推送前,先查 Redis 是否存在对应
sessionId,不存在则走离线流程(如存入 Sorted Set) - 别依赖
session.isOpen()判断——网络闪断时它可能仍返回true,但底层 TCP 已不可写
RabbitMQ 队列声明和绑定必须匹配消费者实际路由逻辑
很多项目卡在「消息发进去了,消费者收不到」,根本原因常是 Exchange/Queue/Binding 的类型或 key 对不上。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 若用
TopicExchange,发送端amqpTemplate.convertAndSend("exchange", "user.12345", msg),消费者队列必须绑定with("user.*")或with("user.#"),不能只写with("user") - 若用
DirectExchange,binding key 必须与 routing key 完全一致,大小写敏感 - 队列名不要硬编码字符串,用
@Bean声明并注入,避免生产/测试环境队列名不一致 - 启用
spring.rabbitmq.publisher-confirm-type: correlated和spring.rabbitmq.publisher-returns: true,捕获发送失败
消息体序列化必须前后端对齐,且预留扩展字段
前端收到 undefined 或解析报错,90% 是因为后端用 FastJSON2 序列化,前端 JSON.parse 后字段被自动转成小驼峰,而 Java 实体类用的是大驼峰命名。
- 统一用
@JSONField(name = "msg_id")或 Jackson 的@JsonProperty("msg_id")显式指定字段名 - 消息体根对象至少包含三个字段:
type(如"notice")、payload(业务数据)、timestamp(毫秒时间戳,用于前端防抖) - 避免直接序列化 Spring Bean(含 Hibernate Proxy 或循环引用),务必封装为 DTO 再发
- RabbitMQ 默认最大消息体为 128KB,超长日志类消息需压缩或改用对象存储 URL
Spring Boot 中 WebSocket 配置容易漏掉关键参数
本地跑通、压测就断连,大概率是心跳和缓冲区没调。
-
registry.addEndpoint("/ws").setAllowedOrigins("*")在生产环境必须限制域名,不能留* - 必须配
registry.addEndpoint("/ws").withSockJS()作为降级兜底,否则 Safari iOS 15+ 会静默失败 -
config.setMaxTextMessageBufferSize(64 * 1024)和setMaxBinaryMessageBufferSize(64 * 1024)默认只有 8KB,大消息直接截断 - 启用心跳:
config.setHeartbeatValue(Duration.ofSeconds(10)),否则 Nginx 默认 60s 断连无法感知
最易被忽略的一点:RabbitMQ 消费者线程池和 WebSocket 推送不是同一个上下文,SecurityContext、RequestContextHolder 全部失效,所有用户标识必须从消息体或 Routing Key 中显式提取,不能靠 ThreadLocal 传参。

















