作者: vivo 互联网大数据团队- Wang Lei
本文是vivo互联网大数据团队《BI 数据可视化平台建设》系列文章第3篇。
随着越来越多代码的堆积,平台的运行加载性能也在逐步下降,在不同程度上极大地影响了用户体验,从而导致用户流失。本文就是从这样的一个背景出发,通过对BI数据可视化平台的一系列的性能优化实践,给大家系统性阐述首页性能优化的核心策略,并探讨在日常开发中如何实现长效性能保障。
往期系列文章:
文章太长?1分钟看图抓住核心观点👇
一、背景
随着业务的拓展和用户数量的激增,平台经历了多个周期的快速迭代,整体功能场景也越发复杂的。在快速迭代的过程中,我们很容易忽略平台的性能,到了某一个节点,猛地发现,随着越来越多代码的堆积,平台的运行加载性能也在逐步下降,在不同程度上极大地影响了用户体验,从而导致用户流失。本文就是从这样的一个背景出发,通过对BI数据可视化平台的一系列的性能优化实践,给大家分享一下如何提升首页性能的思路,并且让我们在日常开发中,如何持续保持高性能,而不是又一次回过头来优化性能。本文主要给大家介绍一平台在进行首页性能提升的一些实践经验。
二、了解性能指标
2.1 用户体验核心指标
衡量一个 Web 页面的体验和质量有非常多的指标,根据页面加载流程可以将指标分成三大类:
-
文档加载相关(TTFB、DCL等)
-
内容呈现相关(LCP、FCP、FMP 等)
-
交互响应相关(INP、FPS 等)
针对这么多的性能指标,Google 提出了网站用户体验的三大核心指标 (LCP INP CLS),分别用来衡量用户感知的加载速度、量化网页首次互交互的感受、衡量网页视觉的稳定性:
图片来源:https://web.dev/
2.2 平台度量指标
但是在实际规划平台性能度量体系时,我们可以根据自身的业务和需求进行自定义,针对数据可视化平台来说,我们更看重用户感知的加载速度,如何让用户快速看到数据可视化内容是我们首页性能优化的关键,因此我们性能指标主要以文档加载和内容呈现为主,这里我们以 TTFB、FCP、LCP 作为我们首页性能的度量指标,它们涵盖了网络请求到页面主要内容加载的过程:
网络请求过程(网络响应衡量指标 TTFB):
TTFB 主要指的是以下请求阶段耗时的总和:
-
重定向时间
-
Service Worker 启动时间(如果有)
-
DNS 查找
-
连接和 TLS 协商
-
请求,直到响应的第一个字节到达
页面主要内容加载过程(内容呈现衡量指标 FCP、LCP):
图片来源:https://web.dev/
- TTFB(Time to First Byte ):
它主要测量的是在网络请求阶段中,从请求资源到响应的第一个字节到达所经过的时间,这有助于识别 Web 服务器因速度过慢而无法响应请求。由于 TTFB 发生在指标 FCP(First Contentful Paint ) 和 LCP(Largest Contentful Paint)之前,因此希望服务器能够快速地响应导航请求。一般来说,大多数网站都应尽量将 TTFB 控制在 0.8秒 以内,且超过75%以上PV 达到该范围。
图片来源:https://web.dev/
- FCP(First Contentful Paint):
它用于标记网页加载过程中用户可以在屏幕上看到的第一个元素所用的时间。元素主要是指文本、图片(包括背景图片)、SVG 或 Canvas。可以用于衡量用户感知的加载速度。为了提供良好的用户体验,网站的 FCP 最好不要超过 1.8 秒,且确保超过75%以上PV 达到该范围。
图片来源:https://web.dev/
- LCP(Largest Contentful Paint):
它用于标记网页加载过程中加载了网页主要内容的时间点。可以用于衡量用户感知的加载速度,也是Google 提出的度量用户体验的三大核心指标之一。为了提供良好的用户体验,网站的 LCP 最好控制在 2.5 秒 以内,且确保超过75%以上PV 达到该范围。
图片来源:https://web.dev/
三、首页性能现状
**背景:**性能问题主要是由于前期开发人力有限、功能快速迭代等原因,导致代码质量和可维护性较差,积累下的技术债务。
(1)平台性能指标分析:LCP (首屏平均耗时) 高达3.3s,远高于Google LCP衡量的标准(2.5s)。
(2)通过Lighthouse工具进行性能分析,性能得分较低,各项性能指标都处于不及格的水平。
四、分析性能问题
4.1 通过Network面板分析
从Network面板上,可以分析得出加载过程中存在以下几类问题:
-
入口文件体积太大,加载耗时长,阻塞其他资源加载
-
低优先级资源阻塞了高优先级资源加载
-
微前端子应用等非首屏依赖资源没有进行异步加载
-
网络传输协议还处于HTTP1.1,传输效率低
-
接口请求传输的数据体过大,存在大量冗余数据
4.2 通过Performance面板分析
从 Performance 面板上,可以分析得出加载过程中存在以下几类问题:
-
FPS 长时间出现红色条形,表示帧速率下降得太低,可能出现动画延迟卡顿等问题
-
CPU 资源占用率过高,可能出现性能问题
-
主线程存在多个 Long Task(长任务),阻塞了页面加载渲染
4.3 通过Lighthouse工具分析
通过 Lighthouse 工具,可以分析得出加载过程中存在以下的问题:
-
大量 Long Task 阻塞了主线程工作
-
存在阻塞渲染的低优先级资源
-
DOM节点数过多,增加了内存占用,样式计算用时延长,并产生高昂的布局重排成本
-
图片资源不是最优压缩效果的格式
-
存在大量未使用的CSS和JS文件代码
五、优化实践
5.1 优化方向 (时间和空间)
通过上述的问题分析,我们可以分析出资源加载渲染耗时 以及浏览器性能资源占有 都有可能导致页面卡顿缓慢,影响用户体验,因此可以从耗时和资源占用两方面来进行性能优化,也可以理解成时间和空间的优化。
5.2 时间优化 (网络耗时、加载耗时、渲染耗时等)
(1)网络传输耗时优化
网络传输耗时优化主要可以从 缓存策略、传输协议、资源预加载预解析、CDN 等几个方向进行。本次优化主要是通过网络传输升级、资源预加载、请求性能优化等方面来讲解一下。
-
网络传输协议升级,由 HTTP/1.1 升级至 HTTP/2.0 版本,通过 HTTP/2.0 多路复用的特性解决了请求并发数限制的问题,同时二进制传输和头部压缩等特性也提高了网络传输的效率。
-
删除资源预加载(Preload),减少首页非关键资源的预加载处理。通过加载瀑布流可以看到,这里提前加载了多个非首屏关键资源的字体文件,且文件体积高达 1.8MB,阻塞了首屏关键资源的加载解析。所以我们需要根据资源的优先级,合理的使用 Preload(预加载)和 Prefetch(预解析)。
-
请求性能优化,降低请求响应耗时。通过Network面板可以看到,首页依赖的主要接口返回的数据体在没压缩前高达 3.1 MB,这里我们对请求内容进行了分析,通过异步请求、减少非关键的冗余数据等处理将传输数据体积降低到了 500KB 内。除了减少数据传输量,我们还可以通过请求合并,利用缓存等减少通信次数来进行请求性能优化。
(2)资源加载耗时优化
资源加载耗时优化可以从 代码压缩、代码分包、组件、工具库、ICON等按需加载等几个方向进行。主要是通过优化体积来减少资源加载耗时,从而提升首屏性能。
-
首先通过 webpack-bundle-analyzer 插件对包体积进行分析,可以看到 chunk-vendors.js 文件体积较大,同时还存在依赖嵌套等问题,导致资源加载缓慢。本次优化我们通过代码分包、资源按需加载,图片格式优化等措施,减少了资源体积, chunk-vendors.js 文件也从 2.3MB 降低到 480KB。下面我们通过几个具体示例进行讲解:
对 Echarts 、Ant-Design-Vue-1.x ICON等UI工具库进行按需加载。
// 优化前 在入口文件进行全量的同步加载
import * as echarts from 'echarts/core';
import { XXXChart } from 'echarts/charts';
import { XXXComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';echarts.use([XXXChart, XXXComponent, CanvasRenderer]);
// 优化后 根据使用场景进行按需加载
async function initEcharts(chartType){
const echarts = await import('echarts/core');
const { XXXChart} = await import('echarts/charts');
const { XXXComponent } = await import('echarts/components');
const { CanvasRenderer } = await import('echarts/renderers');
echarts.use([XXXChart, XXXComponent, CanvasRenderer]);
}
由于历史需求迭代原因,我们对 Ant-Design-Vue-1.x 进行了二次定制开发,这也导致了ICON全量引入,我们这里使用的方案是重定向到本地文件来进行控制 ,使用 alias 将 @ant-design/icons/lib/dist 指向项目中的 antdIcon.js,然后在 antdIcon.js 文件中按需导出即可,通过按需加载,ICON引入体积从 500K+ 降低到 30K+。
// vue.config.js alias配置
resolve: {
alias: {
'@ant-design/icons/lib/dist$': path.resolve(\_\_dirname, './src/plugins/antdIcons.js'),
}
}
// src/plugins/antdIcons.js
export { default as CheckCircleOutline } from '@ant-design/icons/lib/outline/CheckCircleOutline';
export { default as CheckCircleFill } from '@ant-design/icons/lib/fill/CheckCircleFill';
-
检查删除冗余依赖,避免重复npm包引入;随着平台长期的发展迭代,或多或少都会存在冗余的 mf、npm 资源,同时我们在对微前端子应用的包体积进行分析时,发现子应用通过 npm 引入的Echarts,而主应用本身也引入相同的库,相对于引入了2 遍 Echarts,这个时候我们改造了子应用的依赖引入方式,通过传参的方式将Echarts实例传递给子应用,避免重复引入和加载相同资源。
//主应用 通过props传递依赖
import { start, loadMicroApp, prefetchApps } from 'qiankun';export default {
name: 'MicroWidgetReact',
methods: {
async loadMicroApp(){
const echarts = await this.initEcharts();
this.microApp = loadMicroApp({
name: `xxx`,
props: {
...props,
echarts: echarts,
},
});
},
},
};//子应用配置 externals 并且外链依赖加上 ignore 属性(这是自定义的属性,非标准属性)
<script ignore src\="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"\></script>// 当它独立运行时,使用自己的外链依赖 window.echarts const echarts = parent.echarts || window.echarts;
-
对图片、字体等资源文件进行格式优化;我们将图片资源统一转换成WEBP格式,除了文件大小和压缩效率上有优势,WEBP还支持透明度和动态图像等,所以如果不需要考虑 IE以及旧版本Safri的兼容性,WebP 格式更适用于网页开发;而字体文件则转换成WOFF2格式,对比 TTF 格式在文件大小、压缩效率和安全性上都更具优势。
5.3 空间优化 (CPU 占用、内存占用、本地缓存等)
我们在做性能优化的时候,很多情况下都会依赖时间换空间、或者空间换时间等方式,这里需要根据项目的实际情况做出取舍,选择相对合适的一种方案去进行优化。资源占用常见的优化方式包括:
-
代码优化:精简和优化 JavaScript 和 CSS 代码,避免使用过多的循环和递归操作,减少对 CPU 的占用。
-
避免内存泄漏:定期检查并优化内存使用,避免出现内存泄漏问题,可以使用浏览器的开发者工具进行内存分析。
-
图片懒加载:延迟加载图片,只有当图片进入可视区域时再加载,减少内存占用。
-
数据本地存储:使用浏览器提供的本地存储功能(如LocalStorage或IndexedDB),将一些数据缓存到本地,减少对网络请求的依赖,提高性能。
-
使用 Web Workers:将一些耗时的任务放到 Web Workers 中执行,减轻主线程的负担,从而减少 CPU 占用。
-
使用服务端渲染:使用服务端渲染技术,减少客户端的计算压力,提高页面加载速度。
-
使用资源压缩:对 JavaScript、CSS、图片等资源进行压缩,减小文件大小,降低网络传输和内存占用。
本次我们主要使用了 Web Workers 和 数据解绑 (Object.freeze) 等方式进行空间优化,减少了CPU和内存的占用。通过 Web Workers 将需要复杂计算任务放到 Worker 线程,避免阻塞其他首页渲染任务,释放主线程资源,实现单线程到多线程。但是这里要注意,过多的使用 Web Workers 有时候反而会导致资源的过度占用,因为 Web Workers 本身也会占用一定的内存资源,而 Workers 之间的通信和数据同步也可能会带来复杂性和性能开销,特别是在大规模的并发任务处理时,所以我们需要根据场景合理使用。
六、优化前后对比
整体性能提升 292%:
优化后的加载效果对比:
七、性能监控
为了保证平台在后续的迭代过程中,持续保持高性能,我们引入Chrome 开源的 web-vitals 库,结合自研的运行时性能监控埋点(卡顿、崩溃),以及平台的数据可视化能力,实现对前端整体性能的监控。并利用了平台的数据监控预警能力,通过对不同指标的配置告警服务,增加性能指标相关的告警,在性能指标发生异动时,及时发现问题,优化性能,保障了用户使用体验。
八、总结
上面讲了那么多优化方法,都是针对当前项目进行的针对性优化 ,所以我们进行优化时,需要根据具体情况和需求,结合不同的优化策略来达到最佳的性能优化效果。前端性能优化是一个重要的主题,它涉及到许多方面,包括页面加载速度、交互响应时间、资源利用效率等。但不管什么样的优化方式,他们的核心思路都是一致的,因为在用户能看到页面,并且与之交互之前,都是有固定的步骤的,所以优化的核心思路就是:**尽可能去掉一些关键步骤、尽可能提前一些重要步骤、尽可能优化某个具体步骤。**比如 SSR 相比于 CSR,用户能更快的看到页面,就是去掉了「下载入口index.html,下载并执行 CSS、JS,请求接口」这几个关键步骤,比如上面说的对高优先级资源进行预加载就是提前一些重要步骤,再比如说通过web workers 避免 JS 执行时产生 Long Task就是优化某个具体步骤。以上就是本次性能优化实践的所有内容,希望能对你有所帮助。