一、前言
SSE(Server-Sent Events)是一种允许服务器通过 HTTP 协议向客户端(通常是浏览器)单向推送实时更新的技术。它是 HTML5 标准的一部分,主要用于需要从服务器持续接收数据更新的应用场景。
在 SSE 中,客户端通过创建一个 EventSource 对象来连接到服务器的 SSE 端点。服务器则通过一个持久的 HTTP 连接不断向客户端发送数据流。数据通常以纯文本格式发送,客户端可以通过 JavaScript 监听这些数据并进行处理。SSE 的典型应用场景包括实时通知、新闻推送、股票行情更新等,适合那些需要服务器主动向客户端推送数据的场合。与 WebSocket 不同,SSE 是单向的,只能从服务器向客户端发送数据。
同时随着 AI 应用相关的推进,结合业务场景完成 SSE 在 APP 上的落地应用,同时可完成相关 Agent 相关业务场景的基建。
二、SSE 在 Web 上的应用
去年携程机票团队首先在 Web Online 业务上进行落地使用,技术方案主要采用:
- 前端侧,SSE 使用工具库
fetch-event-source。
- 服务侧,业务的流量链路并不是客户端—服务端直达,完全落地 SSE,需要全链路支持 SSE。

同时在服务侧需要注意处理以下问题:
- 中间存在多个 7 层代理需注意处理,包括:
- 7 层加速节点(akamai/aws)
- 流量接入层(slb)
- 中间转发节点(虫洞)
- sidecar(envoy/nginx)
- 分块传输,本质上需要传输数据碎片,但中间层各自实现并不遵从统一标准,例如 nginx,默认配置上会缓存 response。
- 对于 nginx 链路上所有 Nginx,需主动关闭报文缓存,透传相关 header:
proxy_pass_header X-Accel-Buffering;
- 需开启数据压缩。
三、SSE 在 APP 上实现方案
3.1 方案对比
在 APP 中使用 SSE 相比其他实时通信方案(如轮询、WebSocket)具有以下优势:
- 移动平台适配友好:可通过原生网络库实现。
- 跨平台一致性:同一套 SSE 服务可同时支持移动端 APP、网页端 H5,避免为不同端开发多套实时通信方案,降低维护成本。
- 低功耗与后台适配:相比 WebSocket 的双向通信,SSE 单向推送的连接维护成本更低,对移动设备的 CPU 和内存消耗更小,在弱网或网络切换时,SSE 的重连逻辑可更灵活地适配网络状态。
3.2 实现方案
SSE 在机票 Web Online 上成功落地使用后,随即开始在 APP 上针对 SSE 进行使用验证。APP 的实现方式基于 text/event-stream 类型的响应体持续推送数据,通过 Event 来处理数据。业务上基于携程公共框架团队实现的 SSE 基础能力之上进行,框架团队提供了相关接口给业务使用。从实现上分为三层:云端、Native 层和 RN 业务层,大致的关系如下:

React Native Plugin - Native 框架侧
由于机票业务采用 RN 跨平台方案,因此还需同时支持 Native 以及 RN 的使用,增加相应的 RN Plugin,需要分别处理:
onOpen:建立连接
onMessage:服务端向客户端推送批次数据
onClosed:请求完成
onError:请求报错
业务接入实现的大致结构如下:

另外在完成功能链路外,针对 SSE 相关的数据指标也做了一些优化手段,包括:
预建连:优化 onOpen 阶段的耗时。
- 在实际需要发送请求前,提前建立好网络连接,以减少后续请求的响应延迟。
- 目标 host 相同的多个请求,在协议一致且连接空闲可用的情况下,网络层会自动进行连接复用,减少网络开销,提升通信效率。
- 针对 host 连接复用,多个不同请求间共享,时机可在业务入口初始化时触发。
ssePreConnect = (serviceCode: string): boolean => {
const host = host;
if (host && EventSource.preConnect) {
EventSource.preConnect?.(host);
return true;
}
.....;
}
预请求:相比于预建连为 host 维度,预请求针对单一 request 进行链路复用,提升业务请求响应性能。
- 在实际业务的前序流程中提前请求数据,加速业务加载。
export const createSSEPreload = (options: TSSEServerParam) => {
const { params, sseInstance } = createSSEInstance(options);
const sseEventMananger = new SSEEventManager({params});
sseInstance.preload?.();
return () => {
sseEventMananger.onUnsubscribe?.();
sseInstance.removeAllEventListeners();
sseInstance.close();
};
};
请求缓存:服务于预请求。
- 请求命中时,复用缓存数据或者复用请求链路。
- 请求会标记不同的状态标识复用情况,状态枚举如下:

缓存实现通过父子任务进行真实请求与在途管理,父任务用于管理真实请求即 network 类型,子任务即标识挂载到父任务下的相同 cachekey 的在途请求,在途请求发生时会创建子任务挂载到父任务下,同步管理实时数据接收。不同类型的大致流程如下:

报文压缩
针对报文压缩相关进行了两项优化:
- 请求命中时,复用缓存数据或者复用请求链路。开启报文压缩:网络请求性能,尤其在 P90 指标上,报文 size 与网络链路耗时呈现显著正相关关系,需注意开启报文压缩,降低报文 size。
- 更换压缩编码从 Gzip 更换为 Brotli。
切换为 SSE 后带来的一个问题是报文压缩上的差异:压缩算法从 ZSTD 变更为 Gzip,导致 SSE 版本的报文 size 显著上升。压缩后的 size 反映到体验上,除加载完成耗时提升正向收益之外,带来了少量首次数据耗时的负向影响,通过进一步压缩报文 size 进行优化。

四、SSE 在 APP 上优化效果
4.1 SSE 的性能提升数据
通过引入 SSE 方式,相比于原有的轮询方式存在以下优势:
- 减少网络链路请求次数,提升查询性能:
- 简化各端代码结构:减少串行请求的回调监听、时序控制。
- 简化前端预搜索多批处理逻辑:对比串行查询机制,仅需处理一次预搜索即可完成所有批次的数据预请求。
- 轻量灵活便于下沉,多端复用:灵活调整业务查询流程,便于引入更丰富的查询机制。
数据上,APP 接入 SSE 除了在业务逻辑简化等业务收益外,在查询性能上也有所体现。通过实际业务数据对照效果,列表页多批次加载完成的总耗时对比如下:


针对验证数据进行分析:
- 性能提升体现在数据加载完成的耗时,对于首批数据的耗时没有明显的差异。
- 同时分析对比链路请求差异,SSE 能够减少 RN 与 Native 的通信时间(数十毫秒),以及减少一次请求,同时能够避免在服务数据已经 ready,但是需要前端请求到达才能下发的情况。相比于 Web,由于 APP 用户的网络波动更高,尤其对于查询性能高的航线,叠加用户的网络波动和预请求的因素,效果更为显著。

APP 接入 SSE 后的注意事项:
- APP 在使用 SSE 前使用 TCP 连接,使用 SSE 后变更为 HTTP 连接。HTTP 由于 SSL Pinning 机制的存在,会导致部分使用 VPN 等网络环境不安全的用户无法访问。
- 业务上存在上云、海外访问等情况,存在远距离数据传输的问题,需要关注所有链路均支持 SSE,关注各地区数据,避免出现部分地区 SSE 分批失效的情况。数据上表现为多批次数据同一时间到达,需要进行降级处理,同时与相关服务商反馈报障。
- 自动重连:业务实际使用中虽然原生支持,但是暂未启用失败重连特性。若需使用需要注意防范重连风暴风险,避免极端场景大量用户重连,导致服务端负载压力过大。
4.2 报文压缩的数据指标
整体的优化中有专门针对报文压缩编码进行优化,并进行了相关数据对比,主要的观测指标分类客户端和服务端分别进行检测。
客户端性能指标如下:
- 压缩率 Br 与 ZSTD 基本一致。
- 平均报文 size 上 Br 比 Gzip 减少约 20%,超长报文 size Br 可比 Gzip 减少约 30%。
- 耗时上 SSE-Br 相比 SSE-Gzip 可提升 20ms 左右的性能。
服务端指标如下:
压缩率比值

内存开销:无显著差异。

CPU 开销:无明显差异。

服务端压缩耗时:小报文无影响,平均报文压缩耗时下降 30%,但因基数小对总体均值影响较小,主要用于证明对服务侧无负向收益。

另外针对选择 Brotli 的优势:
- 标准 HTTP 编码,框架层接入成本相对较低,同时能够适用于不同的客户端。
五、其他适用的业务场景
针对 SSE 的应用场景,上述业务中的例子是针对轮询场景下的业务应用,该场景下每一批次均为同一业务的数据,数据结构也为同构数据。
除此之外,在前端的日常开发过程中,经常出现服务接口的粒度设计上,存在视图导向和资源导向之间的矛盾。细粒度 API 与后端的资源模型一一对应,而前端视图的需求可能与后端资源划分不一致。很多接口粒度以资源为导向,前端在处理时需要设计复杂度更高的方案去兼容各个接口间的请求时序、渲染时序、不同 API 间的逻辑 PK 关系等等。
因此,我们可以考虑将不同功能的接口,包装为异构的多批数据请求聚合在同一个 SSE 请求中,分批下发,用以解决服务 API 设计原则上产生的粒度过细与页面渲染实际使用间的矛盾问题。能够达到:
- 逻辑下沉,简化前端复杂度,通过 SSE 由服务聚合控制是否分批、如何分批,提升业务灵活性。
- 简化时序边缘场景控制,更灵活地控制页面子元素分批渲染。
- 多端复用生效。
- 相关预请求以及缓存实现的复杂度显著降低。
不同的实现方案在此场景下,SSE 相比 BFF 聚合和 GraphQL 的横向对比如下:

同时当前,查询 Agent 的应用日益广泛。SSE 在携程业务的落地,不仅在当下完成了相关业务实现的基础,同时对查询列表的流式改造也为后续 UI 列表加载与 Agent 的结合预留了改造基础——既解决眼前的实时性与分批渲染诉求,也为未来的能力扩展留出空间。
六、小结
SSE 在查询上的应用价值不是为了比 WebSocket 等其他方式更快,而在于用标准 HTTP 的最小代价,获得服务端主动推送、全链路复用以及原生重连异常处理的组合能力,通过最小的改造成本获得最广泛的业务收益。当业务的需求场景是服务端持续吐数据给客户端时,SSE 往往是更务实的选择。