2017前端監控系統探索總結

背景

在當下互聯網行業,監控概念與重要性已經不需要再進行闡述,然而監控分為多種,對物理層(機房,雲主機)的監控,對傳輸鏈路的監控,對已部署服務的監控等等,而後端的代碼通常直接運行在伺服器並處於24小時實時的監控狀態之下,一旦服務的可用性出現問題,SRE和DEV往往在第一時間就會收到告警,並根據告警信息在第一時間解決故障。相比之下,前端代碼則運行在客戶端上,為了讓前端能夠和後端一樣,需要將客戶端的前端代碼監控起來,當客戶端出現故障時,能第一時間通知到前端負責人,定位故障,及時止損。

前端監控系統都需要監控什麼?在前端應用日漸複雜的今天,我認為對於前端的監控主要分為三個方面:

性能監控

為什麼要監控性能呢?因為對於任何一家互聯網公司,性能往往與利益直接相關。有數據調查顯示:當Google 延遲 400ms時,搜索量下降 0.59%、Bing 延遲 2s,收入下降 4.3%、Yahoo 延遲 400ms,流量下降 5-9%,所以,很多公司在做用戶體驗分析時,第一個看的就是性能監控指標,在前端領域,性能無非是以下參考指標:

  • 白屏時間;
  • 首屏時間;
  • 用戶可交互時間;
  • 總下載時間;
  • TCP連接時間;
  • HTTP請求時間;
  • HTTP響應時間;

而且很重要的一點,也是大家往往最容易忽視的:性能會伴隨產品的迭代而有所衰減。特別在移動端,網路條件十分不穩定的情況下。性能優化不存在「黃金法則」,我們需要一套性能監控系統持續監控、評估、預警頁面性能狀況、發現瓶頸,更有針對性的指導優化工作的進行。

異常監控

除了性能之外,我們還要監控客戶端腳本發生的報錯,前端報錯受網路,機型,業務邏輯影響而且大部分錯誤難以還原現場,比如我們團隊時時收到用戶的反饋和投訴:

  • 甲用戶 : 點擊XX按鈕之後頁面白屏了

  • 乙用戶: 優惠劵消費後,支付金額顯示不正確
  • 丙用戶:最近頁面特別卡,點擊tab好久才能反應過來

面對用戶的反饋,開發經常感到困惑:到底有多卡,哪個步驟卡?是個別現象還是大面積都受到了影響?白屏時頁面請求的返回碼是多少?是被運行商劫持還是CDN出了問題?能讓用戶用Charles配合抓個包么?如何做有針對性的優化?優化的結果怎麼去衡量?

為了解決這些痛點,我們需要對客戶端服務進行基於用戶行為的監控。

數據監控

互聯網公司的產品,每一個決策,每一個迭代都需要分析各種數據,數據中往往會有我們需要的答案:

  1. 頁面PV,UV可能直接影響轉化率
  2. 從用戶訪問頁面的順序挖掘使用習慣(等等)

這部分監控數據主要供PM/PD使用,業務數據可以驅動業務自身的增長,有人曾說:「要降低創業失敗的可能性只有兩種方法:一是未卜先知,另一個是做精益的數據分析」,由此可見數據分析的重要性。

傳統解決方案及缺陷

目前已經存在了一些針對前端監控解決方案:Sentry,Badjs,jsTracker,GrowingIo等等,在公司內部也有自研的監控系統。它們都從不同維度試著解決前端在監控方面的問題,大家的實現思路都很類似、要實現監控,首先要採集指標:

性能指標

這裡要針對的主要是白屏時間、首屏時間、用戶可操作、總下載時間。

這裡以首屏時間為例:高版本chrome瀏覽器中可以直接通過 firstPaintTime 介面來獲取load time,但大部分瀏覽器並不支持,必須想其他辦法來監測。謹記一點,在做時間相關測量時,千萬不要使用setTimeout和setInterval方法,因為在單線程執行引擎中,非同步隊列的執行是不能確保執行時間的。這邊給出一種可行的測量方案,準確率在99成以上。

<doctype html>

<html>

<head>

<script type="text/javascript">

var timerStart = Date.now();

</script>

<!-- 載入其他資源,執行代碼blabla -->

</head>

<body>

<!-- 路由框架掛載節點 -->

<script type="text/javascript">

$(document).ready(function() {

console.log("DOMready 時間 ", Date.now()-timerStart);

});

$(window).load(function() {

console.log("所有資源載入完成 時間: ", Date.now()-timerStart);

});

</script>

</body>

</html>

另一種優雅的解決方案是直接使用window.performance介面:

connectEnd Time when server connection is finished.

connectStart Time just before server connection begins.

domComplete Time just before document readiness completes.

domContentLoadedEventEnd Time after DOMContentLoaded event completes.

domContentLoadedEventStart Time just before DOMContentLoaded starts.

domInteractive Time just before readiness set to interactive.

domLoading Time just before readiness set to loading.

domainLookupEnd Time after domain name lookup.

domainLookupStart Time just before domain name lookup.

fetchStart Time when the resource starts being fetched.

loadEventEnd Time when the load event is complete.

loadEventStart Time just before the load event is fired.

navigationStart Time after the previous document begins unload.

redirectCount Number of redirects since the last non-redirect.

redirectEnd Time after last redirect response ends.

redirectStart Time of fetch that initiated a redirect.

requestStart Time just before a server request.

responseEnd Time after the end of a response or connection.

responseStart Time just before the start of a response.

timing Reference to a performance timing object.

navigation Reference to performance navigation object.

performance Reference to performance object for a window.

type Type of the last non-redirect navigation event.

unloadEventEnd Time after the previous document is unloaded.

unloadEventStart Time just before the unload event is fired.

介面兼容性:n

異常指標

主動捕獲異常方案主要是 onError 和 addEventListener,onError 在 IE6 開始就支持了,所以 大部分系統的主動採集是使用的 onError。這裡注意瀏覽器的同源性策略(CORS),在高級瀏覽器中如果瀏覽器捕獲到了錯誤信息,如果 JS 文件所在的域名(如:meituan.com)和當前的頁面地址(如:dianping.com)是跨域的,那麼引擎會自動把onError 中的參數 替換為 script error,此時無法獲取行列數以及報錯詳細信息。解決方案是在標籤引入時加上crossorigin欄位。

雖然傳統方法能夠自動catch大部分錯誤,但是也伴隨著以下缺陷

  1. 部分應用在不同網路,機型上表現不同,開發人員需要獲取到更詳細的分類信息,傳統系統很難做分類聚合,開發面對幾十頁分類的table無從下手。
  2. 錯誤與錯誤之間往往成相關性,但是在傳統系統中catch到的錯誤都是孤立的,沒有基於用戶行為的分析。
  3. 針對異常的告警配置不夠靈活,無法滿足開發需求:大部分異常告警指標伴隨高低峰期波動,沒有一成不變的指標,而傳統告警平台都是採用絕對閾值的告警方案,要麼高峰期誤報太多,要麼低峰期錯誤無法發現。

數據指標

傳統監控方案採用的都是手動埋點上報,但是缺點十分明顯:手動埋點往往會出現埋點混亂,甚至埋錯、漏埋的問題,埋點溝通過程中,數據團隊和業務工程團隊配合困難,新功能的開發往往伴隨著新埋點的增加,而且數據團隊的需求優先順序往往靠後,導致很多新上線功能得不到數據的驗證。

而有些基於關係型資料庫的系統實時性差,數據要隔天才能查看,查詢命令執行一次動輒耗費幾十分鐘乃至上小時,已經沒有效率可言。

新解決方案

伴隨著上面討論的問題,我們尋求新的解決方案,一種高可用的監控方案。它應該具有如下特徵:

  1. 全量採集:監控指標健全,端到端採集全量的性能指標,關鍵執行方法,業務指標,覆蓋率做到2個9以上。
  2. 無需埋點:全量上報,無需開發人員手動埋點,從根本上杜絕錯埋和漏埋
  3. 查詢便捷:能夠按照地域,機型,操作系統,瀏覽器版本,網路狀況等多個維度進行數據查詢,最好支持全文搜索,分類聚合。
  4. 場景還原:根據打點信息,還原用戶從登陸開始一系列操作,建立基於用戶行為的時序圖。
  5. 實時性強:秒級查詢,上線後能立即看到優化效果並指導下一步優化。
  6. 智能告警:採用靜態閾值和動態閾值結合的方式,兼顧高低峰,減少誤報漏報。並針對業務數據建立告警指標,發現系統深層次問題。

根據這些需求,我們團隊打造了一套全新的監控體系,新系統採用了無埋點SDK(小程序),ELK做本地化日誌存儲,並使用了基於動態閾值的告警策略。下面是系統架構圖:

開發人員在本地通過代碼接入SDK後,即可使用監控體系的全部功能:數據採集,上報,聚合分析,智能告警等功能,而且所有數據均是實時上報,秒級查詢。

在最開始探索過程中,我們使用webpack插件+npm包下載方式,但是由於兩部分上報邏輯在網路極差的情況下,會出現寫緩存衝突的問題,導致重複上報或錯誤上報,現已將架構調整為單一script標籤引入的方式,部分保留下來的主動上報介面,開發可以根據自己需要在業務代碼中再次封裝:

...

moduleClick(options) {

const { name, ...otherOptions } = options;

M.moduleClick(name, otherOptions);

},

/**

* 曝光事件

* @param options

*/

moduleView(options) {

const { name, ...otherOptions } = options;

M.moduleView(name, otherOptions);

},

/**

* 編輯事件

* @param options

*/

moduleEdit(options) {

const { name, ...otherOptions } = options;

M.moduleEdit(name, otherOptions);

},

...

接入後,新版系統和之前相比有哪些變化?n

1.因為採集是無埋點全量的,關鍵方法都會進行參數上報,然後可以通過分類聚合建立用戶的操作時序,通過故障上下文準確定位問題。

2. 對resource和ajax請求指標做採集,可以篩選出故障用戶當時的場景信息:

3.告警採用動態閾值,對於周期性強的數據,通過機器學習的演算法進行環比告警,大大降低了誤報和漏報:

在做日誌存儲的時候,數據量是一個挑戰,我們採用的是集群架構,但是一個用戶量很大的站點,日誌的上報量是非常高的,高峰期時,一個1萬日活APP可能會突破3000的qps,這對日誌系統並發能力和穩定性是很大的挑戰。我們選擇了全量master+data節點的方式,對數據副本分片設置為1,任意一台節點掛掉,會由副本選舉出新的主分片,不會造成日誌丟失。在寫入方面,我們選擇了bulk方式批量寫入,通過反覆試驗,批量寫入線程大小在5MB-15MB之間。由於日誌系統是主要面對寫入的,所以關閉了_all查詢,寫入性能優化1倍,同時gc新老分配為1:4,保證了批量寫入的穩定。

總結與未來規劃

通過對新監控解決方案的探索,我們積累了比較寶貴的數據分析經驗,對於終端哪些數據對於故障處理,性能優化起到重要作用有了新的認識,不過目前系統仍處於迭代過程中,距離預定的目標還有比較大的優化空間。

在未來,我們將重點攻克以下幾個問題:

1.系統輕量化

減少上報量:合併數據結構,釋放更多上行帶

優化SDK性能:減少緩存寫入頻率,做到業務對監控模塊無感

2.讓監控更智能

識別周高峰和節假日,同時增強數據清洗能力,提高數據的可用性

3.優化數據分析體驗

開放埋點配置平台,讓產品自主配置業務埋點,通過配置文件轉化成埋點,省時高效。


推薦閱讀:

介紹幾款常用的在線API管理工具
一些國內值得關注的API合集
對 echo 框架進行統一的自定義錯誤處理
如何讓Scaladoc鏈接到外部API?

TAG:前端开发 | 数据分析 | API |