一、前言
一直以來,體驗都是得物技術部的關鍵詞之一,對於前端開發而言,提高用户體驗更是一項至關重要的工作。本文從本次交易後台性能優化實踐出發,同時介紹應用整體架構和設計,希望可以給參與網站性能建設的同學提供一定的學習和參考價值。
二、系統簡介
交易後台是現有交易流程主要系統,包含商品、出價、商家、訂單等二級業務域,其承載了交易的核心任務,在交易平台的整體架構中佔據着非常重要的地位。業務背景:整體日均 PV 數達到 10w+,其中 SPU 場景佔比超過一半,由於頁面功能結構複雜的原因,運營的使用體驗和交易效率有一定程度上的影響。技術背景:應用基於 qiankun 微前端架構,首屏加載資源過多,包括主子應用 JS 和樣式文件,圖片和其他靜態資源,數十個子應用路由信息和數百個菜單項,以及接入的一些三方 SDK 等。
三、性能現狀
先看優化前的首屏效果(沒有開慢倍速):
可以從動圖看出頁面加載速度、白屏時間都不太理想,顯然和我們的目標首屏秒開還有一段距離,為了實現首屏秒開的目標,可以先從以下幾個方面進行分析。
性能看板分析
Chrome Performance 火焰圖在頁面性能問題分析中是一項非常重要的工具。
相關閲讀:https://juejin.cn/post/6844904074551230471
在打開看板後,先關注下這幾個紅色區域長任務,我們知道主線程一次只能處理一個任務,所以這會導致瀏覽器在主線程上阻塞一段時間,也是造成頁面卡頓的主要原因。看下底部調用樹的情況:Task1: 發生在 DCL 之前,因為瀏覽器解析 HTML 文件時,會在遇到<script>標籤時立即執行腳本文件,所以 webpack_require 和 Compile Code 很可能是因為入口文件過大導致。
Task2: 發生在 FCP 之後,主要是源碼中組件渲染相關的核心函數,componentUpdateFn 用於比較新舊 VNode 的差異,並調用 patch 函數對真實 DOM 進行更新,所以初步判斷是渲染複雜組件導致。
另外可以觀察以下關鍵性能指標,瞭解一下頁面加載的各個階段的耗時情況
- First Contentful Paint(FCP):首次內容繪製時間,此時頁面僅展示出一些水印組件,和白屏幾乎沒區別,一般應該在頁面加載後的 1-2s 內完成,此時用户可以感受到頁面正在加載。(當前耗時2s+)
- Largest Contentful Paint(LCP):最大內容繪製時間,此時主應用框架渲染基本完成,子應用還未開始加載。通常應該在頁面加載後的 2.5s 內完成,否則用户可能會感到頁面加載緩慢。(當前耗時4s+)
根據這些具體的性能指標,之後才能更好的度量優化效果。
渲染流程分析
為了快速定位問題,我們按步驟拆分一下頁面整體加載的渲染流程,這有助於我們做出針對性的優化。
我們重點關注幾個標記的部分,接下來就可以根據實際情況逐一擊破了。
四、方案拆解
根據對應用渲染流程的分析,我們可以將整體優化方案作進一步的拆解,並設置對應動作的開發優先級。
並通過二八法則,我們將主要精力重點投入到高優方案中,確保整體性能的提升。
五、具體動作
首屏資源優化
首屏資源加載會直接影響頁面的渲染速度和體驗,我們先查看下首屏資源的加載情況,由於各類資源請求過多,這裏僅過濾來源於主應用的網絡請求。可以直接從中發現幾個可優化的點:
-
入口 JS 資源文件 Size 優化
- 嚴重影響了頁面整體加載性能,可以通過構建體積優化,主要思路就是代碼分割拆包,靜態資源上傳 CDN,腳本代碼壓縮等方式處理
-
接口緩存、調用時序治理
- 針對用户信息、菜單信息等不常變動的接口可以採用相關緩存策略,減少首屏等待的時間
- 部分優先級不高的接口可以延遲調用,異步加載
// 我們採用 Cache aside 旁路緩存策略 async function cacheRequest(params: any) { const startTime = Date.now() const key = params.key const cache: any = JSON.parse(localStorage.getItem(key) || '{}') // 當天有緩存 const canCache = cache.cacheTime&&moment(cache.cacheTime).isSame(moment(startTime), 'day') window.sendTrack?.({ event: 'main_request_cache', tags: { eventTitle: '主應用數據緩存', eventType: canCache ? 1 : 0, }, }) const requestCache = async () => { const data = await request(params) localStorage.setItem(key, JSON.stringify({ data, cacheTime: Date.now() })) return data } // 優先從緩存中讀取數據 後續再更新緩存 保證數據的一致性 if (canCache) { setTimeout(requestCache, DELAY_CACHE_TIME * 1000) return cache.data } else { const data = await requestCache() return data } } -
三方 SDK 集中治理
- 儘量放在 script 標籤中異步加載腳本,可以加快解析時間且保證主入口文件的加載不會被阻塞
- 另外注意防止子應用重複加載,還有部分 SDK 需要及時下線
其實原則只有一個:減少首屏需要加載的資源數量和大小,並儘快加載必要的資源。
渲染優先級優化
最後為了減少 LCP 的等待時間,我們將頁面左側菜單欄做一個懶加載處理,初始化僅展示一級目錄菜單,既考慮了首屏視覺效果,也間接將子應用加載的時間點提前。
相關閲讀:當不希望因為不重要的任務導致用户感覺到卡頓的話,就應該考慮使用requestIdleCallback:https://juejin.cn/post/6844903592831238157
// 採用 requestIdleCallback 方式利用 CPU 空閒時間漸進地加載內容
try {
requestIdleCallback((deadline) => {
// 當前幀有剩餘時間 可以執行任務
while (deadline.timeRemaining() > 0 && || deadline.didTimeout) {
// 加載剩餘部分菜單項
}
})
} catch (e) {
// 低版本瀏覽器兜底處理
setTimeout(() => {
// 加載剩餘部分菜單項
}, DELAY_LOAD_TIME)
}
}
最後再看下優化後的首屏效果(沒有開快倍速):
六、效果回收
主要性能指標
優化後各項指標數據均有一定提升,其中
秒開率(首次打開頁面<1秒佔比,以 FMP 為準):🔝15%+
頁面首次進入FMP90分位均值(秒):🔝35%+
頁面TTI%(可交互時長<1.5s佔比) :🔝30%+
TTI 90分位均值(秒):🔝50%+
上述數據均來源於效率工程應用系統前端大盤看板。
優化前-7月
優化後-9月
其他相關數據統計(本機測試):頁面傳輸資源大小減少約 1M、頁面資源加載時間下降超過 0.5s、FCP、LCP 也分別優化到 1s、2s 以內 ,整體結果超出預期。
七、總結展望
隨着業務複雜度提升和用户規模的增加,系統的性能和效率必然會面臨更多的挑戰,本輪性能優化工作基本告一段落,雖然取得了一些成績,但還有很多事項需要後續不斷的完善,比如:
- 框架層面優化。深入探究微前端框架層的性能瓶頸,降低應用間消息傳遞通信和模塊重複資源加載損耗
- 使用 Service Worker。執行一些計算密集型或網絡請求相關的任務,實現靜態資源緩存優化
- 資源加載優化。利用發佈平台事件同步能力實現應用配置文件按需加載,避免網絡帶寬造成過大的壓力
希望能通過各項措施逐步形成後台應用性能優化最佳實踐,從而避免各種性能卡點問題,進一步保證運營體驗。
*文/ Johnny
本文屬得物技術原創,更多精彩文章請看:得物技術官網
未經得物技術許可嚴禁轉載,否則依法追究法律責任!