websocket服务器端不提供消息缓存能力,所有缓存必须由应用层实现:使用redis(zset或lpush+brpop)跨实例存储带msg_id和expireat的消息队列,前端localstorage暂存pending消息并支持回滚,服务端结合user+time窗口幂等校验,各环节须有降级兜底。

WebSocket 服务器端本身不提供消息缓存能力,所有缓存逻辑必须由应用层自主设计和实现。不能依赖 WebSocket 协议或原生 API 的任何内置机制——它只负责传输,不负责存储、确认或重试。
服务端缓存必须跨实例、带 TTL、可持久化
单机内存缓存(如 ConcurrentHashMap)在重启后清零,集群下无法共享,完全不可靠。真正可用的方案需满足三点:
- 使用 Redis 存储离线消息队列,推荐 ZSET 按时间戳排序,或 LPUSH + BRPOP 实现 FIFO;每条消息带唯一 msg_id 和绝对过期时间 expireAt,写入时用
EXPIREAT设置 TTL - 消息体至少包含:sender_id、receiver_id、timestamp(毫秒级)、ttl(秒)、payload,序列化统一用 Jackson2JsonRedisSerializer
- 用户上线后主动拉取,成功投递后必须用
ZREMRANGEBYRANK或LRANGE + LTRIM清理已读部分,避免并发重复消费
缓存触发时机不能只靠 onOpen/onClose
连接状态变化信号不可靠:页面刷新、网络抖动、进程崩溃都会触发 onClose,但无法区分“主动下线”和“异常断连”。正确做法是:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 客户端上线时上报在线状态(如通过 HTTP 接口),服务端记录到 Redis,设较短 TTL(如 30 秒),配合心跳续期
- 消息发送前先查接收方在线状态(查 Redis),在线则直推,不在线则入离线队列
- 禁用在
handleTextMessage或@OnMessage中直接判断session.isOpen()后决定是否缓存——这属于伪逻辑,极易漏消息
消息幂等与业务交付对齐
缓存不是目的,确保“至少一次”且“不重复”才是关键。所有环节需强制校验:
- 每条消息必须携带服务端生成的单调递增序列号或全局唯一 ID,拒绝客户端自填的 timestamp 或 msg_id
- 服务端收到消息后,先校验是否过期(
timestamp + ttl * 1000 ),过期直接丢弃,不 ACK、不进业务逻辑 - ACK 必须与业务成功严格绑定——比如订单创建成功后才返回
{"msg_id":"xxx","status":"ok"},不能在入库前就发 ACK
降级与兜底必须前置设计
缓存链路越长,故障点越多。生产环境需预设多层兜底:
- Redis 不可用时,自动降级为本地 RocksDB 或 H2 内嵌数据库暂存,带定时同步机制
- 消息拉取接口加限流(如 Guava RateLimiter),防用户重连风暴打垮 Redis
- 所有缓存操作包裹 try-catch,失败日志必须含 msg_id、receiver_id、操作类型,便于人工补发










