SSE是什么?一文读懂大模型流式推送技术

作者:热心市民鹿先生2026.11.04 07:03浏览量:0

简介:SSE(Server-Sent Events)是基于HTTP协议的服务器单向推送技术,也是当前大模型应用实现逐字流式输出的主流方案。本文系统讲解SSE的定义、核心组成、消息格式与工作原理,结合大模型场景分析典型应用,并与轮询、长轮询、WebSocket进行对比,帮助读者一文建立对SSE的完整认知,掌握选型与接入要点。

一、SSE是什么:先给出清晰定义

SSE(Server-Sent Events,服务器推送事件)是一种基于 HTTP 协议的服务器推送技术,它允许服务端在一条持久的 HTTP 连接上,主动、持续地向客户端发送数据流。

理解SSE,需要抓住三个关键词:

  1. 基于HTTP:SSE不是全新的、独立的协议,而是构建在标准 HTTP/1.1 协议之上的一种扩展用法。它复用了HTTP的请求-响应模型、头部字段与传输机制,因此对现有Web基础设施(网关、代理、负载均衡、CDN)天然友好。
  2. 单向推送:数据只能从服务器流向客户端。客户端只在最初发起一次连接请求,之后便持续被动接收,无需反复发起新请求。
  3. 持久连接:连接建立后不会立即关闭,服务器每当有新数据就立即写入,形成一条“单向数据管道”。

可以用一个通俗的公式来概括:SSE = HTTP扩展字段 + Keepalive长连接。

从不同视角理解这个概念会更立体:

  • 技术视角:一次内容类型为 text/event-stream、以分块传输方式持续输出、迟迟“不结束”的HTTP响应;
  • 业务视角:让服务器“有话说就说”,而不是等客户端来问的一种实时数据下发机制;
  • 使用视角:浏览器原生提供 EventSource 标准接口,几行代码即可订阅服务器事件流。

目前几乎所有主流浏览器都原生支持SSE,服务端各类主流语言与框架也均有成熟支持,这是它得以广泛普及的基础。

二、SSE为什么出现:它解决了什么问题

2.1 传统“客户端拉取”模式的困境

Web的默认通信模式是请求-响应:客户端发起请求,服务器返回响应,一次交互结束。如果客户端想持续获取“最新数据”,传统做法有:

  • 短轮询:客户端定时向服务器发请求查询。缺点明显:大量请求在多数时间里是“空手而归”的,白白消耗服务器资源;同时数据更新总是滞后于轮询间隔,实时性差。
  • 长轮询:客户端发请求后,服务器先不响应,直到有新数据才返回;客户端收到响应后立即再次发起请求。实时性有所改善,但每次数据到达都要重建请求,逻辑繁琐;服务端还要同时维护大量挂起的请求。

2.2 WebSocket的权衡

WebSocket提供了真正的全双工通信能力,但它是一个独立协议:需要先通过HTTP发起“协议升级”握手,之后的通信便脱离标准HTTP语义。这带来一些代价:部分代理、网关等中间设备需要额外配置才能正确转发;服务器需要独立实现连接与协议管理;对于“只需要服务器推数据”的场景,全双工能力其实是冗余的。

2.3 SSE的定位

SSE正好补上这块空缺:只需要服务器→客户端单向推送时,用纯HTTP的方式实现“服务器主动推”。它简单、轻量、可靠,浏览器原生支持自动重连,无需引入新协议。这也是为什么实时通知、行情推送、日志流,以及当前大模型对话的逐字流式输出,都不约而同选择了SSE。

三、SSE的核心组成

3.1 HTTP层面的约定

一次SSE通信在HTTP层面表现为:

客户端请求:

  • 发起一个普通的GET请求;
  • 携带 Accept: text/event-stream,表明期望接收事件流。

服务端响应:

  • Content-Type: text/event-stream;
  • Cache-Control: no-cache,避免响应被中间层缓存;
  • Connection: keep-alive,维持长连接;
  • 采用分块传输编码(chunked transfer),做到边生成、边发送。

3.2 消息格式:四个标准字段

SSE的流内容是纯文本,按“事件块”组织,事件块之间用空行分隔。每个事件块可包含四个标准字段:

字段 作用 说明
data 数据本体 必备字段;若数据含换行,需拆为多行data,客户端接收后自动拼接
event 事件类型 自定义事件名,客户端可为不同事件注册不同监听器;缺省为 message
id 事件编号 客户端记录最近收到的id,重连时通过 Last-Event-ID 请求头带回,用于断点续传
retry 重连间隔 建议客户端重连前等待的毫秒数

此外还有“注释行”:以冒号 : 开头的行会被客户端忽略,常被服务端用作心跳保活。

一个典型的事件流片段如下:

  1. id: 1001
  2. event: token
  3. data: {"delta": "你"}
  4. id: 1002
  5. event: token
  6. data: {"delta": "好"}
  7. event: done
  8. data: [DONE]

3.3 客户端接口:EventSource

浏览器原生提供EventSource标准接口,基本用法:

  1. // 建立连接(整个生命周期只需请求这一次)
  2. const source = new EventSource('/api/chat/stream?prompt=你好');
  3. // 监听默认的 message 事件
  4. source.onmessage = (e) => {
  5. console.log('收到数据:', e.data);
  6. };
  7. // 监听自定义事件
  8. source.addEventListener('token', (e) => {
  9. appendText(JSON.parse(e.data).delta); // 逐段追加渲染
  10. });
  11. source.addEventListener('done', () => {
  12. source.close(); // 任务结束,主动关闭
  13. });
  14. // 错误处理
  15. source.onerror = () => {
  16. // 浏览器通常会自动重连;如需停止可调用 source.close()
  17. };

EventSource的三个关键特性值得记住:

  1. 自动重连:连接异常断开后,浏览器会自动尝试重连,默认间隔约3秒,可通过流中的 retry 字段调整;
  2. 断点续传:重连时浏览器自动在请求头中携带 Last-Event-ID,服务端可据此从断点继续推送;
  3. 就绪状态:readyState 有三种取值——CONNECTING(连接中或重连中)、OPEN(已打开)、CLOSED(已关闭)。

3.4 服务端实现要点

服务端的核心工作只有一件事:按格式持续写出数据并及时刷新。伪代码示意:

  1. # 服务端伪代码:处理一次SSE请求
  2. 设置响应头: Content-Type = text/event-stream
  3. 设置响应头: Cache-Control = no-cache
  4. 设置响应头: Connection = keep-alive
  5. 序号 = 0
  6. for chunk in 大模型逐块生成(prompt):
  7. 序号 += 1
  8. 输出 "id: " + 序号
  9. 输出 "data: " + JSON(chunk)
  10. 输出空行
  11. flush() # 关键:立即刷新输出缓冲
  12. 输出 "event: done"
  13. 输出 "data: [DONE]"
  14. 输出空行
  15. 结束响应

其中 flush() 是最容易踩坑的一步:如果每个事件写完后不立即刷新,数据会滞留在服务端缓冲区,客户端将感知不到“流式”效果,表现为长时间空白后一次性输出全部内容。

四、SSE的工作原理:完整生命周期拆解

一次完整的SSE交互流程如下:

  1. 建立连接:客户端通过EventSource发起GET请求,携带 Accept: text/event-stream;
  2. 服务端响应:服务器返回200状态码,声明 Content-Type: text/event-stream,且不结束该响应,连接保持打开;
  3. 持续推送:每当服务端产生新数据,就按事件格式写入并flush,数据经分块传输实时到达客户端;
  4. 事件分发:客户端解析事件流,按 event 字段分发到对应监听器,同时记录 lastEventId;
  5. 心跳保活:空闲期服务端周期性发送注释行(如 : ping),防止中间代理或防火墙因连接长期无数据而将其切断;
  6. 断线重连:连接异常断开时,浏览器自动重连,并在请求头中带上 Last-Event-ID;服务端据此从断点继续推送,避免数据丢失;
  7. 正常结束:任务完成时,服务器发送结束事件并结束响应;客户端也可随时调用 close() 主动断开。

特别说明SSE与大模型的契合点:一次回答的生成往往持续数秒到数十秒,内容是逐块(逐词)产生的。SSE恰好把“服务端的生成过程”实时映射为“客户端的渲染过程”——用户看到打字机般的逐字输出,首字响应时间从“等待全部生成完”缩短为“等待第一块生成完”,体验提升显著,同时天然支持“停止生成”等交互。

五、典型应用场景

  1. 大模型对话流式输出:当前SSE最热门的应用。模型逐块生成回答,服务端将每个增量片段通过SSE实时推给前端,实现逐字渲染与中途停止;
  2. 实时通知与消息推送:站内信、审批提醒、状态变更通知等,事件发生后即时下发;
  3. 实时数据看板:监控指标、物联网传感器数据、行情类数据的动态刷新,替代低效轮询;
  4. 日志流与运维观测:远程查看构建/部署输出、训练任务日志,实现类似“实时跟随”的效果;
  5. 任务进度反馈:文件导入导出、批量计算、转码等长任务的进度百分比与阶段状态推送;
  6. 内容渐进式加载:先推送框架性内容让页面先可用,再逐步补充细节。

判断是否适合SSE的黄金法则:只要“数据由服务器持续产生、客户端只需要接收”,且数据是文本形态,SSE就值得优先考虑。

六、SSE与相关技术的区别

6.1 SSE vs 短轮询 / 长轮询

  • 通信模型:轮询是客户端反复“问”,SSE是服务器主动“说”,客户端只连接一次;
  • 实时性:轮询的延迟取决于询问间隔,SSE近乎实时;
  • 资源开销:轮询产生大量无效请求,SSE单连接复用,开销小得多;
  • 恢复能力:SSE自带重连与断点续传,轮询方案需自行设计。

6.2 SSE vs WebSocket(重点对比)

维度 SSE WebSocket
通信方向 单向(服务器→客户端) 全双工(双向)
协议基础 标准HTTP 独立协议(经HTTP升级握手后切换)
数据格式 仅UTF-8文本 文本与二进制均可
自动重连 原生支持,配合Last-Event-ID断点续传 需自行实现
基础设施兼容 走标准HTTP,代理/网关普遍兼容 部分中间设备需专门配置
实现复杂度 低,客户端几行代码 相对较高

选型建议:

  • “服务器推、客户端听”(通知、行情、日志、大模型流式回答)→ 选SSE,更简单、更轻量;
  • 客户端需要高频回传数据(即时通讯聊天室、多人协同编辑、联机游戏、双向实时控制)→ 选WebSocket。

需要澄清一个常见误解:大模型对话虽然形式上像“聊天”,但单轮问答中客户端只在开头发一次请求,回答阶段只需要服务器→客户端的单向推送,因此SSE完全够用,这也是业界主流大模型对话类应用普遍采用SSE而非WebSocket的原因。

6.3 补充:SSE与HTTP/2

HTTP/1.1下浏览器对同一域名的并发连接数有限(通常为6个),同时打开多个SSE连接可能互相挤占。HTTP/2的多路复用允许多个流共享一条TCP连接,可显著缓解这一问题,是SSE密集型应用部署时值得确认的前提条件。

七、使用注意事项

7.1 代理与网关的缓冲问题

不少反向代理、网关组件默认会缓冲后端响应,导致事件被“攒够一批”才到达客户端,流式效果失效。接入时需确认中间链路针对 text/event-stream 关闭了缓冲,或在响应头中声明不缓冲。

7.2 超时与心跳

负载均衡、服务器对“安静”的长连接可能有空闲超时机制。应对方式:一是适当调大相关超时配置;二是服务端定期发送注释行心跳(如每15~30秒发送一次 : keepalive)。

7.3 浏览器连接数限制

HTTP/1.1同源连接数上限会约束并发SSE数量(例如用户同时开多个流式会话、多个标签页)。可通过使用HTTP/2、域名分片或“单连接多事件复用分发”来规避。

7.4 数据编码限制

SSE只能传输UTF-8文本。若需传输二进制数据(图片、音频帧),要么Base64编码(体积膨胀约三分之一),要么改用WebSocket。

7.5 鉴权与安全

  • 生产环境一律使用HTTPS;
  • EventSource原生不支持自定义请求头,鉴权常用方案:Cookie会话,或将短时效一次性token放入URL参数(需注意泄露与时效控制);
  • 跨域场景需正确配置CORS响应头;
  • 服务端应校验 Last-Event-ID 的合法性,防止伪造越权拉取。

7.6 服务端连接管理

长连接持续占用服务端资源,需要:设置合理的最大连接数与排队策略;在客户端断开时及时释放资源;结合负载均衡分发连接避免单实例过载;任务结束(如模型生成完毕)要显式结束响应,避免产生僵尸连接。

7.7 消息格式细节

  • 事件块之间必须有空行,否则客户端不会触发事件;
  • data 内容含换行时必须拆成多行 data:,客户端接收后会用换行拼接还原;
  • 传输JSON时注意转义与结构完整性。

7.8 重连的语义设计

自动重连虽然方便,但业务上必须明确:重连后是通过 Last-Event-ID 续传,还是重新开始?对重复推送敏感的场景,服务端需设计幂等、去重或会话恢复机制。

八、总结:回到定义本身

SSE是一种构建在标准HTTP之上的服务器单向推送技术:一次请求、一条长连接、多段持续响应。它的核心价值在于用最小的复杂度实现了“服务器主动向客户端推数据”:

  • 简单:完全复用HTTP,客户端几行代码即可接入;
  • 轻量:无需协议升级,没有全双工带来的冗余复杂度;
  • 可靠:原生自动重连与Last-Event-ID断点续传机制。

它的适用边界同样清晰:只传UTF-8文本、只做服务器→客户端方向。通知推送、实时数据更新、日志流、任务进度,尤其是大模型逐字流式输出这类“客户端发一次、服务器持续回”的场景,SSE往往是比WebSocket更简单、更合适的选择;而双向高频交互仍应交给WebSocket。

理解SSE,本质上是理解一个朴素的工程判断:在满足需求的前提下,优先选择约束更强、机制更简单、与现有设施更兼容的方案。这正是SSE在大模型时代重新走红的根本原因。