内容简介:目标提升前端和Node中间层性能通天塔是京东内部的一个快速搭建活动页面的平台,用户可以通过在可视化平台上选择需要的模板及配置对应数据,来生成对应的原生、H5及PC活动页面。模板样式丰富,操作灵活,在京东被大量使用,用户流量也呈现出了非常迅猛的增长。但随着项目的迭代,功能越来越复杂,模板越来越多,前端和Node中间层性能问题也逐渐暴露出来,其中,前端首屏加载时间TP75性能要大于2秒,而Node中间层单核QPS相较其他应用也较低。为了提升用户体验,同时提高系统的吞吐率,在2019年下半年我们通过对现有通天塔H
目标提升前端和Node中间层性能
通天塔是京东内部的一个快速搭建活动页面的平台,用户可以通过在可视化平台上选择需要的模板及配置对应数据,来生成对应的原生、H5及PC活动页面。模板样式丰富,操作灵活,在京东被大量使用,用户流量也呈现出了非常迅猛的增长。但随着项目的迭代,功能越来越复杂,模板越来越多,前端和Node中间层性能问题也逐渐暴露出来,其中,前端首屏加载时间TP75性能要大于2秒,而Node中间层单核QPS相较其他应用也较低。为了提升用户体验,同时提高系统的吞吐率,在2019年下半年我们通过对现有通天塔H5项目进行一次全盘分析,做了一次全方位的性能优化。
综合性能提升超过30%
通天塔H5是以React SSR为基础进行架构的,首屏页面在Node中间层进行数据请求及渲染,分页和其他异步请求在客户端请求网关接口并渲染,静态资源托管在CDN,如图1所示。
图1:通天塔H5请求流程
基于以上架构,经过2019年下半年的优化,通天塔H5前端首屏加载性能和Node中间层渲染性能都得到了极大提升。
1
首屏加载性能
首屏加载耗时,TP75从原来的2.47秒减小到了1.58秒,性能提升约36.03%。图2所示为优化前后以周为维度的首屏TP75加载时长。
图2:首屏TP75加载时间
此处首屏加载时长,指用户打开页面到首屏第一张图片请求完成所经历的时间。横坐标代表第几周,01代表今年第一周(2020/01/06 ~ 2020/01/12),52代表去年最后一周(2019/12/23 ~ 2019/12/29)。
2
服务器性能
通过对服务器进行压测,在相同QPS维度下,CPU从原来的29.52%降低至20.5%,CPU使用率相比之前降低了30.5%
图3:同QPS下CPU利用率
线上性能分析
在进行优化前,需要先采集当前线上数据,了解服务目前的性能情况,我们主要通过以下方式对项目的性能进行分析及监控。
1
Performance API
Performance API是ECMAScript5才引入的,精度可达到1毫秒的千分之一,目前主流浏览器基本都已经支持performance对象。通过performance.timing对象,可以拿到浏览器处理网页各个阶段的耗时。通过performance.getEntries方法,可以获取js, css, 图片及ajax在内的所有请求的耗时信息。
我们基于Performance API,封装了一个前端测速模块,该模块在页面加载完成后将所需性能数据上报至服务器,之后可以在可视化平台上进行数据的展示及分析。
2
Chrome Devtools
Chrome Devtools是前端调试及性能分析常用的工具,通过该工具,可以查看页面资源加载情况,所加载CSS、JS及图片的大小,还可以通过Performance面板,查看页面渲染绘制和Script执行情况。
3
v8-profiler
通天塔H5是基于React SSR架构的,页面首屏在Node中间层请求数据并渲染,所以除纯前端的监控及分析,还需对Node层进行性能分析及优化。在Node层性能分析中,我们主要通过v8-profiler模块进行性能分析。
在本地或测试环境下新增两个路由:
const profiler = require('v8-profiler');
router.get('/profiler/start', (req, res) => {
//Start Profiling
profiler.startProfiling('CPU profile');
res.end('profile start');
});
router.get('profiler/end', (req, res) => {
const profile = profiler.stopProfiling()
profile.export()
.pipe(res)
.on('finish', () => {
profile.delete();
res.end();
});
});
通过ab压测工具,对服务器发起请求
ab -c 10 -n 1000 http://localhost:7001/mall/active/xxx/index.html
在压测过程中,可以通过http://localhost:7001/profiler/start开始性能统计,通过http://localhost:7001/profiler/end结束性能统计,并将结果保存为 ***.cpuprofile文件,通过Chrome Devtools中的JavaScript Profiler工具进行分析。
图4:Node服务端代码执行情况
常规优化+业务特性优化
我们主要从两个方面进行性能优化,一方面是基于较为通用的前端常规性能优化方案,另一方面基于通天塔业务特点进行的偏业务方面的优化。
1
前端常规优化
在优化之初,根据《高性能网站建设指南》提及的常规优化方案,我们检查了项目中需要改进或深度优化的地方,主要涉及以下方面:
-
尽可能的减少HTTP的请求数,减小HTTP请求大小
-
将静态资源放在CDN,最大化利用CDN缓存能力
-
减少CSS和JS请求个数,减小CSS和JS包大小
-
启用gzip压缩
1 )减少图片请求大小
在通天塔页面中,图片请求一般占比最大,在前期开发过程中,针对图片请求我们已做过懒加载优化,图片请求数很难更进一步减少,但针对图片大小,我们可以进行优化。
图5:图片展示尺寸及实际尺寸
如图5,在页面中图片实际所展示坑位大小为115x115,即使在3倍屏上,所需图片尺寸也只是345x345,但实际请求中,图片的原始尺寸却是800x800,这对用户流量是一种浪费,同时也增加了图片加载耗时,而通天塔活动页中,这种类似的图片还有很多,而同时,京东图片服务器正好支持图片的裁剪,原来一张800x800的图片,可以按比例缩小到所需的高度,如一张800x800的原图 https://m.360buyimg.com/babel/jfs/t1/85209/24/15512/218570/5e7179d8E957c16c1/5fbcc42fad37fe94.jpg!q70.dpg ,通过修改URL,可以改成下发230x230尺寸的图 https://m.360buyimg.com/babel/s230x230_jfs/t1/85209/24/15512/218570/5e7179d8E957c16c1/5fbcc42fad37fe94.jpg!q70.dpg
鉴于此,针对图片大小,可以做按实际展示大小请求对应尺寸的图片的优化,以减小HTTP的大小。
const isJfsRegex = /360buyimg\.com\/.*\/((s([\d^_]+)x([\d^_]+)_)?jfs)/i;
export function resizeImg(url, rect) {
const dpr = window.devicePixelRatio;
if (!isJpegRegExp.test(url)) {
return url;
}
const result = url.match(isJfsRegex);
if (!result) {
return url;
}
if (result[3] && result[4]) {
if (!rect || (!rect.width && !rect.height)) {
return url;
}
if (rect.width && !rect.height) {
rect.height = rect.width / result[3] * result[4];
}
if (rect.height && !rect.width) {
rect.width = rect.height / result[4] * result[3];
}
const t = 's' + Math.ceil(rect.width* dpr) + 'x' + Math.ceil(rect.height* dpr) + '_jfs';
return url.replace(result[1], t);
} else {
if (rect && rect.width && rect.height) {
return url.replace('/jfs/', `/s${Math.ceil(rect.width * dpr)}x${Math.ceil(rect.height * dpr)}_jfs/`);
}
}
return url;
}
2 )最大化利用 CDN 缓存
在做性能优化前,通天塔静态资源的打包,是开发者在上线前,在自己电脑上进行的,且文件名会依据文件内容重新生成,格式为[filename].[contenthash:8].js。
按这种方式在个人电脑上打包,即使有package-lock.json锁定包版本,但由于个人电脑操作系统及使用的npm包管理 工具 的不同(有的包管理工具不读package-lock.json),node_module下的文件可能会不一致,导致文件的contenthash不同。
针对这个问题,我们基于Jenkins搭建了一个前端CI打包系统,后继所有上线前的前端静态资源打包,都迁移到CI上进行,通过这种方式,确保了文件名的一致性,以最大程度的利用CDN缓存。
3 )调整 webpack 打包策略,按需加载 CSS 和 JS
在性能优化前,通天塔的CSS和JS资源是按以下策略打包的:
-
vendor.[contenthash:8].js: 包含node_module下的代码
-
common.[contenthash:8].js: 包含非node_modules下的代码
-
[channel].[contenthash:8].js: 通天塔有很多渠道,每个渠道的专属代码打包到这个JS中
-
template.[contenthash:8].css: 包含所有渠道通用CSS
-
[channel].[contenthash:8].css: 包含渠道专有CSS
按这种方式来进行打包,有以下两个问题,
1. 每个活动会加载所有模板对应的CSS和JS,造成不必要的加载。
2. 所有的系统模板代码都打包到common包,导致common包非常庞大,而其中有任何一个模板代码有改动,都会影响到common包的文件名,从而导致CDN缓存失效,客户端必须重新请求CDN。
针对两个问题,我们改进了打包策略,最终方案改为:
-
vendor.[contenthash:8].js: 包含node_module下的JS文件
-
lowUsedTemp.[contenthash:8].js: 包含使用频率低的系统模板代码,页面会按照活动是否使用到低频模板按需请求
-
mute.[contenthash:8].js: 包含剔除低频使用模板后,较为稳定,很少改动的系统模板
-
template.[contenthash:8].js: 包含剩余非node_modules下的代码
-
[channel].[contenthash:8].js: 包含渠道专属代码
-
lowUsedTemp.[contenthash:8].css: 包含使用频率低的系统模板代码的CSS,页面会按照活动是否使用到低频模板按需请求
-
template.[contenthash:8].css: 包含所有渠道通用CSS
-
[channel].[contenthash:8].css: 包含渠道专有CSS
按照这种方式打包,只有使用到低频使用模板的活动,才会加载lowUsedTemp.[contenthash:8].css和lowUsedTemp.[contenthash:8].js,其中按需加载的CSS占总CSS大小的25%,按需加载的JS占总大小的17%。而单独打包出的mute.[contenthash:8].js这个JS资源,由于里面包含的模板很少被改动,所以在打包上线时,其文件名也很少会变,这就可以利用CDN缓存,不会每次上线后,用户都重新请求这部分代码。
2
业务优化
在常规的前端性能优化达到瓶颈后,我们开始尝试基于业务进行性能优化。
1 )首屏精准化优化
通天塔页面是运营在可视化配置平台中,通过选择模板,配置数据来动态生成的,而其中类似商品楼层这种素材楼层,配置的素材数量也由运营自己决定,少的可能只有几个,多的几十上百个,这便导致通天塔首屏页面有以下特点
1. 页面灵活多变,页面结构难以预测。
2. 在请求首屏楼层数据时,服务端难以计算需要下发几个楼层刚好满首屏,故按照素材楼层数来进行分页,如果首页素材楼层配置的素材较多,节点数会非常庞大。
由于以上两个特点,导致很多活动页首屏的内容,远大于客户端首屏实际所需展示的长度,这既加大了首屏的渲染耗时,同时也浪费了Node服务器的CPU资源(渲染了不必要的楼层)。
另外,在通过v8-profiler测试Node服务器性能时,我们发现Node服务器端开销最大的地方有三处
-
通过JSON.parse解析后端下发的活动数据
-
React.renderToString 进行首屏渲染
-
JSON.stringify将首屏数据序列化后跟随HTML下发给客户端
综合考虑各种优化方式,最终决定采用在Node层计算每个楼层高度,按首屏高度渲染楼层数的方案。
图6:首屏精准渲染流程
1. 用户向Node中间层发起请求的时候,客户端会向cookie中埋入设备宽高信息。
2. Node中间层从cookie中获取设备宽高信息,若获取失败,则使用默认值。
3. 循环计算每个楼层的高度,如果楼层累计高度超过2倍设备高度,丢弃后面的楼层数据,并重置分页信息。
4. Node中间层根据计算过后的数据,渲染首页楼层,并将数据序列化后随HTNL下发到客户端。
5. 前端检测页面是否满两屏,若没满两屏,立即请求下一页数据。
在楼层维度的分页优化完毕后,还可以精益求精,针对素材楼层进行楼层内素材的优化,如果首页最后一个素材楼层中有素材超过两屏,还可以将超过两屏的素材降级到客户端来渲染。
上面结论中所提到的服务端性能优化,主要就是通过首屏精准化优化实现的。
2 )首屏图片预加载
由于通天塔页面的灵活性,开发者并不知道哪些楼层元素会出现在首屏,所以页面中所有的图片资源,统一设置为懒加载模式。而基于通天塔H5 SSR的架构,首页在服务端渲染完成后下发给前端,前端只有在加载完JS后判断图片是否在首屏,在首屏的才开始加载图片,这就会造成图片必须要在JS加载执行完成后才进行加载,图片坑位的白屏时间较长。
基于以上图片加载滞后的问题,前期我们做了第一个优化是,在第一屏楼层之后插入一段内联JS,获取页面已加载的图片元素,如果在首屏则先将首屏图片的data-src置为src进行加载,但发现以下问题,下面是一个测试Demo。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<img data-src="/img-size.png" id="test" />
<h1>我是测试代码</h1>
<script>
(function() {
var start = Date.now();
console.log('start', start)
var img = document.getElementById('test');
img.onload = function() {
console.log('end', Date.now(), Date.now() - start); // end - start > 2000ms
}
img.src = img.getAttribute('data-src');
})();
</script>
<h1>我是测试代码22222</h1>
<img src="/performance-cpu.jpg" />
<script src="/test.js"></script>
</body>
</html>
其中test.js中很简单
console.log('start');
var start = Date.now();
for(;;) {
if (Date.now() - start > 2000) {
break;
}
}
console.log('end');
图7:Demo页加载执行
从图中可以看出这个图片依然在JS执行完毕后才加载。但我们发现performance-cpu.jpg这张图片,由于浏览器对图片资源预加载的缘故,没被JS阻塞,基于浏览器对图片的预加载,及上面做的首屏精准化渲染,我们又进行了一个优化:在计算首屏楼层高度时,给处于首屏的部分图片元素打标,根据这个标识在render渲染时,对打标的图片不进行懒加载处理,而直接用src,通过这个优化,图片将不被JS阻塞,提前了首屏图片开始加载的时机,减少了图片坑位的白屏时间。
3 )交互优化
在通天塔活动中,有许多包含多Tab的模板,如商品类模板,在之前的开发中,每次切换Tab我们都是销毁之前Tab下的内容,重新渲染新Tab下的内容,这样在重新切换回去的时候,还是需要重新渲染,造成页面的卡顿。基于此,我们将Tab渲染改成了增量渲染,这样在切回上一个Tab的时候,白屏及渲染时间会大大减少。
优化是持续性工作
在通天塔H5前端性能优化的过程中,很大一部分是站在巨人的肩膀上进行的,但光这些远远不够,当纯技术优化到一定程度后,更需要根据自己的业务特点,从业务层面进行优化,这个优化的效果可能更好。
通天塔前端的优化在此告一段落,但优化之路还远未结束,后续我们还会更进一步,基于自身的业务以及一些新技术,继续深入优化,同时也希望本文能给前端及后台开发人员带来一些新的想法。
以上所述就是小编给大家介绍的《通天塔前端性能优化实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
互联网:碎片化生存
段永朝 / 中信出版社 / 2009-11 / 42.00元
《互联网:碎片化生存》内容简介:在世界互联网人数超过17亿,中国网民接近4亿的时候,断言“这个版本的互联网没有未来”是要冒很大风险的。我们生活在比特和连线的世界,现代互联网所描绘出的“数字化”、“虚拟化”的未来是否完全值得信赖? 现代商业取得了巨大成功,但这并不是电脑和互联网精髓的自由体现,我们所使用的这个版本的电脑和互联网只不过是“被阉割”、“被劫持”的商业玩偶。 《互联网:碎片化生......一起来看看 《互联网:碎片化生存》 这本书的介绍吧!