在构建支持多模型(如 GPT、Claude、Gemini)的 AI 应用时,流式输出已成为提升用户体验的标配。Server-Sent Events (SSE) 因其基于 HTTP、实现简单、天然支持自动重连,成为承载这类流式补全的理想协议。然而,网络环境的不稳定 —— 移动端切换网络、代理超时、服务端滚动更新 —— 常常导致连接中断。若每次断线都从头开始推送,不仅浪费带宽,更会破坏用户的沉浸感。因此,实现可靠的断线续传是 SSE 在生产环境落地的核心挑战。本文将深入 SSE 协议的续传机制,并给出从客户端到服务端、再到部署架构的工程化参数与监控清单。
协议基石:id 与 Last-Event-ID 的协同
SSE 的断线续传能力并非魔法,而是建立在简洁的协议设计之上。其核心是两个要素:
- 事件 ID:服务端在推送每条消息时,必须包含一个
id:字段,例如id: 123\n。这个 ID 应是递增且唯一的,用于标识消息的顺序。 - 续传请求头:当使用浏览器原生
EventSource对象且连接意外断开时,浏览器在自动重连的新请求中,会自动携带Last-Event-ID请求头,其值正是客户端成功接收的最后一个事件的 ID。
这个过程完全由浏览器和协议层自动处理。服务端的职责是:在建立新连接时,检查 Last-Event-ID 请求头,并从该 ID 之后的消息开始推送。例如,客户端最后收到 id: 20,断线重连后,服务端收到 Last-Event-ID: 20,便从 ID 为 21 的消息开始发送。这就实现了精确的断点续传,而非重新开始。
客户端选型:原生与封装库的参数抉择
选择正确的客户端实现是续传的第一道保障。2025-2026 年的实践中,主要有两种路径:
1. 原生 EventSource:速度与限制
原生 EventSource API 开箱即用,自动处理重连和 Last-Event-ID 传递。其关键参数是服务端通过 retry: 字段指定的重试间隔(毫秒)。然而,其局限性在复杂应用中很快显现:无法自定义请求头(如 Authorization)、仅支持 GET 方法、无法传递长请求体。这意味着如果你的鉴权依赖 Bearer Token,或需要向多模型网关传递复杂的模型参数,原生方案将无能为力。
2. 基于 Fetch 的封装库:灵活与控制
为此,社区涌现了基于 fetch API 的 SSE 客户端库,以 @microsoft/fetch-event-source 为代表。它们通过 ReadableStream 和 TextDecoder 手动解析 SSE 流,从而支持完整的 Fetch 选项,包括自定义 headers、POST 方法和请求体。
在实现续传时,这类库需要手动管理 lastEventId。重连时,需将其作为查询参数(如 ?lastEventId=xxx)或自定义请求头发送给服务端。虽然增加了些许复杂性,但换来了对鉴权、重试策略(如指数退避)的完全控制。例如,你可以在重连逻辑中加入随机延迟,避免服务端故障时所有客户端同时重连引发的 “惊群” 效应。
服务端实现:状态、心跳与资源管理
服务端是续传逻辑的最终执行者,其实现必须考虑状态管理和生产环境韧性。
1. 状态持久化与可重算
续传的前提是服务端能够 “回忆” 起从某个 ID 之后的所有消息。对于聊天记录或推理流,这意味着需要将消息序列持久化到数据库、或写入可按序读取的日志队列(如 Kafka、Redis Stream)。对于实时生成的数据(如模型输出),则需确保生成过程是确定性的,或者能够根据种子和偏移量重新计算。简言之,服务端必须有能力响应 “请从 ID=N 开始发送” 的请求。
2. 心跳与超时参数
长连接最怕被中间环节(反向代理、负载均衡器)默默关闭。必须通过定期发送心跳来保持连接活跃。SSE 协议中,以冒号开头的行是注释,常被用作心跳包,例如每 15 秒发送一次 : ping\n\n。
关键是要让心跳间隔小于基础设施的超时设置。常见的参数配置如下:
- 服务端心跳间隔:15-30 秒。过短浪费资源,过长可能触及相关超时。
- Nginx/Apache 代理超时:通常设置为
proxy_read_timeout 300s;或更高,确保远大于心跳间隔。 - 云负载均衡器空闲超时:AWS ALB/NLB 或 GCP Cloud Load Balancing 默认超时多为 60 秒,需根据心跳间隔适当调高。
- 客户端 EventSource 重试时间:通过
retry: 10000设置,建议 5-10 秒,配合指数退避。
3. 连接管理与资源回收
每个 SSE 连接都持有服务端资源(内存、文件描述符)。高并发下,必须严格监听连接关闭事件,及时清理定时器、释放缓冲区。例如在 Node.js 中,监听 req.on('close', ...) 进行清理。否则,内存泄漏将迅速拖垮服务。
高并发架构下的续传考量
当用户量激增,单机连接数成为瓶颈时,架构需要演进。
1. 连接粘性与状态外置
如果用户连接被负载均衡器分配到不同后端实例,而会话状态存在实例内存中,那么重连时若被分配到不同实例,续传就会失败。解决方案有二:一是配置负载均衡器的会话粘性(session affinity),确保同一用户的连接总落到同一实例;二是将会话状态(如最新的消息 ID、待推送队列)外置到共享存储如 Redis 中,使任何实例都能处理续传请求。后者更利于水平扩展和故障恢复。
2. 连接复用与数量控制
浏览器对同一域名有并发连接数限制(通常为 6)。一个页面内若有多个组件独立创建 SSE 连接,极易达到上限。最佳实践是建立全局连接管理器,让多个组件订阅同一个 SSE 连接的事件流,从而大幅减少连接数,减轻服务器压力。
可落地参数清单与监控要点
综合以上,我们提炼出一份可直接落地的断线续传配置与监控清单:
协议与数据层
- 每条消息必带递增的
id:字段。 - 服务端实现
Last-Event-ID或lastEventId参数解析,支持从指定 ID 恢复。 - 消息源支持按序重取或确定性重算。
客户端层
- 根据需求选择:简单场景用原生
EventSource,复杂鉴权 / 参数用@microsoft/fetch-event-source。 - 实现重连退避策略(如初始 1 秒,最大 30 秒)。
- 在
localStorage缓存lastEventId,支持页面刷新后续传。
服务端层
- 响应头正确设置:
Content-Type: text/event-stream、Cache-Control: no-cache、Connection: keep-alive。 - 实现心跳机制,间隔建议 20 秒,发送
: heartbeat\n\n。 - 严格监听连接关闭事件,释放所有相关资源。
基础设施与监控
- 配置反向代理 / 负载均衡器的空闲超时 > 心跳间隔 * 3(例如 90 秒)。
- 监控服务端 SSE 连接数、内存使用量,设置告警阈值。
- 记录断线重连率、续传成功率,作为体验核心指标。
正如一篇技术文章所指出的,“SSE 的自动重连机制是基于 EventSource 的自动重连机制实现的,这是一个非常简单的机制”。然而,将这种简单机制转化为生产级可用的续传能力,需要我们在协议理解、工程实现和运维部署上做足功课。通过上述参数化配置和清单化检查,我们可以让 SSE 在多模型流式补全的场景中,既保持其轻量优雅,又具备工业级的韧性,确保无论网络如何波动,信息流都能如溪水般持续、准确地送达用户眼前。
资料来源
- 掘金文章《大模型应用中,前端绕不开的 SSE》,其中详细介绍了 SSE 基础、代码实现与断线续传示例。