去哪兒網異常統計分析實踐 —— Heimdall
作者介紹
沙丹丹
17 年加入去哪兒,致力於提升研發和測試人員的效率,在 CICD、測試工具領域有一定的經驗,目前在基礎架構—基礎平臺團隊負責測試工具的開發和維護工作,包括接口自動化測試、全鏈路壓測、異常日誌統計等。
一、背景
隨着業務發展和微服務架構的普及,企業內微服務拆分粒度越來越細,服務間調用關係錯綜複雜。對於一些複雜的,比如機票和酒店售賣業務場景,可能動輒涉及上百個應用,當某個系統發生異常時會導致多個服務受到影響。此時 APM 系統就派上了用場,監控(Metrics)、調用鏈(Tracing)、日誌(Logging)幫助業務同學快速定位問題。普通的業務監控報警能起到快速發現問題的作用,但具體 case 的排查還需要研發人員通過異常棧信息來分析,比如數據庫連接異常、空指針等等。
去哪兒網很早就有了監控系統 Watcher,能夠起到快速提醒業務響應異常的作用,然後開發同學排查是接到報警的系統本身的問題還是下游依賴的系統的問題,如果是下游系統的問題,就要這樣一層層地找下去,有時候定位問題時間會比較長。當某個系統出現問題時最根本的表現就是產生異常,如果能直接提示開發同學系統產生了新的異常,或者異常量上漲了,就能夠大大縮短開發同學排查問題的時間,做到快速恢復故障。
去哪兒網有一套完整的日誌收集和查看體系,首先應用通過日誌打印框架將日誌打印到本地 log 文件,機器上默認安裝日誌收集的 agent,將日誌內容通過 kafka 上報,再通過 ELK 提供日誌存儲和查詢的能力。如果能夠自動地、快速地識別異常,並將日誌堆棧內容直接提醒給研發同學,將會大大提高解決問題的效率,甚至防患於未然。因此,異常統計分析系統——Heimdall 應運而生,主要目標如下:
1. 分鐘級別的異常統計
2. 發佈過程中展示同比環比
3. 支持添加監控報警
4. 支持用戶自定義時間範圍查詢
5. 能夠展示異常棧
6. 支持應用和機器級別的異常統計
整體的演進包含兩個階段:
1. 基於實時日誌收集的建設
2. 基於基礎組件的改造,以在業務服務端直接攔截並上報異常的方式進行了改進
以下分階段進行闡述。
二、實踐框架
1. 階段一:基於實時日誌收集的建設
1.1 技術棧
實時日誌收集 kafka + 大衆點評開源工具 CAT+FLINK+Heimdall 平臺
1.2 架構圖
圖 2-1 架構圖
1.3 核心模塊介紹
1.3.1 clog 模塊
clog 模塊主要負責異常棧的接收、存儲和查詢。消費 kafka 消息接收日誌,解析出 ERROR 級別的日誌,並將其關聯的應用、trace、日誌詳情、時間戳等相關信息一併存儲起來,再將轉換成統一格式的日誌以 kafka 消息的形式發出,待 flink 任務消費並做進一步解析。clog 的存儲結構分爲三層:本地磁盤(臨時存儲)+ES(做文件索引)+HDFS(持久存儲)。這樣的存儲結構保證了熱數據的快速查詢和冷數據的持久存儲。
存儲:首先全部的實時日誌都會按 bu 發送到 kafka,clog 以二進制流的方式消費此 kafka 消息,然後經過 MessagePackDecoder 進行解析,解析出日誌級別、AppCode、traceId 等信息,組裝成固定格式的日誌內容。再由 BlockProcessor 爲每個 DataBlock 構造出索引用於查詢,EsIndexManager 將索引保存到 ES 中,數據部分 DataBlock 保存到本地文件,定時轉存到 HDFS 做持久存儲。
block position 格式定義:ip - 保留天數 - 時間( yyMMddHHmmss )- offset,例如:10.xx.xx.xx-7-20211110185535-4625901837。
每個應用 5s 一個 block,一個 block 最大 8M,當本地磁盤空間利用率達到 75%,就上傳到 HDFS。
圖 2-2 存儲架構圖
查詢:查詢異常詳情時先查詢 ES,得到索引,根據索引判斷本機是否存在相應的 blockPosition,如果是本機 ip 並且本地磁盤中存在,直接從磁盤讀取數據返回,若是本機 ip 但磁盤中不存在,根據文件名、position、seconds 等信息查詢 HDFS 。如果不是本機 ip ,則向索引中的 ip 發起遠程 HTTP 請求,轉化成對應 ip 的查詢。
圖 2-3 查詢流程圖
1.3.2 flink 任務模塊
flink 任務主要用來進行異常信息的解析計算處理,將異常類型、應用、機器等相關信息提取出來,按分鐘級別做次數統計,並打印異常指標到監控系統。
1.4 難點分析
(1)容器化後日志收集方式改變—容器化後的應用統計不到數據
近兩年去哪兒在進行 KVM 到容器化的遷移,兩者技術差異還是比較大的,日誌收集的方式也進行了徹底的改變,包括 kafka 消息的形式和格式都變化較大,原有異常日誌統計架構已經完全不能滿足。因此,我們對異常統計分析的系統架構進行了調整,從源頭異常日誌收集到統計邏輯都做了重大改進。下面介紹改進後的架構。
(2)實時日誌存在延遲—flink 數據統計不準確
由於日誌收集屬於非核心流程,當應用的日誌量較大的時候,實時日誌收集存在延遲的情況,有些日誌的延遲甚至超過了 1 個小時。在異常日誌統計時使用了 flink 的滾動窗口來進行計算,由於日誌的亂序和部分日誌延遲,導致這些日誌被丟棄,造成統計數據不準確,誤差將近 10%。
(3)實時日誌收集的日誌量巨大—消耗資源大
由於實時收集的日誌本身是不過濾日誌級別的,大量的非 ERROR 日誌也會被收集。從使用角度上,這些非 ERROR 的日誌並不是用戶關心或者期望看到的數據,納入異常日誌統計並沒有什麼用反而會造成干擾,所以需要從大量日誌中過濾掉非 ERROR 日誌,這會耗費大量的計算資源。還有一部分是多個系統間傳遞數據,消耗在了跨系統傳遞無用信息的寬帶上。
(4)非全量應用都有實時日誌收集—有些應用不能使用此功能
由於公司內整套實時日誌收集是 ELK,成本比較高,所以只有部分核心應用開通了實時日誌收集,未開通的應用就沒辦進行異常日誌統計和監控。
(5)未考慮環境隔離—摻雜仿真環境數據
公司內部根據不同使用目的和途徑,存在多種不同的環境,包括 beta、仿真、灰度和線上,實時日誌收集沒有對環境進行區分,仿真、灰度和線上的日誌都會被統計,而事實上仿真環境屬於測試範疇,會對統計結果造成干擾,尤其是當短時間內進行大量自動化測試且引發異常的情況發生時,干擾會更加顯著。
2. 階段二:基於基礎組件的改造
2.1 改進目標
(1)支持容器的異常日誌統計。
(2)解決統計不準確問題。
(3)降低資源成本。
(4)應用範圍要擴展到全司 java 應用。
(5)可以按照環境類型維度進行過濾。
2.2 改進策略
(1)將數據源從實時日誌收集改成在業務服務端的基礎組件進行攔截和上報異常。
(2)將從全量級別的日誌中篩選異常日誌改成直接在源頭過濾,只上報帶異常棧的日誌。
(3)通過基礎組件在業務服務端直接做好結構化,並做初步聚合(按異常類型做聚合,同種異常次數聚合,異常棧詳情采樣),減少冗餘數據傳輸的資源消耗,kafka 集羣 partition 從 60 個降低到 14 個,異常日誌每秒消息量從 486K 降低到 106K。
(4)廢棄 flink 任務(之前使用 flink 主要是做日誌文本解析,數據源變更後,就不再需要了),開發新的統計服務。
圖 2-4 改進後的架構圖
2.3 核心模塊介紹
2.3.1 logger-spi
負責在客戶端進行日誌採集、過濾、聚合、採樣、上報。通過 agent 對 logger 進行插樁,並過濾出帶異常棧的日誌,然後將異常日誌按照異常類型進行初步聚合,將 1min 內同一種類型的異常進行累加計數,並且對異常日誌詳情進行採樣,最終將數據通過 kafka 消息上報。同時爲了避免對服務造成過多損耗,當佔用的內存達到限額時會直接上報到 kafka。
我們將異常日誌分爲了業務異常( BusinessError )和系統異常。業務異常是指沒有異常棧的,和業務流程相關的異常,比如:"沒有該目的地的航班" 等;系統異常是指沒有系統業務含義的,帶堆棧信息的異常。目前我們只關心繫統異常,業務異常是直接過濾掉的。
上報的數據結構如下:
{
"ip": "xx.xx.xx.xx",
"sendTime": 1634802724460,
"host": "l-xxxxxxx",
"appCode": "xxx",
"envName": "proda",
"exType": "com.xx.xx.xx.xx.xxException",
"count": 100,
"details": [
{
"level": "ERROR",
"fileName": "/home/xxx/logs/xx.log",
"content": "2021-10-21.15:52:04.040 INFO [Dubbo-thread-57] ProxyUtil.proxyConnection:38 [proxyConnection] QTraceId[xxx_211021.155203.xx.xx.xx.xx.6786.xxx_1]-QSpanId[1.7.1.5.1.1.3.1.13.1.3.1] db proxy error",
"timestamp": 1634802724458,
"traceId": "xxx_211021.155203.xx.xx.xx.xx.6786.xxx_1",
"stackTrace": "com.qunar.xxx.QProcessException: api|BookingRule Error: [預定規則錯誤, 最大可預訂間數<=0, maxRoomNumber=0]
at com.qunar.xxx.xxx(xxx.java:197)
at com.qunar.xxx.xxx(xxx.java:117)
...
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)"
}
]
}
模塊詳細架構圖:
圖 2-5 logger-spi 模塊詳細架構圖
**2.3.2 **heimdall-statistic
接收客戶端上報上來的異常日誌初步聚合結果,在內存中按分鐘進行統計並暫存儲統計結果,並定時更新到 hbase 中,更新時先從 hbase 中查詢出該應用、該分鐘原有的異常數據,再與內存中的數據疊加,最後更新到 hbase 中。這種計算、統計方式與 kafka 消息到達的順序無關,不管消息有沒有延遲,只要消息沒丟,就都能統計進去,從而保證統計數據和實際不會有偏差。統計數據包含以下四個維度:
- 每分鐘的異常個數
- 每分鐘每種類型的異常個數
- 每分鐘每個機器的異常個數
- 每分鐘每個機器每種異常類型的異常個數
三、效果展示
1. 指定時間段的異常類型及個數統計環比
圖 3-1 某個應用指定時間段內的異常數據示例圖
- 可查看異常棧詳情及 trace 信息
圖 3-2 某種異常的詳細異常棧信息和 trace 信息示例圖
四、應用場景
基於我們提供的異常統計、分析和異常詳情查看等基本能力,公司內部孵化了一些工具平臺自動幫助業務系統定期進行服務治理和實時觀測系統健康狀態的工具,異常統計分析作爲其中的一個重要數據源和評價、分析指標。下面列舉了幾個比較常用的場景:
1. 服務治理
將 AppCode 和 owner 對應起來,每週發郵件提示 owner 負責的系統的異常量,並制定規範、定義異常級別,比如 P1 的異常必須修復等,便於 owner 持續關注自身系統的健康狀況。
圖 4-1 發送給業務負責人的異常日誌日報示例圖
2. 與發佈系統集成
在發佈過程中,發佈系統會調用 Heimdall 接口獲取該應用及其上下游系統的異常量變化情況即異常環比,便於在發佈過程中有問題及時發現、及時終止。
3. 異常檢測報警
不僅是在發佈過程中要關注異常量,在平時也會出現各種各樣的突發情況,比如硬件故障、中間件故障、數據庫故障、攻防演練等。因此基於 Heimdall 的基礎數據,開發了實時地根據異常統計數據自動識別新增異常類型和異常數量環比上漲,並及時提醒給研發人員的工具,想要開通的應用可以自定義配置報警規則,比如環比上漲多少要報警等。能夠進一步提升系統穩定性和研發人員排查問題的效率。
4. 故障根因分析
在發生故障時,很多情況下系統都會表現出異常,比如下游接口不通、數據庫連接異常、空指針等等,如果系統中大量新增了某種異常,或者某種異常的環比突然增高,異常變化和指標變化的時間點相匹配,那麼很可能和這些異常相關。故障根因分析效果數據:22 年下半年故障處理超時率 60.9%,23 年 Q1 故障處理超時率降到 38.8%(異常日誌分析是其中一個分析項)。
圖 4-2 根因分析平臺定位問題示例圖
五、 總結展望
目前 heimdall 系統已經接入 1300 + 應用,成爲了研發質量中的一個重要指標,研發人員也養成了關注系統異常情況的習慣,爲公司業務穩定發展提供技術保障。
一個系統在誕生的時候基本上都會有一些沒考慮到的點,並且隨着周邊環境的變化,原有的設計也會不滿足,優秀的系統不是一成不變的,而是慢慢打磨、優化、改進、完善才形成的,每個時期和階段都有它的價值。同時也要敢於突破原有設計的束縛,取其精華去其糟粕。
異常量統計數據和異常堆棧的作用遠不止於此,未來我們還會將 heimdall 的數據應用於中測試領域,將 heimdall 中已存在的異常作爲基準,從 beta 環境的異常中過濾掉線上已有的異常,再將剩餘的異常主動提示給開發或 QA 同學,提升聯調測試過程中排查問題的效率,未來的使用場景還會更多!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PQcodjSPGnBQ061Iq8q9IQ