自己動手打造前端效能監控系統
背景
為什麼要做監控頁面效能?
一個頁面效能差的話會影響到很多方面。在公司層面,頁面效能會影響公司收益,如使用者開啟頁面等待的太久,使用者可能會直接關掉頁面,或者下次不再打開了,特別是在移動端使用者對頁面響應延遲容忍度很低。
除此之外,頁面的載入速度還將直接影響頁面的SEO,網頁載入速度太慢,使用者會直接關掉,這直接增加頁面的跳出率,當搜尋引擎發現頁面跳出率高,搜尋引擎會認為該網站對使用者的價值不高,從而降低排名。2018年7月谷歌公司新規定,頁面訪問時間比較長,谷歌公式將會降低該頁面的搜尋排名。
雖然效能很重要,但在開發迭代中,開發會有所忽略,效能會隨著版本迭代而有所衰減,所以我們需要一個性能監控系統,持續監控,評估,預警頁面效能的狀況,發現瓶頸,從而指導優化工作。
頁面效能的評估與監控有很多成熟優秀的工具 ,比如gtmetrix 網站,他可以同時查多個分析工具的的結果,會提供許多的建議。
但這種方式與真實情況偏離,無法反饋某個地區的整體速度,慢速使用者多少,無法反映效能的波動情況,另外除了白屏之類的,我們還有一些功能性的測速,比如頁面可點選時間,廣告展示的時間等等,這些都是無法模擬監控的。
為了持續監控不同網路環境下使用者訪問情況與頁面各功能可用情況,我們選擇在頁面上植入js來監控線上真實使用者資料。具體做法就是用一段程式碼將使用者的資料上報到我們的伺服器,通過一個系統將資料彙總,處理,最後圖形化資料,方便我們檢視各個頁面等效能。
測速系統的設計
測試系統分三個部分,如下
- 前端上報
- 如何記錄測速時間點。
- 如何上報。
- 資料的取樣。
- 資料處理,入庫。
- 資料展示
前端上報
在前端植入一段前端js程式碼,通過這些程式碼來上報頁面效能資料,那一般哪些指標能夠更好的反饋使用者的體驗呢?
使用者最大感受就是,頁面為什麼開啟要等那麼久,為什麼圖片載入那麼慢,頁面載入半天也不能點選。這些使用者的感受對於程式員來說就是重要的頁面效能指標。根據使用者上述痛點抽象出指標,白屏時間,首屏時間,可互動時間。那麼這個時間我們是如何統計的捏?
確定統計起始點
起始點時間,應該是我們輸入網址後,點回車作為起始點,這樣才是使用者真正開始等待的時間。如果是高階的瀏覽器,我們可以直接使用Navigation Timeing介面來獲取統計起點。
Navigation Timeing介面是一個在web中精準測量效能的javascript API,這個介面提供了一系列詳細的時間狀態。
在Chrome中開啟控制檯,在命令列中輸入performance,點開並檢視它的timing屬性,你會看到如下程式碼
每一個performance.timing屬性都表示一個頁面事件(例如頁面傳送了請求)或者頁面載入(例如當DOM開始載入),測量以毫秒的形式從1970年1月1日的午夜開始。結果為0表示該事件未發生(例如redirectEnd或者redirectStart等)
這裡有一張從 Navigation Timing draft 弄來的 performance.timing 事件的順序圖。
其中navigationStart表示當瀏覽器請求的時間點,通俗地就是你在url輸入欄裡按下回車間的時間點,或則頁面按F5重新整理的時間點。
其他時間點的詳細解釋請點選 https://www.w3.org/TR/navigation-timing/,或則google一下,有多篇文章做了解釋,這裡就不再累述。
此介面大部分瀏覽器都已經支援,除了pc端ie9以下的瀏覽器。
白屏時間
使用者看到頁面展示出現一個元素的時間。很多人認為白屏時間是頁面返回的首位元組時間,但這樣其實並不精確,因為頭部資源還沒載入完畢,頁面也是白屏。
真正白屏結束的時間分為三種。
第一種沒有靠js渲染的普通頁面,白屏時間應該是在頭部外鏈資源載入完,因為瀏覽器只要載入頭部資源才會真正的渲染頁面。所以白屏時間點最好是列印在頭部末尾的位置(這裡可能也不精確,但儘量保證接近),如程式碼所示。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><!--頭部資源--><link href="style.css"><title>Document</title><scirot>// 白螢幕結束時間var time = +new Date() - performance.timing.navigationStart;</scirot></head><body></body></html>
第二種,是使用了一些前端框架,比如vue,reacjs,他們需要執行完js後,才會將內容渲染到頁面上,或者非同步拉取資料,在資料拉回來在顯示頁面。這種情況下我們一般會在頁面價格loading態。那麼白屏結束時間在這個loading載入的後面。
首屏時間
首屏時間是指頁面第一屏所有資源完整展示的時間。這個時間每個頁面都不一致。比如一個頁面的首屏是4張圖片,那麼我們應該在四張圖片載入完成後才算首屏時間,或則頁面是非同步拉取資料的,首屏時間應該是將資料插入到瀏覽器的時間。總之找到首屏資源最後載入完成的時間點就是首屏時間。
上報方式
測量好時間後,就需要將資料傳送給服務端。測速資料對丟失率要求比較低,且測速應該儘量在不影響主流程的邏輯和頁面的效能前提下進行。使用的img標籤get請求來上報資料,主要有以下原因。
- 不存在ajax跨域問題,可做不同源的請求
- 很古老的標籤,沒有瀏覽器相容性問題
var i = new Image(); i.onload = i.onerror = i.onabort = function () {i = i.onload = i.onerror = i.onabort = null; } i.src = url;
一些高階瀏覽器還支援 navigator.sendBeacon方法。這個方法可以用來發送一些小量資料,該方法是非同步的,且在瀏覽器關閉請求也照樣能發,特別適合上報統計的場景。
navigator.sendBeacon(url, data ? $.param(data) : null)
最終方案:當瀏覽器支援sendBeacon方法,優先使用該方法,不支援時使用img的方式上報。
取樣
測速上報資料是海量的,由於資料太大,入庫處理時間也會增加,且伺服器效能有限,為了避免資源的浪費,在上報過程中進行資料取樣處理。取樣的粒度由使用者端自己控制,如果取樣為1/10, 也那麼上報資料要加上rate=10, rate為取樣率。
資料收集和入庫
我們在一臺機器上起了一個nginx伺服器,nginx伺服器可以記錄訪問,將使用者的訪問記錄寫進日誌,這個日誌可以記錄請求的所有資訊請求頭,例如請求引數,請求ip。日誌可以按照自己設定的格式來生成日誌。
當頁面的測速傳送請求過來,nginx記錄這個請求,將該請求寫進日誌中。
我們並沒有用到nginx的logrotater(日誌定時輪詢)。由於logrotater最小顆粒度是1天,但我們希望日誌是按5分鐘一個檔案來儲存(原因是檔案可以分批處理,避免一次性處理檔案太大,且我們查詢的測速點一天的走勢的測速點顆粒度也是5分鐘)。
nginx配置如下:
if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{1})[0-4]"){set $logname $1-$2-$3-$4-$50;}if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{1})[5-9]"){set $logname $1-$2-$3-$4-$55;}生成該日誌:access_log logs/stat.y.qq.com.sp.access.$logname.log spdata;
直接在日誌檔案生成的時候,就分割好。但這樣有個缺點不能使用logrotate定時刪除日誌檔案的功能,要額外寫一個定時刪除日誌的指令碼,避免檔案過度沉澱,浪費磁碟資源。
log存的資料格式為:
log_formatspdata'$time_local ~|^ $http_x_forwarded_for ~|^ $request ~|^ $http_referer ~|^ $status ~|^ $http_user_agent ~|^ $cookie_ptisp ~|^ $cookie_uin';
其中 ~|^ 為分隔符
統計的欄位有
- 時間,按5分鐘
- IP,使用者的ip地址
- data,頁面上報的資料
- appid 產品id產品id (比如QQ音樂, 全民K歌)
- pid 專案id (比如PC客戶端, YQQ, QQ音樂手機客戶端, 其他H5)
- pageid 頁面id (專案下面的具體某個頁面)
- points 測速點 1=xx&2=xx&3=xx...
- r 取樣率 0~1
- referer 頁面referer
- ua 解析出 分平臺 分系統 分版本 分app 網路型別
- isp 運營商
- uin 使用者的id
為了減少伺服器的壓力,上報機器與入庫伺服器分離。入庫時,入庫伺服器定時從上報機器上拉取日誌,進行資料入庫。
資料的入庫
資料的處理是該系統一大難題,全平臺每天的pv上億。為了避免資料過於龐大,我們將收集的資料按日期建立新表。
即使按日期建立新表,查詢的資料也有上千萬,直接查詢表的資料也是非常耗時的。為了解決資料查詢耗時的問題,我們建立了三個表,資料統計表,原始資料表,原始資料索引表 。
資料統計表
統計表是記錄5分鐘內某個頁面所有點的平均耗時。在解析資料的時候,程式將一天分為多個5分鐘,計算每個測速點的5分鐘平均速度,並寫進資料統計表,在查詢某個測速點的一天的走勢,我們可以直接查詢統計表,無需將所有點再重新便利一遍。使用統計表可以大大減少查詢的資料量,從而提高查詢速度,查詢mysql是毫秒級別。
原始表 & 索引表
資料統計表,可以解決大部分資料查詢需要,但如果增加幾個複合條件查詢(查詢條件有,國家,省份,運營商,網路型別,操作平臺),顯然統計表是滿足不了的, 如果把每個條件組合都建立一個統計表,那會產生很多額外的表,而且複合查詢使用率也不是很高,付出的卻很大,這種方案是不可行的。
我們將原始資料分別存到不同的表裡,通過索引表裡面的索引來查原始資料。一個表的資料不宜過大,資料超過一定的數量級,查詢速度會非常慢,為了保證Mysql的效能,這裡建議單表記錄數不超過1千萬。通過索引來查詢各個分表的資料。
閾值告警
在某個資料介面返回太慢而導致頁面開啟速度變慢,這個時候我們需要一個預警,來通知開發人員,在處理資料入庫時,某個節點5分鐘平均用時超過預設閾值,或者預設閾值10秒,系統會將這個資訊以某種方式來告訴開發人員。頁面通過告警能夠非常敏感發現問題,從而及時解決,不讓問題繼續擴大。
資料展示
系統提供主要展示一個頁面各個測速節點耗時的柱狀圖,單個測速點單天走勢,和一段時間走勢圖。多維分析列表。
頁面整體概況
來展示整個頁面所有測速點耗時情況。
使用柱狀圖的原因,也是為了方便了頁面開發人員,觀察到底是哪個地方最為耗時,頁面的瓶頸在哪。
除了檢視頁面的整體耗時,還可以檢視單個測速點詳細情況。
測速點詳情
測速點將展示下面幾個方面的資訊
- 平均耗時
- 請求量
- 慢速使用者比例
- 速度的正態分佈
為了方便挖掘效能可能的瓶頸,需要從多維度對資料進行分析,比如移動端比較看重網路型別,需要我們根據網路型別對資料進行分析。
常見的維度的還有
- 國家
- 省份
- 運營商
- 網路型別
- 作業系統
異常資料處理
在看觀察點的走勢圖中,走勢圖會有一個很長的突刺。造成突刺的原因是這個點的延時要比周圍點的資料高很多。為了搞清楚突刺的原因,查詢了原始表,大多數上報點都是正常的,但會有一次上報的耗時是30多分鐘,目前不知道為什麼會上報這麼長的渲染時間,可能跟使用者機器有關,也可能跟當時網路情況有關。這些資料是使用者端上報的,具體很難定位問題,這些點對算出來的圖表平均值影響較大,為了保證資料整體正常,資料不受某一個異常節點影響太大,我們將大於10分鐘的點過濾掉直接過濾掉。
總結
我們從三個各方面,前端上報,資料收集和入庫,資料展示來介紹瞭如何打造一個測速系統,效能優化是我們需要持續關注,為了打造流暢的使用體驗,測速系統是必不可少的工具。
參考
https://fex.baidu.com/blog/2014/05/build-performance-monitor-in-7-days/
https://www.qcloud.com/community/article/655542
http://javascript.ruanyifeng.com/bom/performance.html