飞猪双十一会场再加速,营销域 SSR 解决方案的落地实践
曾经在PC时代通过模板同步渲染实现页面秒出基本是标配,但由于移动端的网络和运行环境条件受到地域,设备的影响参差不齐,再加上手机相比于电脑的屏幕差距对页面的响应要求更高,为了减少页面大小以及配合离线技术和前后端分离,移动端H5页面静态资源分离和异步渲染逐渐成为默认的标准。
而为了满足在2G,3G的弱网条件页面可用性,提高响应速度,静态资源离线成为移动端体系下一个黑科技,并逐渐发展为各个App的通用能力,有了离线能力及时是在弱网或者无网的情况下,依然可以给到用户响应(无网下是一个打底页),离线能力在过去至少5年的时间一直是移动端H5技术体系下不可或缺的一环。
另一方面从12年all in 无线以来,移动端的环境也逐渐发生着巨大的变化,手机硬件的升级日新月异,配合着webview的优化,页面的渲染不断加速,4G的普及和5G的到来也让网络延迟降到个位数毫秒级,于此同时我们也在不断思考在未来网络和终端下前端页面的渲染应该是什么样的?后离线时代我们的备选方案是什么?
于是Serverless Faas和React/Rax SSR的结合实现页面的同步渲染成为我们的一个探索方向,Node Faas SSR同构实现一套代码两端运行解决复用的问题,Serverless的低运维,弹性伸缩解决SSR对机器Cpu消耗高的难题,所以今年9月开始我们在调研之后,选择对接淘系提供的比较成熟的营销域SSR方案在飞猪双十一会场落地实践。
整体链路耗时长
基于上图可以大致看出,目前飞猪营销搭建的页面真实的渲染并不是直接在接口数据返回后执行的,还需要拉取依赖的模块缓存,然后再进行页面的render,整个过程串行进行,耗时很长且不稳定。
从今年国庆会场的整体性能数据上也可以看出,首屏模块load耗时Android 500-600ms、iOS 400-500ms,首屏mtop耗时均值在 300ms左右,整体首屏时间 Android 2s左右、iOS 1.5s左右,所以用户进入页面到真的能够看到页面,基本需要在1s以上,这还是在端内有接口prefetch和模块缓存的数据,在端外体验问题更加严峻 而现在基于各类数据统计都告诉我们页面加载时间增加,会导致更多的用户流失。
页面 snapshot 的不足
为了减少页面loading过长的情况,营销页面做了首屏html的本地缓存,使得用户再次进入页面时减少等待,但缓存方案存在以下问题。
缓存的方案需要用户进入过一次页面,并成功缓存了html才有效,所以该方案无法解决用户第一次进入页面时等待时间较长的问题,且由于使用缓存,若用户清除缓存或是缓存存储失败,那仍然无法减少用户后续进入时的等待时长。 缓存只能存储上一次进入的数据信息,而目前飞猪营销页面,大量的投放数据都是基于个性化算法投放,缓存数据与真实数据的不一致会使页面重新渲染,尤其是当页面模块有明显变化时,重新渲染带来的页面闪烁感会更明显。
基于以上的问题,我们希望能够通过SSR将html片段生成放在服务端执行,以此优化渲染时间,主要分析如下:
客户端渲染快慢非常受容器影响,也就是我们常说的高端机快,低端机慢,但是当渲染放在了服务端上,这个容器的影响就可以规避。 从飞猪链路上可以看到,容器渲染前模块load的时间消耗,而这块耗时平均在300ms上下,端侧渲染,每一次http请求的建立都会有较长的时间损耗,而在服务端渲染,这段时间的消耗可以通过服务器来避免。 由于纯粹的SSR直出页面对定位和个性化场景的改造较大以及和离线,prefetch体系的不兼容,所以我们选择借助天马的异步ssr的方案,在现有的搭建链路上,增加ssr render的链路,在mtop数据中返回首屏的Html Str直接插入到页面中,这样在端内可以借助离线和prefetch加速页面秒出的过程。
MTOP:阿里内部移动端统一的API网关
优点:链路改造成本低,模块基本复用,可使用端上现有的离线包、prefetch等优化手段,页面url无变化,走mtop链路无需额外处理数据安全。 缺点:接口耗时增加,因为在mtop中返回html,数据量和耗时都会升高。
图片闪烁
沉浸式 titlebar 的支持
飞猪营销页面中,尤其是大促时的会场页面,会有大量的沉浸式titlebar使用场景,而前端titlebar的实现,是通过桥方法的调用,由容器告知原生titlebar以及状态栏的高度,而在ssr渲染时,服务器无法通过桥与容器进行交互拿到状态栏高度,这成为了ssr支持沉浸式的一个问题。
方案探索:
实际改造方案
在ssr层,我们根据容器、以及沉浸式参数匹配,在ssr层注入titlebar的占位dom,并给上一个默认高度。 在web层,ssr html注入完成后,获取真实的titlebar高度,dom操作原有的默认高度,将其变为真实的占位高度,使其和web时渲染的titlebar高度一致。
// 示例代码
// server render
function renderTitlebarP(type, stickyPaddingTop) {
return (
<Fragment>
{type === 1 ? <View
className="ssr-title-bar-main"
style={{
position: 'fixed',
top:0,
height: 0,
left: 0
}} /> : null}
<View className="title-bar ssr-title-bar" style={{
width: '750rpx',
height: stickyPaddingTop + 'rpx',// stickyPaddingTop 为不同状态titlebar时的默认高度
position: 'relative'
}} />
</Fragment>
)
}
// web render
if (如果有html返回) {
const pageContent = document.getElementsByClassName('mui-zebra-page-content')[0];
pageContent.innerHTML = html;
// ssr的html注入后页面后,获取真实的不同机型的titlebar(含状态栏)的高度stickyPaddingTop
// dom操作ssr的html
ssrTitlebar.style.height = `${stickyPaddingTop}px`;
}
// 拉取模块,并render web
fetchAndEvalJS(seedComboUris, { sequence }).then(() => {render(<PhoneApp />)})
titlebar 改造引发新的问题
页面闪烁 setAttitude的使用:改造方案中由于对dom操作中使用setAttitude进行了style的改变,但是由于rax组件在生成时会有一些属性的注入,使用setAttitude会造成style的结构变化,触发重选渲染,因此出现页面闪烁。 解决:改用 A.style.xxx 来最小力度改变想改的属性,避免重排。 页面模块重复 dom不一致:titlebar的判断中,使用了大量的端判断,而端判断是基于容器的ua,而在ssr层通过mtop获取的ua是不一样的,导致titlebar的判断上端判断不一致,因此返回的dom不一致,而这里需要提到rax的hydrate,他是通过比对dom顺序进行,导致如果渲染的两份dom顺序不一致,就会出现两份dom的重复情况。
// mtop ua
"MTOPSDK/1.9.3.48 (iOS;14.0;Apple;iPhone11,8)"
// window
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"
离线包
飞猪离线原理,统一渲染页html离线,ssr作为页面开关会,当命中飞猪离线时会以离线的统一渲染页的页面开关为准。 增加ssr上的白名单链接配置,确保只有指定的页面会真的进行ssr 链路的render。
无ssr | 有ssr |
---|---|
线上数据
根据线上页面的数据对比,我们发现开启了SSR的会场的可见时间都在1s以下,极大优于传统的首屏时间。
同一页面(wifi下)实验数据
从整体会场性能和单页面的对比可以看到,异步ssr的方案会带来mtop耗时的上升,因为html在接口中返回,增加了接口数据大小,但是由于直接返回了html,节省了原有的模块拉取再render页面结构的时长,所以整体的页面可见时间仍然较原来有了提升,基本都能达到1s内可见。
iphone8 | 安卓(p30 pro) | |||||||
---|---|---|---|---|---|---|---|---|
开启ssr | 未开启ssr | 开启ssr | 未开启ssr | |||||
首屏mtop耗时 | 首屏时间 | 首屏mtop耗时 | 首屏时间 | 首屏mtop耗时 | 首屏时间 | 首屏mtop耗时 | 首屏时间 | |
507 | 1123 | 268 | 1057 | 374 | 796 | 336 | 1277 | |
887 | 1463 | 569 | 1252 | 385 | 859 | 304 | 1115 | |
372 | 973 | 328 | 1196 | 402 | 1083 | 293 | 1256 | |
322 | 815 | 491 | 1195 | 387 | 826 | 298 | 1370 | |
1058 | 1557 | 398 | 1548 | 414 | 918 | 494 | 1560 | |
446 | 851 | 248 | 867 | 464 | 1063 | 248 | 1117 | |
355 | 837 | 279 | 1062 | 403 | 838 | 496 | 1418 | |
372 | 792 | 332 | 1344 | 447 | 1021 | 286 | 1163 | |
420 | 841 | 316 | 1024 | 380 | 863 | 297 | 1282 | |
399 | 1230 | 276 | 2531 | 372 | 844 | 333 | 1355 | |
均值 | 513.8 | 1048.2 | 350.5 | 1307.6 | 402.8 | 911.1 | 338.5 | 1291.3 |
同步 SSR 支持
在目前多端多适配前端技术体系下,客户端预渲染、Weex、Flutter 可能难以成为解决多端秒出的方案,通过拥抱原生html渲染技术配合Faas SSR面向未来网络条件布局,是前端页面性能往前再走一步的尝试突破,而这也是在Serverless概念热火朝天的当下云+端的最佳实践之一,从试点到通用再到普惠,刚刚走出第一步,前路漫漫仍需努力。
🔥第十五届 D2 前端技术论坛开放报名,速抢!
关注「Alibaba F2E」
把握阿里巴巴前端新动向