淘寶承接頁(yè)是如何實(shí)現(xiàn)秒開(kāi)的
前言
用戶承接頁(yè),是承載上游的落地頁(yè),其核心職能是承接流量、轉(zhuǎn)化用戶。對(duì)用戶增長(zhǎng)業(yè)務(wù)來(lái)說(shuō),如何讓用戶更快看到頁(yè)面,是影響用戶決策、決定拉新成功的關(guān)鍵。用增承接頁(yè)的目標(biāo)用戶是手淘低活用戶,這部分人的手機(jī)設(shè)備中低端占比90%以上,網(wǎng)絡(luò)條件也不穩(wěn)定,這對(duì)于我們承接頁(yè)的性能、體驗(yàn)提出了更高的要求。
業(yè)務(wù)背景
我們業(yè)務(wù)目標(biāo)用戶是淘寶潛客人群,業(yè)務(wù)上以定向權(quán)益配合特定的貨品池,打造低價(jià)心智來(lái)吸引用戶。一般的承接頁(yè)形式如下:

承接頁(yè)一般都會(huì)紅包搭配貨品,這里有2個(gè)比較重要的邏輯:紅包直塞、補(bǔ)貼價(jià)計(jì)算。
紅包直塞:用戶訪問(wèn)頁(yè)面的時(shí)候,就判斷是否是目標(biāo)人群,如果是目標(biāo)人群,直接發(fā)放紅包權(quán)益。
補(bǔ)貼價(jià)計(jì)算:貨品模塊根據(jù)紅包發(fā)放狀態(tài)展示抵扣后的價(jià)格,讓用戶在前臺(tái)看到最低的價(jià)格。
由于承接頁(yè)每個(gè)模塊都要處理定向的權(quán)益、貨品,導(dǎo)致模塊請(qǐng)求都是定制化的,而且為了得到最精確的補(bǔ)貼價(jià),模塊請(qǐng)求之間還會(huì)有串行邏輯,貨品模塊需要等待紅包模塊請(qǐng)求成功后發(fā)起。
如何滿足業(yè)務(wù)的定制需求,讓用戶擁有更好的體驗(yàn),幫助業(yè)務(wù)成長(zhǎng),我們針對(duì)如何提升承接頁(yè)的性能和體驗(yàn),開(kāi)啟了專項(xiàng)優(yōu)化之路。
承接頁(yè)的秒開(kāi)優(yōu)化
首先來(lái)看未優(yōu)化前的承接頁(yè),肉眼可見(jiàn)的“慢”,原始頁(yè)面性能數(shù)據(jù)如下:頁(yè)面首屏可視時(shí)間:低端機(jī)6.6s、中端機(jī)4.2s、高端機(jī)2.8s,平均首屏可視時(shí)間4.9s。
分析performance,導(dǎo)致頁(yè)面變慢的原因主要是:
總體js過(guò)大,業(yè)務(wù)js 200k,加上基礎(chǔ)js,總大小約有400k 接口串行,先請(qǐng)求紅包后請(qǐng)求貨品,導(dǎo)致請(qǐng)求時(shí)間變長(zhǎng) 外部條件惡劣,用戶機(jī)型差、網(wǎng)絡(luò)差的情況下,請(qǐng)求時(shí)間不可控
H5頁(yè)面從加載到首屏可視,主要經(jīng)歷了webview初始化 - 主文檔加載 - 資源加載 - 數(shù)據(jù)請(qǐng)求 - 業(yè)務(wù)內(nèi)容渲染幾個(gè)部分,我們針對(duì)每個(gè)步驟影響對(duì)首屏可視時(shí)間的影響,進(jìn)行了:中心化接口改造、數(shù)據(jù)預(yù)加載、靜態(tài)化SSR的優(yōu)化,最終實(shí)現(xiàn)了承接頁(yè)的秒開(kāi),低端機(jī)首屏可視時(shí)間0.9s,高端機(jī)0.8s。
| 機(jī)型 | 方案 | 頁(yè)面可視時(shí)間 |
|---|---|---|
| 低端機(jī) | CSR(未優(yōu)化) | 6.6s |
| 靜態(tài)化SSR | 0.9s(優(yōu)化5.7s) | |
| 高端機(jī) | CSR(未優(yōu)化) | 2.8s |
| 靜態(tài)化SSR | 0.8s(優(yōu)化2s) |
(低端機(jī) - CSR vs SSR)
承接頁(yè)優(yōu)化過(guò)程
中心化接口改造
最初的承接頁(yè),每個(gè)模塊單獨(dú)定制發(fā)請(qǐng)求,請(qǐng)求串行,頁(yè)面渲染鏈路如下:

為了對(duì)用戶做定向權(quán)益和貨品,承接頁(yè)會(huì)進(jìn)行紅包直塞、補(bǔ)貼價(jià)計(jì)算邏輯,原本的執(zhí)行邏輯交給前端來(lái)控制,通過(guò)紅包模塊請(qǐng)求完畢后發(fā)送事件告訴其他模塊發(fā)起請(qǐng)求,在用戶網(wǎng)絡(luò)條件不穩(wěn)定的情況下,首屏可見(jiàn)時(shí)間不可控。
所以我們進(jìn)行了中心化接口的改造,將模塊中定制的請(qǐng)求邏輯抽離,將數(shù)據(jù)請(qǐng)求合并成一個(gè)。同時(shí)服務(wù)端改造,紅包直塞和補(bǔ)貼計(jì)算的串行邏輯在服務(wù)端處理,前端模塊通過(guò)一個(gè)動(dòng)態(tài)加載器模塊請(qǐng)求頁(yè)面數(shù)據(jù)并分發(fā)給各個(gè)模塊。改造后,模塊只需要根據(jù)拿到的數(shù)據(jù)進(jìn)行處理,讓模塊開(kāi)發(fā)變成:UI+數(shù)據(jù)的簡(jiǎn)單模式,每個(gè)模塊的平均開(kāi)始時(shí)間,也從2人日減少到0.5人日。
中心化接口后的頁(yè)面渲染鏈路如下:

數(shù)據(jù)預(yù)加載
數(shù)據(jù)預(yù)加載,也叫prefetch,是淘寶這邊結(jié)合客戶端的優(yōu)化手段。中心化接口將首屏接口請(qǐng)求減少到1個(gè),為開(kāi)啟數(shù)據(jù)預(yù)加載做好了準(zhǔn)備。
簡(jiǎn)單來(lái)說(shuō),數(shù)據(jù)預(yù)加載就是淘寶客戶端,根據(jù)下發(fā)的配置文件,來(lái)判斷當(dāng)前頁(yè)面需不需要提前發(fā)頁(yè)面請(qǐng)求。如果命中配置文件的相關(guān)配置,在用戶點(diǎn)擊進(jìn)入目標(biāo)頁(yè)面時(shí),webview初始化階段就發(fā)起頁(yè)面請(qǐng)求,當(dāng)頁(yè)面接口請(qǐng)求真實(shí)發(fā)起時(shí),可以直接使用提前請(qǐng)求的結(jié)果,從而減少接口請(qǐng)求占用的時(shí)間,頁(yè)面渲染過(guò)程如下:

一般來(lái)說(shuō)提前首屏的接口請(qǐng)求,大約可以節(jié)省300~400ms的時(shí)間,如果是低端機(jī),收益會(huì)更高。這是開(kāi)啟了數(shù)據(jù)預(yù)加載后的對(duì)比視頻:
(低端機(jī)y67 - CSR vs prefetch)
數(shù)據(jù)預(yù)加載雖然可以提前發(fā)出請(qǐng)求,但在傳統(tǒng)的CSR鏈路中,首屏?xí)r間還是比較長(zhǎng),主要是因?yàn)榛綣S+模塊JS這部分資源加載還是很耗時(shí),對(duì)于低端機(jī)更是明顯。另外,該能力依賴客戶端提供能力,如果是用戶喚端入淘的場(chǎng)景,數(shù)據(jù)預(yù)加載的命中率很低,能提供的幫助也比較有限。
靜態(tài)化SSR
傳統(tǒng)的CSR頁(yè)面,如果需要優(yōu)化,資源的緩存和提前請(qǐng)求基本都需要依賴客戶端配合,而用戶增長(zhǎng)業(yè)務(wù)的場(chǎng)景包含端外、端內(nèi)兩部分,這使得很多客戶端的優(yōu)化手段用不上,這也是我們承接的一個(gè)難點(diǎn)。
如何在資源加載和請(qǐng)求發(fā)出前,就讓用戶看到首屏呢?我們想到了利用SSR(服務(wù)端渲染)。常規(guī)的SSR方案,是將頁(yè)面的渲染工作放到了Server端,在文檔請(qǐng)求中返回渲染好的HTML,但這個(gè)方案成本很高:
改造成本高,承接頁(yè)使用的是多是搭建鏈路,改成服務(wù)端渲染需要修改原本的模塊機(jī)制,導(dǎo)致頁(yè)面渲染架構(gòu)需要修改 服務(wù)器成本高,由于用戶請(qǐng)求url的時(shí)候,就會(huì)發(fā)起對(duì)服務(wù)端的渲染請(qǐng)求,針對(duì)大流量來(lái)說(shuō),服務(wù)器成本不得不考慮;另外服務(wù)端渲染失敗的情況,會(huì)導(dǎo)致直接出現(xiàn)白屏,缺少兜底能力 無(wú)法結(jié)合客戶端優(yōu)化,渲染過(guò)程放在了服務(wù)端,導(dǎo)致無(wú)法結(jié)合客戶端做優(yōu)化
經(jīng)過(guò)我們的技術(shù)調(diào)研,最終在淘寶承接頁(yè)落地了「靜態(tài)化SSR」方案。利用CDN緩存做靜態(tài)化,當(dāng)命中緩存直接返回SSR HTML,如不命中則通過(guò)SSR FaaS服務(wù),重新渲染最新的SSR HTML,并寫(xiě)入CDN緩存。大致流程如下:

一方面靜態(tài)化SSR利用了CDN緩存,就近原則,可以讓它獲得比常規(guī)SSR更好的性能;另一方面,大部分人命中CDN的情況下,對(duì)服務(wù)端(SSR FaaS)的壓力相對(duì)較小。當(dāng)然它也有缺點(diǎn),因?yàn)榫彺娴脑?,?dǎo)致它基本支持不了個(gè)性化。
不巧的是,用增承接頁(yè)主要是定向權(quán)益、貨品等個(gè)性化內(nèi)容,所以我們?cè)贔aaS服務(wù)上做了匿名緩存,也就是只緩存無(wú)個(gè)性化內(nèi)容,保證SSR鏈路的CDN緩存是通用數(shù)據(jù)。當(dāng)用戶的頁(yè)面JS加載后,我們發(fā)出真實(shí)請(qǐng)求,替換頁(yè)面緩存的內(nèi)容。
(低端機(jī)y67 - CSR vs prefetch vs SSR)
靜態(tài)化SSR動(dòng)畫(huà)數(shù)據(jù)
靜態(tài)化SSR方案,當(dāng)用戶緩存內(nèi)容和真實(shí)內(nèi)容有區(qū)別的時(shí)候,會(huì)有比較明顯的數(shù)據(jù)刷新的過(guò)程,這對(duì)于用戶體驗(yàn)來(lái)說(shuō),“不是不能用,但是不夠好”。有沒(méi)有辦法來(lái)優(yōu)化從匿名緩存到個(gè)性化數(shù)據(jù)的過(guò)程呢?我們提出了靜態(tài)化SSR動(dòng)畫(huà)數(shù)據(jù)。
所謂靜態(tài)化SSR動(dòng)畫(huà)數(shù)據(jù),是指將用戶的數(shù)據(jù)切換過(guò)程中的直接刷新DOM的過(guò)程,改成設(shè)計(jì)感的過(guò)渡動(dòng)畫(huà)。這個(gè)過(guò)程,可以由前端和設(shè)計(jì)師參數(shù),將“bug”變成“feature”。
天降紅包動(dòng)畫(huà):數(shù)據(jù)變化出現(xiàn)突然抖動(dòng)出現(xiàn)紅包模塊,給人突兀的感覺(jué),可以設(shè)計(jì)成紅包模塊出現(xiàn)的時(shí)候是一個(gè)從上而下彈出,并最終收攏到紅包模塊位置的前端動(dòng)畫(huà),給人一種“天上掉紅包”的感覺(jué) 商品換一換動(dòng)畫(huà):原本的數(shù)據(jù)變化會(huì)把緩存的商品替換掉,讓人有bug的感覺(jué),可以設(shè)計(jì)成真實(shí)請(qǐng)求商品數(shù)據(jù)返回時(shí),通過(guò)漸入漸出、上下滑動(dòng)的動(dòng)畫(huà)形式,給出“換一換商品”的氛圍感,如果更精細(xì)點(diǎn),同個(gè)商品因?yàn)橛袡?quán)益添加后價(jià)格降低,可以做成價(jià)格滾動(dòng)動(dòng)畫(huà),突然“降價(jià)”的感覺(jué)
我們?cè)谂c業(yè)務(wù)溝通后,現(xiàn)階段暫時(shí)采用了比較簡(jiǎn)單的過(guò)渡動(dòng)畫(huà),效果如下,播放速度做了0.5倍處理:
結(jié)尾
前端性能優(yōu)化是一個(gè)老生長(zhǎng)談的問(wèn)題,也是一場(chǎng)持久戰(zhàn)。在這個(gè)過(guò)程中,我們需要深入理解頁(yè)面架構(gòu)和業(yè)務(wù)邏輯,否則很難找出設(shè)計(jì)不足的地方,與此同時(shí),我們也要遵循以下幾個(gè)原則。
1、依據(jù)數(shù)據(jù)而不是憑空猜測(cè):頁(yè)面有性能問(wèn)題,我們可以通過(guò)performance、監(jiān)控、日志等手段,找出具體導(dǎo)致頁(yè)面很慢的原因,從而對(duì)癥下藥。
2、忌過(guò)度優(yōu)化,綜合考慮:性能優(yōu)化方案需要綜合考慮開(kāi)發(fā)成本、服務(wù)器成本,需要做衡量,不要過(guò)度優(yōu)化。
3、性能優(yōu)化要與業(yè)務(wù)結(jié)合:我們技術(shù)優(yōu)化的目標(biāo)是為了讓業(yè)務(wù)更好,選擇適合業(yè)務(wù)、提升業(yè)務(wù)的方案才是一個(gè)好方案。
