websocketsession不能存入redis,因其为jvm内tcp连接封装对象,含不可序列化原生资源;正确做法是本地用concurrenthashmap缓存session,redis仅用于pub/sub广播消息和存储用户路由元数据(如ws:route:user:{userid}),配合全局单例监听器与带前缀频道实现跨节点消息分发。

WebSocketSession 不能存 Redis,别试了
直接把 WebSocketSession 序列化进 Redis 是行不通的。它本质是 JVM 内的一个 TCP 连接封装对象,持有 SocketChannel、ByteBuffer 等原生资源,跨节点反序列化后无法复用,甚至会触发 NotSerializableException 或运行时 NPE。
常见错误现象包括:
- 应用启动时报
java.io.NotSerializableException: org.springframework.web.socket.WebSocketSession - 集群中某节点能发消息,另一节点收到 Redis 消息后调用
session.isOpen()返回false,但没报错,消息静默丢失 - Redis 中存了一堆无效的 session 字符串,内存持续上涨却查不到对应连接
正确做法是:只在本地 JVM 缓存 WebSocketSession(比如 ConcurrentHashMap<string websocketsession></string>),Redis 只负责“广播消息”和“寻址元数据”。
用 Redis Pub/Sub 广播消息,不是每个连接起一个订阅
错误姿势:在每个 WebSocketSession 建立时,new 一个 RedisPubSub 实例去 subscribe —— 这会导致连接数爆炸、CPU 拉高、Redis maxclients 快速打满,且扩容后新节点收不到旧消息。
必须改成全局单例订阅:
- 整个 Spring Boot 应用只初始化一个
RedisMessageListenerContainer或RTopic(Redisson)监听器 - 监听器启动后调用
runInThread()或交由线程池托管,避免阻塞主线程 - 频道命名必须带业务前缀,例如
ws:notify:all、ws:room:1001,禁止用ps.psubscribe("*") - 消息体统一用
JSON序列化后.getBytes(StandardCharsets.UTF_8)发布,避免字节/字符串类型错乱
示例片段(Redisson):
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
RTopic<string> topic = redissonClient.getTopic("ws:notify:all");
topic.addListener((channel, msg) -> {
// 收到后从本地 ConcurrentHashMap 查 userId → session,再 send
webSocketServer.broadcastToUsers(JSON.parseObject(msg, NotifyMessage.class));
});
</string>
目标用户不在当前节点?得靠 Redis 存路由映射
纯广播(如全员通知)可以跳过这步;但点对点(A 给 B 发私信)、房间内(B 在 room:1001,A 在 room:1002)必须知道“用户 B 当前连在哪台机器”。
需要额外维护一张轻量级路由表:
- Key 格式:
ws:route:user:{userId} - Value 是 JSON:
{"serverId":"node-01","sessionId":"abc123"},TTL 建议设为 30 分钟 - 连接建立时写入,断开时
DEL,并加守护线程定期清理过期 key - 发送前先
GET ws:route:user:{targetUserId},若serverId == 当前节点 id,直发;否则PUBLISH到目标节点专属频道(如ws:node:node-01)
注意:serverId 必须在启动时生成且全局唯一(如 spring.application.name + IP + PID),不能硬编码。
Spring Boot 2.7 下 RedisTemplate 和 Redisson 怎么选
两者都能跑通,但行为差异直接影响稳定性:
-
RedisTemplate:依赖RedisMessageListenerContainer,需手动注册MessageListener,消息体默认是byte[],容易漏掉UTF-8解码逻辑,调试时看到一堆乱码\u0000\u0000... -
Redisson:自动处理序列化(可配JsonJacksonCodec),API 更语义化(addListener、publish),支持重连与失败回调,3.12.3+版本已适配 Spring Boot 2.7 的响应式上下文
如果你的项目已经用了 spring-boot-starter-data-redis,又不想引入新客户端,就用 RedisTemplate,但务必封装一层 publishUtf8String() 和 onMessageUtf8() 工具方法;否则建议直接上 Redisson,省掉 80% 的隐形坑。
最易被忽略的一点:所有节点必须共用同一个 Redis 实例(或 Redis Cluster),且网络互通。测试时发现消息不通,90% 是因为配置了不同 Redis 地址,或者防火墙拦了 Pub/Sub 端口(默认 6379,但部分云 Redis 要求走代理端口)。










