開發與維運

釘釘前端-如何設計前端實時分析及報警系統

關於釘釘前端

釘釘從 2014 年底創業至今,發展極其迅速,釘釘前端監控也在相應的演進。我們有億級的用戶和千萬級的企業用戶,前端產品有安卓、iOS、桌面端、小程序、 H5等,前端應用的發佈也涵蓋全量發佈、灰度發佈的情況。

億級流量的挑戰

對於這樣一個億級平臺,除了做前端監控系統之外,我相信很多小夥伴也有體感,要保證整體釘釘前端的穩定性,還需要有一些技術運營的手段,包括人的一些情況。我們現在整體有 100 多個前端開發成員,然後我們的技術模塊上面有 IM、通訊錄、直播、教育、文檔、硬件等等非常具有 B 端屬性的業務。

image.png

成果

image.png

我先說一下我們的成果: 100% 覆蓋了我們今天所有的 h5 和小程序、支撐了 100 多個前端人員的監控需求。前端監控的日誌量達到百億,監控大盤個數超過 100 個,能做到線上問題一分鐘感知和一分鐘模糊問題定位。在人力投入上面,始終維持在兩個負責人員以內,大部分情況下是我一個人為主在負責整體的一個監控情況,所以在人力投入上面,我們的成本是相對比較低的。

上圖中的兩張趨勢圖是我們監控的主要產品結構,一張是我們的監控趨勢圖,另一張是我們的業務大盤文件夾用於承載各個業務,同時,我們有一個生產環境的統一小程序、H5 監控大盤。

演進之路

接下來我會講一下,關於釘釘前端監控,我們是如何對系統進行演進,拿到一個不錯的結果。

image.png

考慮到有很多小夥伴們不是搞前端監控的,所以這邊我會先講一些基礎知識來展開如何設計一個前端監控系統。

我們來看一下上面這段代碼,const 創建一個對象,然後 foot.a.b = c。可以看到這是段非常經典的 NPE 代碼,就是 null point exception,在前端代碼中非常容易出現。這邊會拋出一個錯誤:** Uncaught TypeError: Cannot set property 'b' of undefined**。

對於這樣一個錯誤,在用戶側發生之後,我們的前端監控系統是怎樣去捕獲這個錯誤,並且在一分鐘之內發現?我們來看一下一傳統的做法是怎麼做的:

  • 首先寫一個前端監控 SDK,用於進行數據採集
  • 選型一個通知方案,將這條前端日誌通知到服務端

我這邊演示的是用 image 標籤,創建一個 image 標籤,設置它的 src 指向對應的日誌服務器來發送對應的日誌。

我們對錯誤的採集採用的是 window.onerror 來捕獲全局錯誤。然後將捕獲的錯誤通過創建 image 標籤形式發送到上圖右側的前端監控服務端。

如上的代碼只是一個偽代碼演示,我寫的比較簡單。

對於一個傳統的基於日誌分析的監控系統,你首先要知道這條日誌到底是來自哪裡,所以我們對每一條日誌在前端採集的時候,都有一個應用 id,姑且我這邊稱之為 spmId ,通過 spmId 來標識日誌源,然後將這條日誌存儲到對應的監控服務端,這樣就完成了一個非常簡單的從前到後的一個鏈路。

日誌發生、採集,然後再到存儲的一個閉環,非常簡單。其實見微知著,看到這麼一個簡單的實現,再把日志類型進行豐富,採集和存儲做的強大一點,基本上就可以去搭建一個比較簡單的前端監控系統了。

image.png

一般而言,一個簡單的前端監控分析系統需要包含如下三個維度:

  • 第 1 個是穩定性相關的 js error
  • 第 2 個是性能相關的 performance
  • 第 3 個是 api 成功率相關的

在監控平臺,我們需要做一些日誌存儲,將監控日誌提供給可視化平臺服務器,通過提供一些 API 服務就可以畫出上面這樣一個圖。比如第 1 個是接口成功率。

我覺得在技術選型上面,對於很多稍微有點 Node 或者服務端基礎的前端同學來說,基本上能做出一個簡單的 Demo。然而,這樣一個看似功能很完備的系統,對於做前端監控來說,有沒有什麼問題?是不是能夠滿足釘釘這樣一個億級流量平臺的監控需求?

image.png

上圖左邊展示的是我們的開發人員接入前端監控的過程,包括開發階段、測試階段、上線階段。 在前端監控推行的過程中,我們要求所有的開發人員在應用迭代上線後,要主動觀察監控大盤至少 30 分鐘,觀察三個指標:

  1. js error
  2. performance
  3. api 成功率

對於目前我們 100 多個前端同學的團隊規模來說,人力成本是 100 乘以 30 分鐘,同時對於釘釘這個企業級產品而言,我們對線上的穩定性要求是非常高的,對線上故障容忍度極低,因此還要求每日對線上應用進行巡檢,因此人力成本非常高。

從開發人員的體驗角度看,一個開發人員查看監控的時候:第 1 個他會去可視化分析平臺上去看有沒有錯誤日誌。這邊有一個非常重要的點,就是說我們監控分析平臺看到的日誌,是不是"前端頁面"的日誌?

不一定是。為什麼?因為對於用戶來說,它不僅僅是打開了前端頁面,這個前端頁面背後還有容器的 webview、應用容器、運營商等。

舉個例子,我們一個頁面可以在微信的容器裡面打開、可以在頭條的容器裡面打開、可以在釘釘容器裡面打開。所以你採集的日誌源不僅僅是一個前端頁面,還有容器的 webview,同時我們還會面臨很多的運營商。比如說我們經常看到前端頁面裡插了一段廣告,然後我們還有一些手機的製造商,比如vivo、華為等,也會在我們的頁面裡面插入相關的腳本。所以監控分析平臺採集到的日誌不僅僅是前端日誌,他採集到的範圍實際上是前端頁面對應的用戶終端日誌。

一般我們會碰到如下三種干擾日誌:

  • 第 1 個是第三方腳本注入
  • 第 2 個是容器腳本的注入
  • 第 3 個是由手機製造商腳本注入

image.png

舉個例子,如上是我們線上的一個應用,大概 js error 率是 0.08%, 對於釘釘這樣的體量來說,這個錯誤率影響用戶的數量已經非常大了。

我們來看一下它對應的錯誤實際上是什麼?Script error,WeixinJSBridge is not defined, toutiaoJSBridge is not defined, 20 vivoNewsDetailPage,這些東西從錯誤信息基本上可以判斷跟業務錯誤基本沒啥關係。

所以我們可以得到第 1 個結論,就是前端監控產生的一部分錯誤實際上跟業務無關,這個可能跟很多人的認知是相悖的。

image.png

我們再來看一個問題,左圖是我們桌面端的發佈曲線,釘釘是國內甚至是全球為數不多的非常重桌面端的平臺。釘釘桌面端基本上是一個禮拜或者兩個禮拜一個迭代,由於桌面端的前端代碼是採用離線包的形式,因此代碼的更新修復是比較困難的,對前端穩定性的要求非常的高。

對於我們今天的桌面端而言,已經有 100 多個線上發佈版本了,這麼多的版本上報的日誌採用的是同一個應用id,我們如何去做分層監控,線上流量的不均如何做好分層監控,避免小流量的發版監控被淹沒?

這些問題在釘釘的業務場景是經常碰到的,我們的監控顆粒度需要和前端的發版相適應,並且監控的日誌需要支持更多的維度。比如說以應用和發佈版本,這兩個變量為單位進行監控。

image.png

我們再看一個案例,釘釘有幾百個前端應用,每個應用報警 1 次就非常誇張了,基本上一天報警群就有 500 多條日誌,刷屏現象非常嚴重,而且很多錯誤是線上的長尾錯誤。也就是它雖然有報警,但是不需要去修改等等。長尾錯誤出現的原因是我雖然修復了問題,但是用戶那邊不一定完全訪問的是最新的版本。

所以結論 3 就是我們監控運營的人力成本非常高,對於前端監控的要求不僅僅是要報警報出來,還需要你的報警是直觀的、實時的,同時要支持一些短時關閉和錯誤過濾等等手段。

看完上面這三個案例後,我們來看一下究竟該如何設計一個能夠服務 3 億體量的監控系統。

image.png

首先,我們先界定監控設計的目標,釘釘企業級前端監控需要做到的事情是: 一分鐘感知、5 分鐘定位、10 分鐘恢復。姑且稱這個監控系統為 2.0 系統。

image.png

我們對於前端監控 2.0 在 1.0 的基礎上定義瞭如下的能力水位。

第 1 個是要貼近實際業務,降低人力運營成本、業務方能夠低成本介入。同時對於報警體系,要求做到快報警、準報警,並且支持自定義報警。我們內部定了一個基準線,就是前端監控精度必須達到 90% 以上,人力成本必須減少 20 分鐘每一個人,並且報警和大盤需要能夠支持自定義配置。

image.png

上圖是整體的監控的組件編排方案。左邊是一個圖例,藍色部分代表的是 1.0 的監控組件,墨綠色的部分代表 2.0新增的監控組件。

自定義採集

第 1 個在日誌採集端,除了採集常規的業務數據和監控數據之外,支持自定義採集。

分析智能化

分析智能化這一塊增加了分析可自定義的能力。

報警實時化

在報警實時化這一塊,增加了線上1分鐘報警和5分鐘定位的要求。

最關鍵的技術實現

image.png

同樣,藍色部分是原有的 1.0 的一個體系,墨綠色部分是我們新增的體系。我們會發現在日誌採集在和日誌消費端,我們增加了一個模塊叫做日誌雙寫

一份日誌被兩個系統所消費,一份系統用於實時去報警,一份系統用於去做分析:

  • 服務器拿到日誌後,一塊去做存儲分析以便做一些監控報表服務;
  • 第二塊引入了日誌分鐘計算系統去做實時的報警。

很多同學會覺得日誌雙寫其實是一個非常大的系統的浪費,一份日誌被兩個系統所消費了。實際上釘釘前端監控藉助了阿里非常成熟的日誌消費系統和基礎設施。通過日誌分發兩路被快速消費,讓分鐘計算系統在整個監控體系裡面的編排是前置的,達到 1 分鐘報警的要求,這是我們在這一塊裡面最核心的一個技術思路。

在上圖的紫色虛線下方,是我們的用戶視角。用戶側觸碰到的是兩塊,第 1 塊是前端監控 SDK,我們有 H5 和小程序的 SDK,第二塊是平臺,包括分析平臺和報警平臺。

真實案例

image.png

我們來看一個真實的案例。用戶碰到了兩個 js error 。這兩個 js error 都是前端經典的 NPE 錯誤。

第 1 個是發生在 iPad + 百度瀏覽器。第 2 個是發生在安卓 + 頭條 webview ,結果我們會發現,我們客戶端上報過來的錯誤有兩種:

  1. 真實錯誤: Uncaught TypeError: Cannot set property 'b' of undefined。
  2. 宿主注入的很多幹擾信息,比如說百度瀏覽器會注入 MyAppHrefLink is not defined。

可能很多同學沒有觀察過。我們是仔細去排查過的。百度瀏覽器會注入 MyAppHrefLink is not defined。頭條的也會去注入一些頭條 jsBridge 。

日誌到達服務端後,我們先對日誌進行清洗,把所有宿主的干擾日誌都過濾掉,確保我們的報警系統是消費的真正的業務發生的日誌錯誤。這是黃色區域的第一個模塊: 日誌清洗

image.png

接下來我們進行日誌分組,將應用 A spmId=A 和應用 B 的日誌進行分組,通過應用標識 A 和 B 進行分組。將過濾過來的日誌進行實時計算。

經過這一步後,再將日誌流轉到報警指標項進行實時計算,這個報警規則引擎下發相關的指令到 Map Reduce 對應的機器上去做一些處理。

比如 JS Error 失敗率= JS Error 日誌條數除以 PV 條數。當對日誌進行計算的結果大於 6%,則進行釘釘群報警 ,當失敗率大於 15% 則進行短信報警。

釘釘前端監控 2.0

監控日誌

image.png

通過將同樣的流程應用到各個不同的指標項,比如 api 成功率、js error 失敗率、pv 數據等我們就可以在分鐘計算系統搭建出一套滿足 1 分鐘感知的監控系統。

報警系統架構

image.png

關於報警系統,上圖我們阿里研發事業部那邊的一個非常經典的監控系統,有興趣的同學可以在 infoQ 上搜索 sunfire 看到更詳細的架構介紹,這裡不做過多展開。

整體日誌架構總結

image.png

基本上這就是我今天想要分享的,釘釘前端監控在從 1.0 演進到 2.0 的過程中,我們是如何思考和如何落地的。這邊的話我給大家稍微簡單總結一下:

  1. 最關鍵的技術思路是將日誌報警組件的編排進行前置,我們的實現是採用日誌雙寫到分析系統和報警系統。
  2. 在報警平臺支持報警規則引擎,真正做到報警自定義、報警可分級等。
  3. 對於前端而言,我們不僅僅是前端頁面,我們更多的面對的是用戶終端。

Leave a Reply

Your email address will not be published. Required fields are marked *