sse本身有序但无去重机制,需应用层实现:用message id+客户端set去重,服务端提供单调递增组合id(如时间戳:序号),断线重连时通过last-event-id同步并补漏,异步处理须串行化保障顺序。

在 JavaScript 的 SSE(Server-Sent Events)客户端中,消息去重和顺序保证不能依赖协议本身——SSE 本身是单向、有序、按发送顺序送达的流式协议,但不提供消息 ID、重传确认或幂等性机制。因此,去重需由应用层实现,而顺序在正常网络下天然保持,但断线重连后可能产生重复或跳号,必须配合服务端协同设计。
用 message id + 缓存做简单去重
SSE 响应中若包含 id: 字段(如 id: 12345\n),浏览器会自动将其作为当前事件的标识,并在重连时通过 Last-Event-ID 头发给服务端。你可以利用这个 ID 做客户端去重:
- 维护一个 Set 或 Map 存储已处理过的
id,例如const seenIds = new Set() - 每次收到消息时,先检查
event.id是否存在且已在seenIds中,是则忽略 - 注意:
event.id是字符串,服务端需确保其全局唯一且单调递增(或带时间戳前缀)
服务端配合生成可排序的消息 ID
仅靠客户端缓存 ID 不足以应对长时间离线或重连后跳号问题。推荐服务端使用组合 ID,例如:timestamp_ms:seq_no(如 "1717023456789:3"),这样既能保证全局单调,又支持按时间+序号排序:
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
- 客户端收到新消息后,解析 ID 得到时间戳和序号
- 若新 ID 小于本地记录的最大 ID,直接丢弃(说明是旧消息重发)
- 若大于,则更新最大 ID 并处理;相等则对比 seq_no 防止同毫秒内多条
- 避免只用纯数字 ID(如 1, 2, 3),因服务端重启可能导致 ID 重置
断线重连时主动清理与同步状态
SSE 连接断开后,浏览器会自动重连并带上 Last-Event-ID,但客户端可能已丢失部分上下文。此时建议:
- 监听
onerror,并在重连成功后(onopen)触发一次轻量级状态同步请求(如 fetch/api/sse/sync?since=xxx) - 服务端返回从指定 ID 之后的最新几条消息(含完整 ID 和 payload),客户端比对后补漏或去重
- 不要完全依赖服务端“补发”,而是以客户端 ID 记录为权威,服务端只提供可验证的增量数据
顺序保证的关键:别跨事件处理异步逻辑
SSE 消息默认按接收顺序触发 onmessage,但如果你在回调里启动了 Promise、setTimeout 或其他异步操作,执行顺序就不再受控:
- 避免在
onmessage中直接发起未 await 的fetch或postMessage - 如需异步处理,可用队列 + async/await 串行化,例如用
async function handleQueue() { while (queue.length) { await process(queue.shift()); } } - 或者将消息推入一个有序队列(按 ID 排序),再由单独的消费循环按序处理
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










