写点什么

百度 SSP 单页式应用性能优化实践

  • 2017-03-30
  • 本文字数:3649 字

    阅读完需:约 12 分钟

针对首页和部分页面打开速度慢的问题,百度 SSP 前端团队对单页式应用性能进行了优化。本文将介绍其中一个性能优化方案: 基于 HTTP Chunk 的首屏数据渐进式预加载方案,该方案减少了 1.2s 的加载时间。同时对比其与同构渲染方案的异同。

背景介绍

单页式应用是近几年来前端技术栈发展与落地的最典型场景,Angular、Vue、React 等,这些相关的技术栈目的都是从架构层面为单页式应用提供研发解决方案,着重解决单页式应用的研发效率。基础框架的进化也催生着关联工具链路的发展,如 Yeoman,Grunt -> Gulp -> 各种 cli,Webpack1/2, Babel 等。

随着研发链路体系的稳定成熟,在功能上能够及时满足用户后,百度展示广告部流量端前端团队(百度 SSP 前端团队)开始将精力集中关注产品的可用性层面。经过和产品,运营,用研等多个团队配合,我们走访了多位使用我们产品的用户,产出了一份流量端产品可用性得报告。除了部分交互和产品流程设计上的问题,另一个主要问题就是用户反馈整体的系统流畅性不错,但首页和部分页面打开极其慢,针对这块问题,我们开始了对单页式应用性能优化的探索和实践

本文接下来将一步一步阐述对应用首屏呈现中各个节点的拆解,并根据拆解的节点推导出我的优化思路,最终为大家介绍我提出并尝试的第一个性能优化方案: 首屏数据渐进式预加载

首屏呈现节点分析

在进行任何的性能优化之前,我们都应该先找出系统的性能瓶颈点,从而找出最有价值的优化方向。

绝大多数的单页式应用都符合 Application Shell 架构,根据这个架构我们可以看出一个应用首屏呈现节点可以分解为:请求入口页 -> 渲染应用外壳 -> 渲染首屏片段。我在此基础上进一步将三个节点细分如下:

(点击放大图像)

即对渲染应用外壳和渲染片段这块细分为:应用资源加载,应用初始化,片段资源加载,片段初始化,片段数据加载,片段渲染这些节点。

有了这些细分节点,再将埋点记录的真实用户数据代入:

(点击放大图像)

得出我们的首屏时间为:

** T(s) = T1 + … + T7 = 2800ms **

注:我们一般都将首屏资源一起与应用资源打包在一起,因此这里耗时认为是 0。

整个 timeline 如下:

(点击放大图像)

首屏数据渐进式预加载方案

根据上面的节点数据,首屏数据渐进式预加载的优化思路也得到了体现:

  1. 优化首屏数据加载节点的速度。
  2. 预先加载首屏数据,使得多个串行节点并行化。

接下来详细介绍我们的优化步骤。第 1 点会在第一步优化中体现,但核心思路和主要优化收益更多体现在第 2 点:多个串行节点并行化。

Step1:资源文件下载与首屏数据请求节点并行

为了达到资源下载与数据请求并行的效果,我们充分利用了HTTP Chunk 传输与浏览器的渐进式渲染特性

  1. 将入口页分为静态片段和数据片段:静态片段包含了各个资源标签(script,link),静态的导航栏,加载指示器等;数据片段则是包含首屏数据的内联脚本,大至如下:
复制代码
<script>window.__APP_DATA__ = { /* 相关的首屏数据 */ };</script>
  1. 浏览器请求入口页时,入口页服务器 (这里我们用了 NodeJS ) 并行 做以下操作:
  • HTTP Chunk 方式输出静态片段
  • 请求首屏数据并在所有数据请求完成后将数据片段和应用初始化代码返回给浏览器。

注:http chunk 方式输出在 NodeJS 中及其容易满足,简单的 res.write(chunk) 即可。

整体架构如下:

(点击放大图像)

浏览器的渐进式渲染特性在收到静态片段并解析后立刻去下载资源,由此巧妙的将应用资源加载节点和首屏数据请求节点并行化;当应用初始化完毕后,首屏组件直接读取 window.__APP_DATA__拿到数据渲染即可。

整个首屏呈现 timeline 变化如下:

(点击放大图像)

最终并行化这块耗时为:Max(下载资源文件,请求首屏数据输出片段) = 1000ms。

根据变化后的节点我们算出首屏呈现时间为: 2350ms

首屏呈现耗时的通用计算公式变为:

下载静态片段 + Max(下载资源文件,请求首屏数据) + 应用初始化 + 首屏初始化 + 首屏渲染

Step2:应用初始化,资源文件下载,首屏数据请求节点并行

在 Step1 的基础上继续分析,应用初始化节点耗时也很明显,同时该节点要进行必须等待资源文件下载完毕,但理论上可以不依赖我们的首屏数据,还是可以让其和首屏数据请求并行。

这里我们无法在 Step1 方案上直接将应用初始化和数据请求并行化,主要原因在于当首屏数据请求时间大于资源加载 + 应用初始化完成时间时,应用会在没有数据的情况下进入收入首屏渲染节点,从而导致异常。

解决方案是将数据片段的输出变成 promise 片段:

  1. pending promise 片段,与静态片段一起输出,大概如下:
复制代码
<script>
window.__APP_DATA__ = {
RESOLVERS: {}
userInfo: new Promise((resolve, reject) => {
// 超时认为失败
let timer = setTimeout(reject.bind(null, {message: 'timeout'}), 12000);
window.__APP_DATA__.userInfo = (err, data) => {
clearTimeout(timer);
err ? reject(err) : resolve(data)
}
})
};
</script>
  1. resolve promise 片段,该片段在数据请求成功返回后输出,大概如下:
复制代码
<script>window.__APP_DATA__.RESOLVERS.userInfo(null, data); </script>
  1. reject promise 片段,该片段在数据请求失败后输出,大概如下:
复制代码
<script>window.__APP_DATA__.RESOLVERS.userInfo(error); </script>

即此时应用初始化完毕后可以无视首屏数据的完成度,直接进入首屏渲染节点,组件在数据 promise 被 resolve 后渲染即可:

复制代码
window.__APP_DATA__.userInfo.then(data => component.render());

通过对数据片段的 promise 化改造,使得应用初始化节点也加入了并行队列。

整个首屏呈现 timeline 变化如下:

(点击放大图像)

根据变化后的节点我们得到首屏呈现时间为: 1800ms

首屏呈现耗时的通用计算公式变为:

下载静态片段 + Max(下载资源文件 + 应用初始化,请求首屏数据) + 首屏初始化 + 首屏渲染

优化小结

经过上述 2 个步骤改进,我们应用首屏呈现时间从 2800ms -> 2350ms -> 1800ms,总体效果约为 36%,可以看到是收益还是很可观的。

在实际项目中耗时是在1600ms左右,比 1800ms 还要小,主要原因如下:

  1. 用户在请求入口页中半个 RTT 时间,服务器就开始了数据请求。
  2. 数据请求在服务端进行减少了浏览器与服务端的请求创建开销,同时数据请求在内网进行,总体调用速度也会加快。

当首屏数据请求数超过浏览器并发请求数时,该方案收益会更明显,因为 NodeJS 端没有并发限制,甚至在 NodeJS 端与后端服务的交互中可以采用更高效的协议如 HTTP2 来提高调用速度。

与 SSR 方案的对比

看到这里,相信很多人会问,为啥不用服务端渲染直出 HTML 呢,或者和服务端渲染方案相比有何优势?

事实上,一开始我和大多数人想到的优化方案就是服务端渲染,但真正的障碍在于服务端渲染依赖视图层框架的支持,而我们的项目历史悠久,视图层框架并不支持这一点,为了优化而丧失产品的稳定性得不偿失。

当然,在另辟蹊径使用了数据渐进式预加载方案后,我总结该方案与 SSR 的对比如下。

优势

  1. 对客户端代码来说数据渐进式预加载方案实现成本非常简单,基本可以做到透明化,我们在实际的开发过程中采用基于 uIoC( https://github.com/ecomfe/uioc ) 提供的 AOP 拦截方案,通过配置化的方式让客户端的代码改造仅局限在配置文件,应用代码基本未改动。
  2. 对 NodeJS 端来说,分层合理的应用只需要将数据层简单适配下 NodeJS 端即可完成数据渐进式预加载,这对底层基础框架在视图层没有支持同构的应用来说,整个改造成本可以说大大减小,且收益明显。我们目前的应用基于自有的一套 MVC 框架,仅仅是将 Model 层简单适配 NodeJS 端执行输出数据。
  3. 服务端渲染方案如果未能提供较基于 BigPipe 的渲染,总体的页面呈现速度还是不如数据渐进式预加载的,且目前我也暂时还没有在三大框架中发现有一套基于 BigPipe 的服务端渲染方案。

不足

整体呈现速度可能不如结合了 BigPipe 的服务端渲染方案,但这点没有经过论证,毕竟数据渐进式预加载与服务端同构渲染的区别仅仅在于渲染环节放在客户端还是服务端:渲染看的是 CPU,服务端的 CPU 资源是有限的,要服务诸多请求,而客户端渲染则基本无此压力,渲染能力未必弱于服务端。

总结

我们在单页应用的性能优化上基于很朴素的并行化理念实施了首屏数据渐进式预加载方案,在实际项目中也得到了较为明显的效果,减少了 1.2s 的加载时间,整体的节点变化如下:

优化前:

(点击放大图像)

优化后:

(点击放大图像)

最终数据渐进式预加载方案的首屏呈现时间计算公式为:

下载静态片段 + Max(应用资源加载 + 应用初始化,请求首屏数据) + 首屏初始化 + 首屏渲染

这里忽略了影响很小的片段传输时间,有打算尝试的朋友可以将自己应用的相关节点数据代入计算即可。

数据渐进式预加载,服务端同构渲染,客户端渲染三种方案各有优缺和场景,个人未来计划是将三种方案结合实时流量数据动态切换:在服务器压力不大时用同构渲染;服务器压力较大时用数据预加载;服务器压力很大时用客户端渲染。


感谢韩婷对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-03-30 17:342593

评论

发布
暂无评论
发现更多内容

咸阳有没有等保测评机构?在哪里?怎么联系?

行云管家

等保 等保测评 等保测评机构 咸阳

Openjob 1.0.2 重磅发布,新一代分布式任务调度框架

stelin

分布式架构 Java 分布式

AI老师的作者:17岁的高中生,可能是你想要孩子成为的样子

无人之路

AI 教育 ChatGPT

NFTScan 成为 CMC 官方 NFT 数据合作伙伴

NFT Research

crypto NFT

软件测试 | Web自动化测试

测吧(北京)科技有限公司

测试

世界500强潍柴动力携手用友BIP全球司库打造资金管理系统,正式启动!

用友BIP

全球司库

2022百度ESG报告发布:年度答卷展现安全信任承诺

百度安全

主动发现系统稳定性缺陷:混沌工程 | 京东云技术团队

京东科技开发者

混沌工程 系统 系统工程 混沌工程实践 企业号 6 月 PK 榜

横空出世!京东技术专家狂推的Redis笔记,实战和原理两开花

程序知音

Java 数据库 redis Java进阶 后端技术

Kubernetes 集群管理:Kurator or Kubespray

华为云开发者联盟

云原生 后端 华为云 华为云开发者联盟 企业号 6 月 PK 榜

赋能生态合作 共话数字创新 | 2023开放原子全球开源峰会软硬协同开源分论坛即将启幕

开放原子开源基金会

开源 开放原子全球开源峰会 开放原子 软硬协同开源

用户组是什么意思?怎么容易理解?有什么作用?

行云管家

运维 权限 用户组

软件测试 | Selenium验证页面元素

测吧(北京)科技有限公司

测试

技术赋能-混流编排功能,助力京东618直播重保 | 京东云技术团队

京东科技开发者

监控 直播技术 直播推流 企业号 6 月 PK 榜 重保

TiDB 7.1 LTS 发版:为关键业务提供业务稳定性和多租户场景支持

编程猫

TiDB pingCAP

让ChatGPT来写今年的高考作文,能得几分?

楚少AI

ChatGPT4 2023高考 ChatGPT写作

金融集团企业资金管理难度加倍,用友BIP如何破解这个难题?

用友BIP

资金管理

不愧是阿里P8出手的并发编程笔记!颠覆了我以往"正确"的认知

程序知音

Java 并发编程 java架构 Java进阶 后端技术

【Clickhouse】ReplaceingMergeTree引擎final实现合并去重探索 | 京东云技术团队

京东科技开发者

OLAP final clickhosue 企业号 6 月 PK 榜 合并去重

阿里云 EMAS & 魔笔:5月产品动态

移动研发平台EMAS

阿里云 DevOps 消息推送 低代码平台

2023年数智人力管理不容错过的2大神器

用友BIP

人力资源 数智人力

揭秘阿里云Flink智能诊断利器——Fllink Job Advisor

阿里云大数据AI技术

大数据 flink 企业号 6 月 PK 榜

GreptimeDB v0.3 正式发布|分布式能力全面提升

Greptime 格睿科技

数据库 rust 云原生 分布式数据库 时序数据库

祝贺!Databend 入选 ICT 中国可信云优秀云原生创新案例

Databend

2022 中国开源创新大赛,时序数据库 TDengine 榜上有名

爱倒腾的程序员

涛思数据 时序数据库 ​TDengine

10分钟了解Kubernetes网络

俞凡

架构 Kubernetes 云原生

黑盒不黑:跨端 C/C++ 库一键源码调试方案

阿里技术

调试

Mybatis的parameterType造成线程阻塞问题分析 | 京东云技术团队

京东科技开发者

mybatis CPU告警 企业号 6 月 PK 榜 线程阻塞

理论+示例,详解GaussDB(DWS)资源管理

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 6 月 PK 榜

最强攻略 | 1分钟带你了解内测,成为BUG小能手!

百度Geek说

百度 测试 企业号 6 月 PK 榜 6 月 优质更文活动

苹果MR Vision Pro将会带动哪些零部件出货?

元器件秋姐

苹果 AR 交互 vr MR

百度SSP单页式应用性能优化实践_最佳实践_邓欣欣_InfoQ精选文章