redis pub/sub 是后端多实例集群中实现 sse 消息推送的核心方案,所有实例订阅同一频道广播消息,并通过本地 map 或 redis 绑定判断是否持有目标用户连接,仅归属实例执行 send,配合三回调与定时扫描避免重复推送和连接泄漏。

在后端多实例集群中用 JavaScript 的 SSE 实现消息推送,关键不在前端,而在于后端如何协调多个服务实例——因为每个实例只持有自己内存里的 SSE 连接(EventSource 对应的 SseEmitter 或类似对象),用户可能连在任意一台机器上。直接调用本地连接会漏推。Redis Pub/Sub 是最轻量、低延迟的同步方案。
Redis Pub/Sub 作为跨实例消息总线
所有后端实例都订阅同一个 Redis 频道(如 station:notify),当任一实例需要向用户 A 推送消息时:
- 它不直接写入本地连接,而是向该频道发布一条结构化消息,例如:
{"userId":"A","event":"notice","data":"新订单已创建"} - 所有订阅该频道的实例都会收到这条广播
- 每个实例收到后,检查自己内存(或通过 Redis Hash/Map 缓存)是否维护着用户 A 的活跃 SSE 连接
- 只有真正持有该连接的实例才会执行
emitter.send();其他实例忽略
必须维护用户与实例的映射关系
光靠广播不够,还得知道“谁连在哪”。常见做法是:
- 用户建立 SSE 连接时(如请求
/sse?userId=A),当前实例将A → instance-id写入 Redis Hash,例如:HSET sse:bindings A "node-02" - 同时,该实例把
A加入本地内存 Map(如ConcurrentHashMap<string sseemitter></string>),并设置超时清理逻辑 - 推送前先查 Redis:
HGET sse:bindings A,确认是否轮到自己处理;也可跳过这步,直接本地查 Map——更高效,但需确保连接断开时及时清理 Redis 绑定
前端 EventSource 不需要改,但后端要统一响应格式
前端仍用标准 EventSource,无需感知集群:
- 响应头必须固定:
Content-Type: text/event-stream、Cache-Control: no-cache、Connection: keep-alive - 消息体按 SSE 规范写,例如:
event: notice\ndata: {"msg":"订单已支付"}\n\n - 推荐带上
id:字段,便于浏览器自动重连续传(如id: 12345\n)
避免重复推送和连接泄漏的关键点
集群环境下容易出问题的两个环节:
- 重复推送:不要让多个实例都尝试 send。必须依赖“唯一归属”判断——要么靠 Redis 绑定查,要么靠本地 Map 存在性判断(推荐后者,快)
-
连接泄漏:客户端断开时,本地 Map 和 Redis 绑定都要清理。建议用
emitter.onCompletion()+onTimeout()+onError()三回调兜底,并辅以后台定时扫描(如每 30 秒清理超 5 分钟无活动的连接)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











