有貨電商技術架構上採用的是先後端分離,前端是主要以業務展現和接口聚合爲主,擁有本身的 BFF (Backend For Frontend),以 nodejs 爲核心;後端以提供較小的業務數據接口,業務服務實現爲主,以 java 技術體系爲核心。在實際應用場景下,先後端應用對系統性能的關注點是不同。所以,前端團隊須要跟據自身的需求,來搭建本身的 APM 系統。html
對前端團隊來講,用戶體驗相當重要,而頁面的打開速度就是用戶能感知因素中最重要的一環。前端團隊對 APM 的需求就是要儘量地收集與頁面打開速度有關的因素。通過對業務和技術的討論,咱們認爲如下方面影響了頁面的加載速度:前端
在前端頁面加載是用戶感知的第一層。咱們使用如下指標:java
除了加載時間,咱們還會把前端 js 運行過程當中出現的錯誤上報。這樣,咱們就可以及時發現問題,快速修復上線,使公司損失最小化。node
這塊咱們主要關注頁面請求到頁面響應完成的時間:req-res-time,這個時間可以表明咱們系統的響應速度,因此這個指標能衡量當時系統的性能。mysql
此外,還會針對 http 狀態碼這個值也進行記錄,這樣就能夠知道哪些路由有問題,這樣就能夠經過狀態碼的狀況獲得系統的健康程度。webpack
這塊咱們會對每一個接口都監控其調用時間:api-time。同時咱們還會針對每一次請求生成一個惟一 ID,對這個請求所調用的 api 進行標識,這樣咱們就能分析出,頁面調用的接口數,每一個接口調用的時間,接口的調用順序等,這些數據對後端的壓測和服務治理會很是有用。web
同時,對 api 的響應狀態碼進行監控,方便及時瞭解後端接口的基本狀況。ajax
這塊包括系統 cpu 和 memory 的使用狀況作了收集,方便咱們知道機器的狀況。sql
針對這四個方面,咱們設定了這 10 項指標,經過這些指標,咱們這能全方位對咱們網站業務的速度和穩定性進行了解,方便之後優化。數據庫
Web-APM 在總體架構設計上,分紅了六個部分,以下圖所示,包括 client,service , collector, storage, api ,ui。箭頭表明數據的流向。其中,client 和 service 是收集指標併發送指標,而 collector 做用是聚集指標,過濾數據,存入 storage。 stoarge 的存在,咱們是但願能保存一段時間數據狀況,方便過後進行查找和分析。 而 api 則是對 storage 的數據對外提供一個接口,方便監控和分析;ui 是提供一個界面,方便使用者進行查看。
從實現角度來看,咱們仍是比較功利的,即採用咱們本身熟悉的技術,並無上來就使用 ELK Stack,這其中是有緣由的:
因而咱們跟據本身的需求和總體架構,在實現系統角度上,劃分紅了多個層,每一個層有各自的選擇,以下圖所示:
注意:圖中灰色的部分爲將來規劃。
從圖中能夠看出,Web-APM 系統實現上分紅了 4 個層,分別是採集層,收集層,存儲層和監控層,每一層咱們都選擇了合適的技術來實現。
採集層對應着咱們的需求,也分紅了四塊,包括前端層(yas.js), 業務層(yohobuy-node),數據層(yohonode-lib),系統層(yohonode-lib)。不一樣的層採集不一樣的數據,最終數據發送到收集層。
收集數據時,解決如下問題:
咱們參考了這篇文檔,思路就是計算首屏基線高度之上的全部圖片元素 onload 以後的時間。
利用 window.error 接口來實現,self.writeError 裏面就是咱們本身的上報邏輯。
數據採集時,咱們注意到數據不一致的問題。如路由。電商的頁面不少,最多可能就商品詳情頁。從前端的角度來看商品詳情頁,每一個商品詳情頁就是不一樣的 path,如 https://www.yohobuy.com/product/51768088.html
。但從後端來看就不過是一個參數 :id 而已,如 https://www.yohobuy.com/product/:id.html
。咱們作監控的時候,不可能針對一個商品的連接進行監控,這樣是沒有什麼用的,咱們但願對商品詳細頁這一類的頁面進行監控。 若是之前端的 path 來進行數據統計就只能統計到單個頁面的問題,不能統計到商品詳情頁這一類的狀況。固然能夠在進行數據聚集的時候,進行正則匹配。這對咱們來講不太現實,由於咱們網站信息架構調整了屢次,路由也已經調整屢次,在另外一個地方進行正則就意味着,要在另外一個地方維護一張正則映射表。咱們但願的是就一個地方維護路由。
這個問題咱們進行了屢次討論,在技術的可行性下,選擇了一個技術方案,在每一個頁面中,寫入一個全局變量,把這個頁面的後端路由的 md5 寫入到頁面中,這樣只要這個頁面路由不變,這個值就一直不變,發送監控數據就把這個路由值也帶上,這樣先後端的數據狀況就能經過路 由對應起來,這樣就能更方便的統計數據。
有貨前端項目都是以 nodejs 爲核心創建的技術棧。在 nodejs 中,一直缺較好的技術手段對異步進行跟蹤和標識。目前來看 async_hooks 技術應該是比較好的候選方案。但在對咱們來講,該技術不是很合適。由於咱們關注點會更高一點,只是針對業務流程進行跟蹤,而不是對每個異步進行細緻的分析。在考慮到業務實際狀況和技術實現,咱們選擇了基於 reqId 進行業務流程跟蹤的方案。該方案時序圖以下:
在頁面請求過來時,咱們針對這個請求生成 reqId,而且在調用後端 api 時,會帶上 reqId 生成的上下文 ctx。最後返回頁面時,把 reqId 寫入用戶的 cookie 中。同一個頁面 ajax 發送請求時,就會去判斷是否有這個 reqId,這樣就能區分是頁面上的 ajax 的請求,仍是別的方式形成的請求,同時也能統計出頁面上調用 ajax 請求的個數。
收集層,主要定義一下從採集端過來的數據的形式(influxdb),以什麼協議傳輸(http)。
對打入的監控數據進行一個緩衝(buffer),依據條件過濾(filter)出咱們須要的數據,把數據形式轉化(tranform)成咱們想要的形式,洗好的數據定時寫入存儲層中。
在存儲層,咱們分爲在線存儲和離線存儲。在線存儲是與監控有關須要實時交互的數據,使用 influxdb 時序數據庫。在寫入時,咱們會把數據再精簡,把最簡單關鍵數據寫入 influxdb 中,如 http-status, api-status, process, 方便下一層監控層使用。
influxdb 的使用中,咱們也碰到了問題。例如:寫入數據時 tag 過多,致使查詢數據緩慢,咱們就精簡數據;爲提升 influxdb 性能,會作一個隊列,批量寫入數據。
離線存儲,咱們選擇是 mysql。在寫入時,咱們對數據進行過濾,主要保存錯誤,異常和慢路由。如前端和後端發生的錯誤(包括堆棧),還有哪些路由的請求時間長於 2000ms,這樣方便咱們進行離線地分析查看和統計。
還有,因爲咱們項目的特色,對離線儲存不是要求一直保存,咱們會定時對 mysql 進行整理和清除,當前咱們就只保留最近 7 天的數據,這樣咱們離線儲存的壓力就比較小。
監控層,也是分紅兩部分,一個部分爲 grafana ,利用 grafana 和 influxdb 配合的提醒功能,能對咱們的線上環境經過短信和郵件實時進行提醒。
另外一部分是咱們的 ci 系統,包括一個監控面板,用於查看詳細的錯誤狀況和路由狀況。特別對於前端,咱們發佈的代碼都是通過 webpack 打包的代碼,直接去找錯誤行列確定是找不到的,所以咱們生打包生產代碼時,會生成 source-map 文件,ci 查看前端腳本錯誤,會去解析 source-map, 拿到出錯代碼的先後 20 行,這樣能方便地定位前端的錯誤。下圖爲解析 source-map 的相關代碼和前端的展現結果。
ci 天天都會去 mysql 查看路由的狀況和異常的狀況,生成統計報表,郵件給前端團隊。
當前 agent 代碼,前端代碼,爲了知足功能須要,引入方式是將壓縮後的 agent 代碼直接插入到佈局模板的 head 中。配置項目是在壓縮時就寫入定值,這樣能減小項目接入的複雜度。
對於服務器端代碼的接入,咱們當前沒有采用獨立進程的方式進行部署,這是由於使用者和維護者都是同一個團隊,還有監控這塊對性能影響並非很大。因此咱們接入方式大部分直接引入一個獨立的包就能夠完成工做,少部分的代碼直接傳入 express 對象代理接口和監聽事件,就能拿到數據,作到業務無感知。
收集層咱們是獨立部署的一臺服務器,這樣會更方便。並且咱們設計成收集層掛了,也不會影響咱們採集層的工做。
下圖是系統的調用狀況:
當前,有貨 Web-APM 基礎的功能已經完成,已經在有貨 pc 站和 h5 站項目進行部署,進行監控數據的上報。經過一段時間的使用,咱們解決了多數因爲字段未定義形成的系統問題,使咱們的系統更加穩定;前端咱們也針對狀況有選擇性地進行了頁面的優化。詳細的優化方案,請看這篇 有貨移動WEB端性能優化探索實踐2。
在將來,咱們會對 Web-APM 進行如下方面優化:
以上就是咱們有貨前端團隊 Web-APM 的實踐分享,歡迎你們批評指正。