SSE本身不提供消息去重和顺序保证,需服务端生成单调递增ID并显式设置id字段,客户端利用lastEventId自动恢复、滑动窗口缓存去重,并通过lastSeenId校验与待排序队列保障逻辑顺序。

SSE(Server-Sent Events)本身不提供消息去重和顺序保证的机制,但可以通过客户端和服务端协同设计来实现。核心思路是:服务端按序生成带唯一 ID 的事件,客户端用 lastEventId 自动恢复 + 本地缓存比对实现去重,再结合 ID 递增或时间戳校验维持逻辑顺序。
服务端:为每条消息分配单调递增 ID
这是顺序和去重的基础。不要用随机 ID 或 UUID,而应使用数据库自增主键、Redis INCR 计数器、或服务端全局原子计数器生成严格递增的 id 字段:
- 响应中必须显式写出
id: 123(注意冒号后有空格),浏览器会自动将其存为eventSource.lastEventId - ID 类型推荐整数,便于客户端做大小比较;若用字符串(如时间戳+序号),需确保字典序等价于发送序
- 示例响应片段:
id: 456 event: message data: {"content":"hello"}\n\n
客户端:利用 lastEventId + 缓存窗口做幂等消费
浏览器在断连重连时会自动携带上次收到的 lastEventId 发起请求(通过 headers: { 'Last-Event-ID': '456' }),服务端据此从下一条开始推送。客户端还需主动防重复:
- 维护一个滑动窗口缓存(如
new Set()),只存最近 N 条已处理的 ID(N 取决于网络延迟和重试频率,一般 100~1000) - 收到新消息时,先检查
event.id是否已在缓存中;存在则丢弃,不存在则处理并加入缓存 - 避免用全量历史 ID 做查重——内存和性能不可控
补充顺序保障:拒绝乱序 ID 的消息
仅靠服务端 ID 递增还不够,网络抖动或服务端多实例可能导致 ID 跳变或轻微乱序。客户端可增加轻量校验:
立即学习“Java免费学习笔记(深入)”;
- 记录上一条成功处理的 ID(
lastSeenId),新消息 ID ≤lastSeenId即视为乱序,暂存入待排序队列 - 配合小顶堆或有序数组管理待处理消息,持续尝试按序提交(ID 连续时 flush)
- 设置超时兜底:等待超过 5 秒未等到缺失 ID,触发告警或跳过该间隙(适用于非强一致性场景)
不依赖 EventSource 内置重连时的注意事项
如果手动关闭/重建 EventSource(例如切换主题、登出),lastEventId 会丢失。此时需:
- 在关闭前主动读取
eventSource.lastEventId并持久化到 localStorage - 新建 EventSource 时,从 localStorage 读取并传入
options(需封装 fetch 请求或使用 polyfill 支持 Last-Event-ID header) - 避免直接 new EventSource(url),改用工厂函数统一管理 ID 恢复逻辑


















