夜雨聆风学习资料网

ARTICLE · 1327448

告别轮询!携程机票 APP 基于 SSE 的全栈流式通信实战与性能优化

告别轮询!携程机票 APP 基于 SSE 的全栈流式通信实战与性能优化

作者简介

Haitao,携程资深前端开发经理,关注前端性能优化相关与AI应用落地。

导读:面对移动端传统轮询带来的延迟与高功耗痛点,携程机票前端团队完成了Server-Sent Events(SSE)从Web到APP(Native+RN)的全栈落地。本文深度拆解了全链路改造的踩坑经验,以及预建连、预请求、Brotli压缩等硬核优化手段。数据显示,其列表多批次加载总耗时平均提升11.8%。此外,文章还探讨了如何利用SSE“异构分批”解决前端渲染与API粒度设计的矛盾,并为AI Agent流式改造铺平道路。如果你正苦恼于复杂列表的串行请求与首屏体验,这篇全栈级实战干货不容错过。

一、前言

二、SSE在Web上的应用

三、SSE在APP上卖现方案

四、SSE在APP上优化效果

五、其他适用的业务场景

六、小结

一、前言

SSE(Server-Sent Events)是一种允许服务器通过HTTP协议向客户端(通常是浏览器)单向推送实时更新的技术。它是HTML5标准的一部分,主要用于需要从服务器持续接收数据更新的应用场景。 

在SSE中,客户端通过创建一个 EventSource 对象来连接到服务器的SSE端点。服务器则通过一个持久的HTTP连接不断向客户端发送数据流。数据通常以纯文本格式发送,客户端可以通过JavaScript监听这些数据并进行处理。SSE的典型应用场景包括实时通知、新闻推送、股票行情更新等,适合那些需要服务器主动向客户端推送数据的场合。与WebSocket不同,SSE是单向的,只能从服务器向客户端发送数据。

同时随着 AI 应用相关的推进,结合业务场景完成 SSE 在 APP 上的落地应用,同时可完成相关 Agent 相关业务场景的基建。

二、SSE在Web上的应用

去年携程机票团队首先在Web Online业务上进行落地使用,《干货 | 携程机票前端Web流式通信SSE全链路应用实践》技术方案主要采用:

  • 前端侧,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(Server-Sent Events)相比其他实时通信方案(如轮询、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阶段的耗时

    1)在实际需要发送请求前,提前建立好网络连接,以减少后续请求的响应延迟。

    2)目标 host 相同的多个请求,在协议一致且连接空闲可用的情况下,网络层会自动进行进行连接复用,减少网络开销,提升通信效率。

    3)针对host连接复用,多个不同请求间共享,时机可在业务入口初始化时触发。

ssePreConnect = (serviceCode: string): boolean => {    const host = host;    if (host && EventSource.preConnect) {        EventSource.preConnect?.(host);        return true;    }    .....;
  • 预请求:相比于预建连为host维度,预请求针对单一request进行链路复用,提升业务请求响应性能

    1)在实际业务的前序流程中提前请求数据,加速业务加载。

export const createSSEPreload = (options: TSSEServerParam) => {    const { params, sseInstance } = createSSEInstance(options);    const sseEventMananger = new SSEEventManager({params});    sseInstance.preload?.();    return () => {        sseEventMananger.onUnsubscribe?.();        sseInstance.removeAllEventListeners();        sseInstance.close();    };};
  • 请求缓存:服务于预请求

    1)请求命中时,复用缓存数据或者复用请求链路。

    2)请求会标记不同的状态标识复用情况,状态枚举如下:

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

  • 报文压缩

针对报文压缩相关进行了两项优化包括:

1)请求命中时,复用缓存数据或者复用请求链路开启报文压缩:网络请求性能,尤其在P90指标上,报文size与网络链路耗时呈现显著正相关关系,需注意开启报文压缩,降低报文size。

2)更换压缩编码从 Gzip 更换为 Broti。

切换为 SSE 后带来的一个问题是报文压缩上的差异:压缩算法从 ZSTD 变更为 Gzip,导致 SSE 版本的报文 size 显著上升,压缩后的size,反映到体验上是加载完成耗时提升正向收益之外,带来了少量首次数据耗时的负向影响,通过进一步压缩报文 size 进行优化。

四、SSE在APP上优化效果

4.1 SSE的性能提升数据

通过引入 SSE 方式,相比于原有的轮询方式存在以下优势:

  • 减少网络链路请求次数,提升查询性能

    • 减少网络交互

    • RN与Native的通信次数

    • 串行查询时的处理及等待耗时

  • 简化各端代码结构,减少串行请求的回调监听,时序控制

  • 简化前端预搜索多批处理逻辑,对比串行查询机制,仅需处理一次预搜索即可完成所有批次的数据预请求

  • 轻量灵活便于下沉,多端复用,灵活调整业务查询流程,便于引入更丰富的查询机制

数据上,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%,但因基数小对总体均值影响较小,主要用于证明对服务侧无负向收益

另外针对选择 Br 的优势

  • 标准HTTP 编码,框架层接入成本相对较低,同时能够适用于不同的客户端

五、其他适用的业务场景

针对SSE的应用场景,上述业务中的例子是针对轮询场景下的业务应用,该场景下每一批次均为同一业务的数据,数据结构也为同构数据。

除此之外,在前端的日常开发过程中, 经常出现服务接口的粒度设计上,存在视图导向和资源导向之间的矛盾,细粒度 API 与后端的资源模型一一对应,而前端视图的需求可能与后端资源划分不一致。很多接口粒度以资源为导向,前端在处理时需要设计复杂度更高的方案去兼容各个接口间的请求时序、渲染时序、不同API间的逻辑PK关系等等。

因此,我们可以考虑将不同功能的接口,包装为异构的多批数据请求聚合在同一个SSE请求中,分批下发,用以解决服务API设计原则上产生的粒度过细与页面渲染实际使用间的矛盾问题。能够达到:

  • 逻辑下沉,简化前端复杂度,通过SSE由服务聚合控制是否分批、如何分批,提升业务灵活性

  • 简化时序边缘场景控制,更灵活地控制页面子元素分批渲染

  • 多端复用生效

  • 相关预请求以及缓存实现的复杂度显著降低

不同的实现方案在此场景下 SSE 相比 BFF 聚合和 GraphQL 的横向对比如下:

同时当前,查询 agent 的应用日益广泛。SSE 在携程业务的落地,不仅在当下完成了相关业务实现的基础,同时对查询列表的流式改造也为后续 UI 列表加载与 agent 的结合预留了改造基础——既解决眼前的实时性与分批渲染诉求,也为未来的能力扩展留出空间。

六、小结

SSE 在查询上的应用价值不是为了比 WebSocket 等其他方式更快,而在于用标准 HTTP 的最小代价,获得服务端主动推送、全链路复用以及原生重连异常处理的组合能力,通过最小的改造成本获得最广泛的业务收益。当业务的需求场景是服务端持续吐数据给客户端时,SSE往往是更务实的选择。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料