螞蟻金服如何把前端性能監控做到極致?

本文來自螞蟻金服前端技術專家楊森在ArchSummit北京2018的分享,他將分享如何通過 Performance 相關的 API 準確的採集用戶性能數據,並如何通過大數據計算加工最終產出用戶性能分析產品,以及如何通過性能數據縱向衡量產品性能、發現性能瓶頸。

本文的主要從以下四個方面闡述:

  • 前端性能監控的兩種主要技術方案;

  • 怎麼樣去做一個真實用戶性能數據的採集;

  • 怎麼去分析這些數據;

  • 這些性能數據在螞蟻金服怎麼應用。

##前端性能監控的兩架馬車

從技術方面來講,前端性能監控主要分爲兩種方式,一種叫做合成監控(Synthetic Monitoring,SYN),另一種是真實用戶監控(Real User Monitoring,RUM)。

合成監控

什麼叫合成監控?就是在一個模擬場景裏,去提交一個需要做性能審計的頁面,通過一系列的工具、規則去運行你的頁面,提取一些性能指標,得出一個審計報告。

合成監控中最近比較流行的是Google的Lighthouse,下面我們就以Lighthouse爲例。

Google Lighthouse 系統架構圖

Lighthouse提供了很多種方案,但是我們這裏演示的是一種以命令行工具的形式去對一個淘寶首頁做性能審計。我們可以輸出目標頁面,它就會在我們這個模擬環境裏面打開淘寶,然後去做一些性能數據的提取。

我們會看到一個生成的性能報告,從這個性能報告裏我們可以看到,Lighthouse生成的不僅僅是一些性能相關的數據,甚至包括PWA,然後一些SEO,甚至一些前端工程化的最佳實踐等等。

當然其實業界對於Lighthouse也是評價有褒有貶,因爲Google藉助這個看似中立的性能評審工具也是在推行它的一些技術的方案。

合成監控的優缺點

現在,我們通過下表,來看看合成監控的優缺點:

真實⽤用戶監控(RUM)

所謂真實用戶監控,就是用戶在我們的頁面上訪問,訪問之後就會產生各種各樣的性能指標,我們在用戶訪問結束的時候,把這些性能指標上傳到我們的日誌服務器上,進行數據的提取清洗加工,最後在我們的監控平臺上進行展示的一個過程。

真實用戶監控(RUM)的優缺點

因爲真實用戶監控也是在運行時執行,所以這種真實用戶監控比較難採集到一些硬件相關的指標,包括也很難去採集這個頁面執行的幻燈片(即逐幀截圖)。當然技術上,你可以用JS把當前頁面保存成一個Canvas,做一些逐幀對比,甚至把這些數據回傳回去。但是在實踐過程中,我們肯定不會這樣做,因爲這對用戶的流量是極大的浪費。介紹完這兩種監控方案我們來看一下他們兩種方案的對比。

下文中,我們會着重介紹真實用戶性能數據採集的方案。

真實用戶性能數據採集方案

我認爲,在真實用戶性能數據採集時,要關注四個方面的東西:

  • 使用標準的API;
  • 定義合適的指標;
  • 採集正確的數據;
  • 上報關聯的維度。

下面我們來逐個解析一下。

使用標準的API

如果大家有研究過前端性能監控,可能會知道瀏覽器會提供這麼一個API叫 performance.timing,它會提供一個頁面,從開始加載一直到加載完畢,中間各個階段的一個模型,但這個 API 已經“廢棄”了。爲什麼會被廢棄?因爲 W3C 給我們提供了更全面、更強大的一個性能分析矩陣,比單一的 performance.timing 更加強大,能幫助我們從各個方面分析前端頁面性能。

還有 High Resolution Time 這個基礎的 API,可以爲我們提供更精準的 timestamp。

爲什麼所有的這些規範都是基於這個高精度時間的規範呢。大家寫過JS都知道,可以用Data.API去提取當前的時間軸,單位是毫秒,這是我們新的高精度時間的定義,它可以通過 performance.now 來獲取,它的單位也是毫秒。但是同樣是毫秒, performance.now 返回的變量小數點後面 10 位以上是有的,這裏面我列了一個簡單的轉換式,大家可以換算一下,是可以精確調納秒級別的。這就是高精度時間的第一個特性。

爲什麼叫高精度時間,就是精度非常之高,也是爲了適應現在前端一些複雜的一些性能衡量的場景,包括一些複雜的動畫場景,需要的一些高精度的定義。

那麼 High Resolution Time 的另外一個特性就是,當我們在用戶界面獲取當前時間,然後修改一下系統時間,再次調用同一個方法就可以獲取當前時間。

爲了方便大家去對比,我列了一個輔助線,可以看到,同樣是調用 Data.now 這個 API 獲取系統時間,如果我們修改了系統時間,在獲取時間的時候,我們拿到的是,就是當前對應的系統時間。比如說我們先獲取一次,修改系統時間再獲取一次,Data.now就變成了昨天的時間,但是與此同時,HR.time 其實是不會受這個系統時間變更的影響的,它依然會單調的遞增,這個是HRtime的第二個特性,它是一個單調遞增的。

所以綜合來說,HRtime有兩個特性,一個高精度,一個單調遞增,保證了我們在提取性能指標的時候,不會受宿主環境的一些時間影響。

然後在 HRTime 之上,是一個叫 PerformanceTimeline 的 API,這個API很簡單,它只是去定義了把各種各樣的性能的情況。

在此,我們得出的最佳實踐是:採集性能數據時先抹平 Navigation Timing spec 差異,優先使用 PerformanceTimeline API(在複雜場景,亦可考慮優先使用 PerformanceObserver)

定義合適的指標

講完使用標準的API,我們來看一看,怎麼定義合適的指標,假設有一天你老闆問你說,我們頁面性能怎麼樣,你回答他,反正我覺得打開挺快的,這是一個非常不嚴謹的論述。我們到底怎麼樣定義我們的頁面性能呢,其實業界有非常多的方案,這裏只是列舉了十幾個相對來說比較常見的一些的性能指標定義方式。

那麼在這麼多方案裏邊,到底哪些指標是適合我們的,我挑了幾個具有典型代表意義的指標例子,給大家做個詳細的講解。

首先我們來看一看大家最熟悉的頁面加載時長,頁面加載時長是被清晰的標在這個頁面的底部的。它是指 DOM load 事件觸發完成,它的優點有:

  • 原生API;

  • 接受度高;

  • 感知明顯(瀏覽器Tab停止loading)。

缺點是:

  • 無法準確反映頁面加載性能;

  • 易受特殊情況影響。

爲了解決這個問題,W3C 的工作小組引入了首次渲染/首次內容渲染。首次渲染是指第⼀個非網頁背景像素渲染,⾸次內容渲染是指第一個⽂本、圖像、背景圖片或非白色 canvas/SVG 渲染。

這裏以打開 YouTube 爲例,因爲打開這個網站可能相對反應會慢一點,我們能更容易發現這種區別。當你去採集這兩個指標的時候,你會發現,或者說你在大部分時候會發現,這兩個指標沒有任何差異。

根據我們實際採集到的性能數據也證實了我們的觀點,在 70% 的情況下,First Paint和First Contentful Paint他們兩個指標之間的差異小於一百毫秒,爲什麼?因爲現在一個前端網站的架構設計是傾向於單頁應用的,它原本的HTML結構非常小巧。在渲染的時候,尤其是在第一次渲染的時候,它就已經能夠把文本和背景一起渲染出來了,這兩個指標差異會非常之小。

我們可以總結一下,這兩個指標相比於頁面加載時長它更聚焦於頁面元素的渲染,相對來說更客觀,但同時可以看到,頁面上有象素被渲染出來,並不一定代表着用戶去看到了它關心的主要內容,在實際的經驗中也可以看到,大多數時候,這兩個指標的相差並不是特別大。

那麼頁面加載時長會有異常情況,First Paint和First Contentful Paint又會有各種差異不大,或者是不能夠完全代表這個頁面性能的情況,於是就有下面一個算法,通過算法計算出來的指標,叫做First Meaningful Paint,首次有效渲染時長,這個指標最早是由Google提出的,它的一個核心的想法是渲染並不一定代表着用戶看到了主要內容,Load也不一定代表用戶看到主要內容,那用戶什麼時候能夠看到主要內容呢?我們假設當一個網頁的 DOM 結構發生劇烈的變化的時候,就是這個網頁主要內容出現的時候,那麼在這樣的一個時間點上,就是用戶看到主要內容的一個時間點。

具體,可以查看此鏈接:

https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/view#heading=h.ycg9fbz776q3

它的優點是相對校準的估算出內容渲染時間,貼近用戶感知。但缺點是無原生API支持,算法推導時 DOM 節點不含權重。

然後最後一種指標叫做開始渲染時間,Start Render Time,這個指標時間是沒有辦法或者很難通過JS在用戶環境執行來獲取的。因爲它的定義非常簡單,就是假設我們開始加載頁面,一直拿個照相機對着屏幕拍,直到拍到有一幀,發現這個頁面不一樣了,那麼這個時間就是頁面的一個開始渲染時間。這個定義看起來和那個剛纔講的一個First Paint非常像,但實際上,因爲它的採集方式不一樣,First Paint還是通過瀏覽器的渲染引擎來計算出來的時間,而Start Render Time 就是客觀觀察到頁面的一個加載變化的時間,所以可能Start Render Time能夠更客觀地反映我們頁面的加載情況,但是因爲這個指標很難通過JS計算,所以是僅僅作爲一個參考。

講了這麼多性能指標的定義方法,我們做一個簡單的總結。

這麼多的性能指標,我們到底應該怎麼選呢?我們得出了最佳實踐:根據業務特性及性能監控方案選擇最適合的性能指標,必要情況下可使用自定義性能點位。

怎樣採集正確的數據?

定義完合適的指標,我們來看看怎麼樣採集正確的數據。

不同階段之間是連續的嗎?

這是目前最規範的一個 RUM 性能模型,可以看到我們的頁面加載被定義成了很多個階段。

很多做性能監控的方案都是計算階段再上報數據,當然這可以減少數據傳輸量,但是在實際的應用中我們會發現,這樣做是有問題的。

比如說DNS 查詢完,馬上就開始建立 TCP 連接了嗎?TCP連 接完馬上就開始去發送這個請求了嗎?實際上是不一定的,除了我們剛纔模型定義的階段之外,會有一個叫做 stalled 的時間,那麼這個階段發生了什麼事情呢?

當你觀察到有這種 blocked 或者 stalled 的時間出現的時候,大部分情況下,都是因爲被同域名TCP併發連接限制了,就比如說,我們一個頁面可能加載了CDN上的十個資源,那麼在TCP連接會有限制,比如Chrome 它就會有限制。單頁面、單個域名的TCP連接,併發數是6,也就是說加載十個資源,只有六個連接被真正建立了,剩下四個資源是要等待這六個連接消費完成之後才能被複用的。

還有其他的一些情況也會導致這個頁面 stalled 的時間產生,如果我們只是用剛纔那個性能去計算某一個階段的時間的話,就會把這個 stalled 的時間漏掉,這樣會導致我們真正把各個階段加起來去算這個頁面總的加載時間的時候,出現一個誤差。在實際觀察中我們會發現,這種 stalled 時間是非常常見的。

每個階段都一定會發生嗎?

一個最典型的例子,DNS查詢,每次頁面訪問都會去查DNS嗎?我們知道現在瀏覽器會對DNS查詢結構做緩存了,你第一次訪問了淘寶,你第二次再訪問的時候,很大概率DNS是不會再查詢了的,那麼這個階段可能算出來就是0,那麼在各種特殊的情況下,我們怎樣去採集這個正確的數據呢?

我們目前得出的結論是:上報頁⾯加載開始時間,以及後續各時間點相對增量,在數據端進行階段清洗和異常處理。

比如說頁面開始加載時間是0秒,那麼從0項開始時間是一毫秒,TCP開始時間是兩毫秒,把各個時間點相對的增量算出來。

最後在數據端進行一個階段的清洗,以及異常的處理,這樣一方面能夠保證在規範定義中沒有被體現出來的階段不會被遺漏掉,另一方面也能夠讓我們去掌控這個頁面加載的分佈到底是怎樣的,也不會因爲我們在前端就把這個頁面算好,遺漏導致數據的丟失。

採集完合適的指標,最後一個是上報關聯的維度。

我們都知道在做前端的數據採集的時候,維度數據是非常重要的,除了我們剛纔定義的各種度量,怎樣採集到合適的相關維度,也能夠極大地幫助我們分析頁面性能的效果。

上圖提到的點都是必須要採集的,但是在分析頁面性能的時候,有很多相對專業的維度是會被大家忽略掉的,比如說當前頁面是否可見,這個頁面加載方式是怎麼樣的,它是直接打開,還是刷新打開,還是前進後退打開等等。就是通過後面的數據分析,我們會發現,不同的頁面操作,頁面打開方式都會對我們頁面加載的性能會有影響,以及一些更復雜的,比如說是否啓用 HTTP2、Service Worker 等等,這些數據我們都應該儘可能採集到,從而能夠更好的去分析我們的頁面性能。

準確分析性能數據及影響因素

講完前端數據的一些採集,我們來看一看怎麼樣去分析這些性能數據。這個是我們採集到真實的螞蟻的某一個業務的頁面加載時長數據。可以看到大部分的頁面都會在10秒內完成這個頁面的加載,但是仔細觀察就會發現,還會有大量的長尾值存在,可以看到從10秒到30秒,甚至到60秒,都會採集到這樣的數據,這也是一個非常真實的情況。

這個數據可以從兩個方面進行解讀,第一,由於頁面加載時長會受很多異常因素的影響,你的用戶可能已經在正常地使用你的網站業務,只不過一個 icon 沒加載出來,一個小圖片沒加載出來,就會導致這個指標變得超長,這都是合理的情況。而另外一種情況就是在分析性能數據的時候,我們會發現,長尾的數據是非常常見的,因爲影響外部性能因素太多了,除了前端自己的原因,除了我們JS寫的太大,靜態資源太多,沒有壓縮等等,比如用戶的網絡不好,服務器壓力突然很高,甚至 IO 堵塞了,都可能導致最後這個頁面沒有被渲染出來。

在螞蟻內部,我們開玩笑說,一些體量比較小的業務,可能有一天它的用戶蹲在廁所裏面用手機去刷了一下它的網站,頁面加載時長統計出來的數據就要暴漲,這都是非常真實可能存在的情況。

我們理解這些長尾數據之後,來看一看還會有哪些影響我們這個頁面加載性能的因素,我剛纔在那個上報維度裏面講到了,頁面的可見狀態是一個非常大的影響因素,從Chrome 68開始,Chrome啓用了一種全新的瀏覽器的架構模式,在這種模式下,其實它對不可見Tab的硬件資源的分配做了一個極大的限制。

你會發現,在你的不可見Tab下,可能你的動畫會被減慢。那麼比如說用戶他在後臺打開你的頁面,就是說它在前一個頁面,比如說右鍵,先打開,後面右鍵後臺先打開,這個時候在你這個新打開的頁面去提取性能數據的時候,會發現那個性能就沒有正常打開,相對來說就沒有那麼好。

另外一種頁面加載方式也會影響我們頁面性能,比如我們正常打開一個頁面會去加載各種各樣的資源,如果刷新打開,可能有一些緩存,瀏覽器幫我們做掉了,一些靜態資源,304沒有更新,遊覽器就不會重新下載,比如說前進後退的時候,甚至瀏覽器會幫我們做一些頁面內容的緩存,有些頁面前進後退都是秒出的,根本沒有出現那種頁面完整刷新的效果,就是這種不同的頁面加載方式,會影響到我們頁面的性能數據的採集結果。

最後一種就是Service Worker,大家如果實踐過就會發現,Service Worker能力非常強大,它可以做非常強大的緩存,這是可能會影響到整個頁面的一個性能。

數據是如何撒謊的?

最後一個我想講的是,我們性能數據是怎麼樣撒謊的,還是以剛纔這個採集到的性能直方圖爲例,老闆問說,你性能採集到了,你給我一個數,你們這個頁面性能到底怎麼樣?我說我算了一下,大概是6秒左右。

老闆不開心了,說這個數據怎麼這麼大?然後我們可以通過一些小技巧,一些不同的加工方式,又算了一下,算出來是4.2秒左右,老闆說還可以再好一點。又換一種加工方式,2秒5左右,這個數字不錯,同樣的一份數據,原始的直方圖就擺在這裏,同樣的一份數據,爲什麼會得出三個截然不同的結論呢?

這是因爲我們指標計算的口徑不一樣,第一個數據,特別大的數據是95分位數,所謂95分位數就是把我們所有的頁面加載時長,從小到大排列,取第95%的位置,這個值作爲我們的性能指標,那麼使用95分位數,隱含的意義就是,我們承諾95%的人訪問我們頁面的時候,頁面加載時長是小於6秒的。後面一種雞賊的方法就是我們取平均數,緩存特別好的,可能幾十毫秒就打開了,緩存不好的幾十秒纔打開,我們取平均一下,看起來也很中立,4秒多;如果我們再雞賊一點,比如說我們長尾數據特別長,我們還可以取10%的截尾平均數,掐頭去尾取平均,這樣的數據看起來就非常漂亮。

但是在實際上,如果我們做的是一個給自己看的性能產品,我們應該是對自己的性能數據負責任,所以,在實際分析性能指標的時候,我們是建議分析性能指標時建議關注百分位數(percentile),對性能的要求越高,使用越大的百分位數。

比如我們要承諾 99% 的用戶都要小於 5 秒,我們看頁面加載時長時候就應該看 99 分位數。如果我們現在精力不夠,我們只能承諾 50% 的人頁面加載時長小於 5 秒,實際上 50 分位數,就是中位數,就是 50% 的訪問能夠不小於這個時間打開這個頁面。

總結

最後一點,我想講的是在螞蟻金服,我們是怎麼用這些性能數據的。

首先性能數據採集當然是要給用戶一個分析的結果,因爲這是一個性能分析的產品,我們要從各種維度,各種時間和剛纔講到的關聯的維度,給用戶一個分析的抓手,告訴它你的性能瓶頸在哪裏,你的綜合的情況怎麼樣的,讓用戶感知他的業務到底是快還是慢。

一些客觀的數據能夠了解自己業務的綜合情況,但是看到自己的數據還不夠,我們在螞蟻金服在做另外一件事情,就是對所有中後臺產品做一個性能數據的橫向比分,當然不僅僅是性能數據,這個產品是綜合的一個叫體驗分的東西,體驗包括了性能數據,同時也包括了頁面的美觀度,用戶的感知度,以及一些任務的轉化率等等,其中這個性能的分數可以通過我們採集到的性能的指標來反饋。然後當然你的可能有人就會覺得說,我們業務形態不一樣,我們的前端架構方式不一樣,你把這些分數橫向比較有什麼意義?

這個時候就涉及到性能指標的定義,應該儘可能的選取對,如果我們是一個性能監控的中臺,而不是一個簡單的對自己業務性能的一個監控,我們應該儘可能的選取一箇中立的指標去衡量,就是客觀的衡量各種業務的一個展示形態,同時在做一個橫向比較的時候,我們也會換用不同的性能計算方式,比如說在這個體驗分項目裏面,我們就會用截尾平均值,目的並不是爲了讓這個數據變得好看,是爲了讓這個數據變得平穩,讓異常值不會產生太大的影響。同時我們在一些橫向比較中,或者在這種大盤的比較項目中,我們也會儘可能的去讓用戶關注自身性能的一個提升,你可以不關心你比別人快還是慢,但是你要關心你比之前快了還是慢了。

以上就是整個性能數據的分享以及在螞蟻金服的應用,謝謝大家。

更多內容,請關注前端之巔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章