SSE(Server-Sent Events,服务端推送事件),为什么大模型使用SSE进行消息推送
SSE(Server-Sent Events)是一种基于HTTP协议的服务端推送技术,允许服务器向客户端持续发送数据流,适用于单向通信场景。其特点包括实现简单、自动重连和轻量级,主要用于文本数据传输。SSE通过HTTP长连接实现,服务器返回特定头部信息,客户端使用EventSource监听数据流。SSE在文件下载、大模型流式文本生成等场景中表现优异,因其低实现成本、单向数据流需求和内置断线重连机制
SSE协议
SSE(Server-Sent Events,服务端推送事件)基于 HTTP 协议,为了解决服务器无法主动推送信息的问题。通过向客户端声明接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
特点:
-
单向通信:服务器向客户端推送数据。
-
实现简单:基于 HTTP 协议,大部分浏览器都支持,只需设置正确的 HTTP 头和响应格式,轻量级。
-
自动重连:浏览器会自动处理重连机制。不允许缓存
-
数据格式:SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
协议实现
SSE 协议很简单,本质上是一个客户端发起的 HTTP Get 请求,服务器在接到该请求后,返回 200 OK 状态,同时附带以下 Headers
Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive服务器数据格式: 每个事件以
\n\n结尾,data字段包含有效负载,可选id(事件ID)、event(自定义事件类型)等字段。通过定时Flush推送数据,避免缓冲区阻塞func StreamHandler(c *gin.Context) { c.Header("Content-Type", "text/event-stream") for { token := generateNextToken() fmt.Fprintf(c.Writer, "data: %s\n\n", token) c.Writer.Flush() // 关键:即时刷新缓冲区 if isEnd(token) { break } } } // data: {"token": "Hello", "end": false}\n\n // data: {"token": "World", "end": true}\n\n
- SSE 是一个一直打开的 TCP 连接,所以 Connection 为 Keep-Alive
客户端使用JavaScript的
EventSource监听数据流:// 客户端:EventSource监听 const source = new EventSource('/stream'); source.addEventListener('message', (e) => { document.getElementById('output').innerHTML += e.data; });
业务场景
以「文件下载」功能进行说明,一般情况下,大文件的下载,服务端压力比较大、处理时间也比较长,为了有更好的交互体验,我们可以使用异步处理,服务端处理完了之后主动通知 客户端,效果如下:

这个小弹窗就是服务端处理完了之后,通过 SSE连接主动推送到客户端。
我们来看看处理流程:

1)SSE 连接:
先建立 SSE 连接,确保服务端有主动推送消息的能力。
2)异步下载:
长耗时下载任务我们通过异步的方式处理,避免用户在下载页面长时间等待。
3)广播并推送:
下载完成后,我们需要将完成事件推送给客户端。需要注意的是,由于服务是集群部署、SSE 连接在节点本地 Map 维护,这就有可能导致当前客户端的 SSE连接所在节点 与 事件推送节点 是两个独立的节点。
因此,我们这里借助于 Redis 的发布/订阅能力,将消息广播出去,能匹配连接的节点负责将消息推送至客户端、其他节点直接丢弃即可。效果图如下:

能否做到精准投递?
答案也是可以的,我们可以这样来做:
借助 Redis 做中心存储,存储Map<用户,节点IP> 这样的映射关系。
在推送消息之前,先通过映射关系找到该用户的SSE连接所在节点
然后在通过 RPC调用 直接将消息投递到对应的服务节点,最后由该节点进行事件推送。
一般情况下,我们可以用「广播」这种简单粗暴的方式应对大部分场景,毕竟「精准投递」需要中心化的维护节点关系、应对节点变更等,处理起来稍显麻烦。具体视业务场景来做选择即可。
为什么大模型使用SSE而不是websocket
1.实现成本
- 时间
WebSocket成本 = TCP握手(1次RTT) + TLS1.2握手(2次RTT) + Upgrade: websocket 请求(1次RTT) ,共计4次RTT。(如果是TLS1.3,可以做到0~1次RTT)
SSE成本 = HTTP长连接(1次RTT) + TLS ,会减少一次协议升级的RTT
tcp知识点:为什么三次握手是一个RTT
标准定义的 RTT 是:发送一个请求 + 等待一个响应的时间。
Client Server | -------------- SYN --------------------> | ← 第一次握手 | <----------- SYN + ACK ---------------- | ← 第二次握手 | -------------- ACK --------------------> | ← 第三次握手对于客户端来说,ACK 不需要等响应(第三次握手不等待服务端返回)。所以完整的RTT过程是 从 SYN 到收到 SYN+ACK
- 内存
WebSocket需维护全双工长连接,每个连接占用独立线程或内存资源,而SSE通过HTTP长连接实现推送,服务器资源消耗更低,适合高并发场景
- 兼容性
SSE基于HTTP协议,无需额外协议升级(如WebSocket的ws:///wss://),可直接复用现有HTTP端口(80/443),减少防火墙或代理配置问题。现代浏览器原生支持EventSource API,兼容性优于WebSocket的部分限制
- 格式
SSE支持纯文本或JSON流式传输,与大模型输出的结构化数据(如Token序列、置信度)天然契合。WebSocket虽支持二进制数据,但需额外编码解析
2.单向数据流需求
大模型的核心场景是流式文本生成,用户输入请求后,服务器需持续推送生成的文本片段。这种场景下,通信方向是单向的(服务器→客户端),而WebSocket的全双工通信能力(双向交互)反而冗余。
当用户输入问题后,模型逐词生成回答,客户端无需中途向服务器发送额外数据。
3.流式信息的不均衡性
大模型的输出Token量通常远大于输入(如输入10字问题,生成500字回答)。SSE的单向推送机制能高效处理这种**“稀疏请求,密集响应”**模式,避免WebSocket双向通道的资源浪费
4.断线重连天然支持
SSE内置自动重连机制(默认3秒重试),支持通过Last-Event-ID头部恢复断连后的数据流,适合大模型长文本生成场景的网络波动容错。而WebSocket需手动实现心跳检测与重连逻辑
SSE的核心优势在于将协议复杂度降至最低,精准匹配大模型的单向流式需求,同时通过HTTP生态的成熟性保障稳定性和兼容性。而WebSocket更适用于实时双向交互场景(如在线协作、聊天室)。技术选型时需遵循“用对的,而非用贵的”原则
什么时候不适用SSE
虽然SSE性能卓越,但在以下场景请慎用:
| 场景 | 问题 | 推荐方案 |
|---|---|---|
| 双向实时通信 | SSE不支持客户端推送 | WebSocket |
| 二进制流传输 | SSE仅支持文本 | WebSocket+ArrayBuffer |
| 超低延迟要求(<10ms) | HTTP协议栈开销 | QUIC协议 |
| 移动端弱网环境 | 长连接保活困难 | MQTT+长轮询 |
典型案例:某在线教育平台的白板协作功能,初期采用SSE导致画笔延迟明显,切换WebSocket后延迟从200ms降至50ms。
ref :
更多推荐
所有评论(0)