NO.1
背景
会场作为承载天猫、淘宝等系列大促的重要载体。面对亿万的消费者,会场的性能体验直接影响消费者的购物体验。
会场性能优化专项小组联合了客户端基础团队、容器团队、前端团队、数据分析团队、测试团队等多个团队,跨栈协同共同努力,在性能优化方向上也做了不少的优化工作。梳理了全链路性能埋点、定义新的性能口径(从用户点击到可视),使用了预渲染、数据预请求、资源加速下载、离线资源等优化手段,既能全链路的维度来度量,也能拆分到各个子阶段细粒度的数据。
这些性能优化手段,经过了618、双11等大促场景的实践检验。用户打开会场的整体平均耗时缩短了200ms~700ms左右,秒开率提升10%~14%。优化对中低端机绝对收益更高,已实现在低端机上实现秒开会场。在中低端机和高端机上优化前后的对比效果。
Android y67 优化前 Android y67 优化后
iPhone 7P 优化前 iPhone 7P 优化后
iPhone12 优化前 iPhone 12 优化后
本文将从客户端和前端两个方向分别展开讲述优化过程中实践与思考。
NO.2
变化与挑战
统计口径
往年会场性能指标通常是指前端部分的耗时。客户端容器和前端是分开统计各自只负责自己的部分,没有整体串联起来,无法真实反映用户的体感。新的变化是从用户的体感出发,全链路的视角来看体验。使用全新的可视时间的口径,即从用户点击到看到页面内容的展示。新挑战要将各部分数据的口径统一和信息整合。
性能目标
相信很多人对一秒法则有所了解,指的是在WIFI或4G的网络下,一秒内能够完成首个页面的渲染。对于会场业务来说,新的性能目标,希望用户在一秒钟能够展示会场的首屏内容,提升这部分用户的比例让更多的人能在一秒钟内打开会场。以往多关注的前端阶段性能,新挑战要包含客户端、WebView、前端页面渲染多个阶段,要在一秒内展现,挑战更大。
NO.3
解决方案与分析实践
针对上面提到的两个变化与挑战。首先我们需要梳理在新的统计口径下,用户点击到页面展示,经历了哪些阶段。然后分析各阶段的时间与消耗,分析其合理性、找出可行性的优化、以及预估的优化收益。
以双11的预售会场为例,我们拆分了用户进入会场路径的各个阶段。大致可分为以下四个过程。从用户点击开始,经过路由模块,客户端PHA容器, WebView, 会场框架,最后上屏到用户看见会场页面。如下图所示:
阶段拆分
各阶段拆分如下图所示,同时与用户实际的体感(导航动画、容器白屏、WebView白屏、页面加载等)做了对应关系。
从用户体感这条线看,容器白屏时间、WebView白屏时间、页面加载渲染时间。是提升用户体感的关键时间。从阶段拆分这条线看,需先了解4个阶段的职能以及内部具体的实现,寻找可行的优化手段,做优化的成本与收益的权衡。
客户端部分会涉及:路由模块、PHA容器和WebView;前端部分涉及:WebView和会场框架。
客户端部分
路由模块,核心作用是通过对跳转URL进行按规则拦截,并跳转分发到对应的模块,这部分本身时间消耗短,优化的收益不明显。
会场业务中使用的是PHA容器。PHA(全称Progressive Hybrid App),它是提升Hybrid体验的一种应用框架,能提升页面加载速度和交互体验的渐进式Web应用。使用PHA开发的应用本质上没有脱离前端开发和W3C标准,但依然拥有原生应用的特性和体验。
PHA容器介于客户端Native和前端之间,是两者连接的纽带。以渐进式 Hybrid App 的方式,借助端能力不断改善端上 Web 页面的性能与体验,无限接近 Native 体验为目标。PHA容器功能上支持了Tabbar、横滑、多页面类型、样式定制等功能,性能方面也提供了预渲染、数据预请求、离线资源、NSR等特性。
关于会场性能中的用户体感涉及的两部分
导航动画部分是系统的导航提供的能力,该部分耗时在客户端表现稳定,且跟硬件和系统有直接关系,通常在15~30ms左右时间。其优化空间有限,收益也不明显。
该阶段有容器的创建和页面配置这两部分。等这两部分都完之后才能进入Native容器的布局展示。
客户端ViewController或Activity的初始化和创建,此部分消耗由系统能力决定。
Web应用样式和页面的内容,是依赖JSON配置的下载和动态JS脚本的解析执行。由于会场业务的特殊性,除了JSON配置之外,还有一大部分动态JS的执行,内涵页面降级、页面展示、样式信息等信息。加速JSON配置的下载和提前解析执行,能减少或去除容器白屏时间。
WebView作为基础容器,提供了很多扩展功能,JSBridge、资源加载加速、同层渲染组件等。关于性能,WebView的多进程架构,WebView的创建耗时高,尤其在低端机问题更加明显。
上图中的WebView的白屏就是在页面加载过程中,实时创建WebView、loadURL、建立连接、页面所需的HTML/JS/CSS等资源。从数据上来看,这部分在整个链路中耗时占比较高,优化收益明显,是优化的重点部分。
前端部分
为了解决产品运营们随时调整页面结构、增删模块等灵活性的业务要求,同时在当前结构下能够利用客户端缓存做到资源离线最大化的技术诉求,目前的会场页面使用了多段式的终端渲染方案:先加载HTML和Entry JS,再发起数据请求,最后进行模块加载和渲染。
当前的终端渲染方案,虽然是在结构上的串行的加载渲染策略,但在运行时,首先我们将资源加载等部分利用客户端提供的ZCache、PHA资源缓存等能力实现了资源的离线推送和在线缓存,这大幅缩减了HTML、模块JS的资源加载耗时。
同时,当前的渲染方案也在运行时实现了部分并行,来优化整体耗时。例如,在当前的终端渲染方案中,我们结合了客户端的Data Prefetch预加载能力,将数据接口的请求前置到客户端跳转时,即用户从手淘首页点击进入会场时,就会同时触发客户端的运行代码、将下一跳页面的数据请求提前发送,前端只需要发送正常的请求,就可以获取到已经由客户端提前取回的数据。相比由前端代码执行的时机,节省了容器初始化和HTML的加载执行耗时,收益可观。
但有了以上这些,在性能的最优解上,还是不够。结合线上的大盘数据,可以分析出当前阶段仍然存在的问题有:
主会场作为大促业务的锋线,承载了流量分发、个性化推荐等作用,在这些业务逻辑背后所对应的繁杂的服务端接口,使得整个首屏接口的整体RT不容乐观。尤其是在页面的HTML文档、EntryJS等核心资源缓存、实现毫秒级返回后,数据接口的预加载提前量明显变少,如何解决用户的“白屏等待”,是主会场必须要解决的一个问题。
上面简单介绍过当前渲染方案的前端代码的执行时序,其中会场页面所包含的模块是未知的,必须要等待页面接口整体返回后才能开始加载。但前面的数据等待耗时能否先利用起来、是否可以有其他的优化手段来实现模块的提前加载,也是一个优化方向。
主会场模块因为交互复杂、逻辑复杂等因素,4-5个首屏模块的资源总体积达到了300kb以上(gzip后)。如此大量的模块JS资源,在中低端的设备上的执行和渲染耗时都比较长,拖慢了整体耗时。在业务优先的前提下,暂时没有办法来通过简化代码、删减动效等方式来大规模降低首屏的渲染耗时。那么,这一渲染过程是否可以借用客户端的能力,不依赖前端时序而提前完成,这看起来也是一个可以尝试的解决方案。
NO.4
客户端的核心手段
Web应用对比Native存在一些差异,在于WebView容器的创建、资源加载速度、渲染速度等方面存在差距。这部分也是我们的突破口。再结合以上的各阶段分析,PHA容器和WebView需要做是
对应的具体方案如下
离屏提前将WebView和前端资源加载等提前执行,去除WebView的白屏等待时间。用户交互时使用快照数据渲染上屏可见,再做数据刷新。
提前并发请求数据,减少动态数据请求的等待时间
优化后的阶段拆分和用户体感变化,增加了预渲染阶段。当用户从点击,经过系统导航动画,直接解析提前下载的配置,解析执行,命中了预渲染WebView的缓存规则,直接上屏显示。从用户点击到可视时间大大缩短了。
WebView预渲染
预渲染是在今年双11会场中使用的技术方案,也是核心的抓手。将原有WebView阶段的WebView创建和资源加载等耗时部分提前。在某个合适的时机,去创建WebView,执行“渲染”,并将渲染结果缓存与内存之中。以备需要时使用。
该方案需要解决几个关键关节问题
WebView的预渲染是要消耗网络内存资源。因此尽量避免在任务密集的时刻去调度,很可能会阻碍正常的任务执行,会适得其反。由基础架构团队提供调度系统负责预渲染的触发执行。有多种可选的机制,如空闲状态、用户操作等多种触发时机。结合会场的业务形态,选择在进入会场之前,空闲时间执行调度任务。
这里的渲染与正常执行页面渲染有差别。它是离屏状态下的操作行为,提前创建WebView以及页面依赖的JS的下载与执行,并会使用打底的数据做渲染。然后将已经执行预渲染WebView,按照一定的规则建立缓存。更重要的一点,还需要针对数据更新请求、面渲染内容、数据埋点等真实用户行为做延迟处理。更详细的内容在前端部分有展开说明。
当用户真正点击进入会场,并且访问的内容规则匹配命中了缓存中预渲染的WebView,那么将消费这个WebView,直接上屏,达到页面快速展示的效果。
具体的流程如下图所示
数据预请求
由于会场的业务复杂度高,服务端的数据计算的规则复杂,数据请求接口的耗时高,原有流程中的串行数据请求,严重影响了页面内容展示。数据预请求能力,将数据预请求的时机由业务发起请求的时机,提前到用户点击时,并行发送数据请求,缩短数据等待时间。
资源加载提速
NO.5
前端的核心手段
在客户端容器的帮助下,主会场的H5页面可以提前在客户端首页通过离屏的WebView进行加载和渲染、并在用户实际访问时“即开即用”。除了上面介绍的客户端的部分策略,前端也利用了端侧能力在资源提前加载、页面提前渲染上实现了一定的优化。
预渲染适配
在提前创建的离屏WebView中,为了做到真正的秒开,会场页面可以提前进行渲染。但相比原有的访问过程,有一些关键节点必须要提前考虑:
因为手淘首页的qps远远超出主会场接口的qps阈值,所以在首页预创建的WebView时,会场页面在用户访问前,不能发起任何的实际数据请求,否则很可能会压垮服务端接口。
主会场页面虽然已经预创建,但在用户真实访问之前,是不能够将预创建页面的UV、PV、数据曝光等埋点等发送出去,否则会干扰正常的数据统计。
与数据请求的问题类似,会场内部一些独立发送请求获取数据的模块(例如,红包余额、权益领券等),必须在预渲染的过程中进行适配,例如提供静态占位、不发送异步请求等等。
在最终的预渲染方案中,前端通过使用快照方案,在不发起接口请求的前提下,基于快照数据完成了预先的节点渲染。在页面埋点和模块渲染的策略上,前端提供了全局的props.isPrender等透传属性,实现了页面埋点延迟发送,同时支持动态配置占位元素、实现了新模块的自动适配。
数据快照
为了能够做到真正的“秒开”,让用户不再有白屏的等待体感,本次的主会场仿照手淘客户端首页的渲染策略,将用户的上次访问数据进行了本地缓存,在预创建的WebView内渲染时,优先使用上次的数据作为打底数据进行占位渲染。在用户真正进入会场后,直接通过hydrate的方式局部刷新节点,实现“无感”的软刷新。
这套快照方案的核心节点为:
前端主动将用户上次的访问数据通过客户端JSBridge、localStorage等缓存接口存储在设备本地,并会在每次用户访问会场时、主动刷新缓存。同时为了保证读写稳定性,仅将首屏相关的数据、模块等写入缓存。
快照数据依赖用户访问,初次访问的用户本地一定是没有快照的。为了优化初次访问的体验,会场前端将主会场的模块列表、资源版本等静态信息,直接托管到CDN。在没有快照时,优先拉取这份静态数据来提前加载模块,减少模块加载耗时。
结合实时性、个性化等业务特点,会场侧设计了一套多级的缓存失效策略。快照缓存会拆分为两部分:模块数据 和 模块资源,并支持动态的失效时间配置。
模块数据(商品列表、版头图片等)默认当天当前一小时有效,支持动态配置失效时间,例如,3小时、6小时。限时秒杀、倒计时等时效性很强的业务玩法,可配置主动失效。
模块资源,默认不失效。即当页面数据失效时,前端依然会获取上一次的模块资源列表,将页面所需要的JS模块资源等提前加载。
节点更新
在预创建的WebView渲染中,前端使用了快照数据将节点提前渲染出来,并在真实访问时二次刷新。二次刷新的体验尤为重要,需要尽量少的避免抖动、闪烁等,否则方案可能会适得其反、给用户造成干扰。这里,前端侧直接采用了与SSR相同的hydrate方案。
在预创建的WebView中,类似于SSR的服务端渲染过程,前端先通过一个影子节点将模块内容渲染出来,获取到对应的首屏内容的html,将这部分html提前塞到根容器节点内。
而在用户点击进入后,会结合真实数据在根容器节点上进行hydrate渲染。相比于直接的两次渲染过程,这种hydrate方式可以做到局部刷新(例如,商品模块仅更新了图片、标题等元素),整体体验较好。
NO.6
全链路性能数据
要将性能指标能真实的反应优化的效果,将原来的分散的性能数据做收敛和串联。打通性能数据基础能力,并将各个子阶段和子任务的影响应能指标的状态和关键的性能节点都能串联起来,性能埋点实现统一上报。
NO.7
总结和思考
主会场性能优化达到最优效果有多个影响因素,对性能大盘有改进。主会场是切入点作为,整个淘系电商Web应用众多,性能体验是一个需要持续关注和长线投入去做的事情,那么如何提升淘系业务的大盘整体性能是一场艰巨的持久战。
PHA当前的WebView预渲染方案,有效果有收益,但资源消耗也不小,另外预渲染的整体运作实现机制以及配置有部分实现针对会场的定制化的实现。后续会探索更轻量级的、方便易用的、能服务更多常态化业务的方案。
与BI团队一起梳理性能数据的各阶段与细分的性能埋点,其实很多阶段和数据是具有通用性的。对这部分性能指标做进一步的抽象去服务更多的业务场景,作为判别业务性能的一项重要指标,也能做出更完整的性能大盘。
目前对于性能数据整体,但是如何能够快速方便的获取当前性能数据,以便快速定位问题。需要有辅助工具来支持,PHA容器作为客户端和前端的中间纽带,规划辅助开发工具。
喜欢就点这里