根因分析初探:一種報警聚類算法在業務系統的落地實施

背景

衆所周知,日誌是記錄應用程序運行狀態的一種重要工具,在業務服務中,日誌更是十分重要。一般狀況下,日誌主要是記錄關鍵執行點、程序執行錯誤時的現場信息等。系統出現故障時,運維人員通常先查看錯誤日誌,定位故障緣由。當業務流量小、邏輯複雜度低時,應用出現故障時錯誤日誌通常較少,運維人員通常可以根據錯誤日誌迅速定位到問題。可是,隨着業務邏輯的迭代,系統接入的依賴服務不斷增多,引入的組件不斷增多,當系統出現故障時(如Bug被觸發、依賴服務超時等等),錯誤日誌的量級會急劇增長。極端狀況下甚至出現「瘋狂報錯」的現象,這時候錯誤日誌的內容會存在相互掩埋、相互影響的問題,運維人員面對報錯一時難以理清邏輯,有時甚至顧此失彼,沒能第一時間解決最核心的問題。java

錯誤日誌是系統報警的一種,實際生產中,運維人員可以收到的報警信息多種多樣。若是在報警流出現的時候,經過處理程序,將報警進行聚類,整理出一段時間內的報警摘要,那麼運維人員就能夠在摘要信息的幫助下,先對當前的故障有一個大體的輪廓,再結合技術知識與業務知識定位故障的根本緣由。算法

圍繞上面描述的問題,以及對於報警聚類處理的分析假設,本文主要作了如下事情:後端

  1. 選定聚類算法,簡單描述了算法的基本原理,並給出了針對報警日誌聚類的一種具體的實現方案。
  2. 在分佈式業務服務的系統下構造了三種不一樣實驗場景,驗證了算法的效果,而且對算法的不足進行分析闡述。

目標

對一段時間內的報警進行聚類處理,將具備相同根因的報警概括爲可以涵蓋報警內容的泛化報警(Generalized Alarms),最終造成僅有幾條泛化報警的報警摘要。以下圖1所示意。api

圖1

圖1

咱們但願這些泛化報警既要具備很強的歸納性,同時儘量地保留細節。這樣運維人員在收到報警時,便能快速定位到故障的大體方向,從而提升故障排查的效率。緩存

設計

如圖2所示,異常報警根因分析的設計大體分爲四個部分:收集報警信息、提取報警信息的關鍵特徵、聚類處理、展現報警摘要。安全

圖2

圖2

算法選擇

聚類算法採用論文「Clustering Intrusion Detection Alarms to Support Root Cause Analysis [KLAUS JULISCH, 2002]」中描述的根因分析算法。該算法基於一個假設:將報警日誌集羣通過泛化,獲得的泛化報警可以表示報警集羣的主要特徵。如下面的例子來講明,有以下的幾條報警日誌:bash

server_room_a-biz_tag-online02 Thrift get deal ProductType deal error.
server_room_b-biz_tag-offline01 Pigeon query deal info error.
server_room_a-biz_tag-offline01 Http query deal info error.
server_room_a-biz_tag-online01 Thrift query deal info error.
server_room_b-biz_tag-offline02 Thrift get deal ProductType deal error.

咱們能夠將這幾條報警抽象爲:「所有服務器 網絡調用 故障」,該泛化報警包含的範圍較廣;也能夠抽象爲:「server_room_a服務器 網絡調用 產品信息獲取失敗」和「server_room_b服務器 RPC 獲取產品類型信息失敗」,此時包含的範圍較小。固然也能夠用其餘層次的抽象來表達這個報警集羣。服務器

咱們能夠觀察到,抽象層次越高,細節越少,可是它能包含的範圍就越大;反之,抽象層次越低,則可能無用信息越多,包含的範圍就越小。網絡

這種抽象的層次關係能夠用一些有向無環圖(DAG)來表達,如圖3所示:運維

圖3 泛化層次結構示例

圖3 泛化層次結構示例

爲了肯定報警聚類泛化的程度,咱們須要先了解一些定義:

  • 屬性(Attribute):構成報警日誌的某一類信息,如機器、環境、時間等,文中用Ai表示。
  • 值域(Domain):屬性Ai的域(即取值範圍),文中用Dom(Ai)表示。
  • 泛化層次結構(Generalization Hierarchy):對於每一個Ai都有一個對應的泛化層次結構,文中用Gi表示。
  • 不類似度(Dissimilarity):定義爲d(a1, a2)。它接受兩個報警a一、a2做爲輸入,並返回一個數值量,表示這兩個報警不類似的程度。與類似度相反,當d(a1, a2)較小時,表示報警a1和報警a2類似。爲了計算不類似度,須要用戶定義泛化層次結構。

爲了計算d(a1, a2),咱們先定義兩個屬性的不類似度。令x一、x2爲某個屬性Ai的兩個不一樣的值,那麼x一、x2的不類似度爲:在泛化層次結構Gi中,經過一個公共點父節點p鏈接x一、x2的最短路徑長度。即d(x1, x2) := min{d(x1, p) + d(x2, p) | p ∈ Gi, x1 ⊴ p, x2 ⊴ p}。例如在圖3的泛化層次結構中,d(「Thrift」, 「Pigeon」) = d(「RPC」, 「Thrift」) + d(「RPC」, 「Pigeon」) = 1 + 1 = 2。

對於兩個報警a一、a2,其計算方式爲:

公式1

公式1

例如:a1 = (「server_room_b-biz_tag-offline02」, 「Thrift」), a2 = (「server_room_a-biz_tag-online01」, 「Pigeon」), 則d(a1, a2) = d(「server_room_b-biz_tag-offline02」, 「server_room_a-biz_tag-online01」) + d((「Thrift」, 「Pigeon」) = d(「server_room_b-biz_tag-offline02」, 「服務器」) + d(「server_room_a-biz_tag-online01」, 「服務器」) + d(「RPC」, 「Thrift」) + d(「RPC」, 「Pigeon」) = 2 + 2 + 1 + 1 = 6。

咱們用C表示報警集合,g是C的一個泛化表示,即知足∀ a ∈ C, a ⊴ g。以報警集合{「dx-trip-package-api02 Thrift get deal list error.」, 「dx-trip-package-api01 Thrift get deal list error.」}爲例,「dx服務器 thrift調用 獲取產品信息失敗」是一個泛化表示,「服務器 網絡調用 獲取產品信息失敗」也是一個泛化表示。對於某個報警聚類來講,咱們但願得到既可以涵蓋它的集合又有最具象化的表達的泛化表示。爲了解決這個問題,定義如下兩個指標:

公式2

公式2

H©值最小時對應的g,就是咱們要找的最適合的泛化表示,咱們稱g爲C的「覆蓋」(Cover)。

基於以上的概念,將報警日誌聚類問題定義爲:定義L爲一個日誌集合,min_size爲一個預設的常量,Gi(i = 1, 2, 3……n) 爲屬性Ai的泛化層次結構,目標是找到一個L的子集C,知足 |C| >= min_size,且H©值最小。min_size是用來控制抽象程度的,極端狀況下若是min_size與L集合的大小同樣,那麼咱們只能使用終極抽象了,而若是min_size = 1,則每一個報警日誌是它本身的抽象。找到一個聚類以後,咱們能夠去除這些元素,而後在L剩下的集合裏找其餘的聚類。

不幸的是,這是個NP徹底問題,所以論文提出了一種啓發式算法,該算法知足|C| >= min_size,使H©值儘可能小。

算法描述

  1. 算法假設全部的泛化層次結構Gi都是樹,這樣每一個報警集羣都有一個惟一的、最頂層的泛化結果。
  2. 將L定義爲一個原始的報警日誌集合,算法選擇一個屬性Ai,將L中全部報警的Ai值替換爲Gi中Ai的父值,經過這一操做不斷對報警進行泛化。
  3. 持續步驟2的操做,直到找到一個覆蓋報警數量大於min_size的泛化報警爲止。
  4. 輸出步驟3中找到的報警。

算法僞代碼以下所示:

輸入:報警日誌集合L,min_size,每一個屬性的泛化層次結構G1,......,Gn
輸出:全部符合條件的泛化報警
T := L;              // 將報警日誌集合保存至表T
for all alarms a in T do
    a[count] := 1;   // "count"屬性用於記錄a當前覆蓋的報警數量
while ∀a ∈ T : a[count] < min_size do {
    使用啓發算法選擇一個屬性Ai;
    for all alarms a in T do
        a[Ai] := parent of a[Ai] in Gi;
        while identical alarms a, a' exist do
            Set a[count] := a[count] + a'[count];
            delete a' from T;
}

其中第7行的啓發算法爲:

首先計算Ai對應的Fi
fi(v) := SELECT sum(count) FROM T WHERE Ai = v   // 統計在Ai屬性上值爲v的報警的數量
Fi := max{fi(v) | v ∈ Dom(Ai)}
選擇Fi值最小的屬性Ai

這裏的邏輯是:若是有一個報警a知足 a[count]>= min_size,那麼對於全部屬性Ai , 均能知足Fi >= fi(a[Ai]) >= min_size。反過來講,若是有一個屬性Ai的Fi值小於min_size,那麼a[count]就不可能大於min_size。因此選擇Fi值最小的屬性Ai進行泛化,有助於儘快達到聚類的條件。

此外,關於min_size的選擇,若是選擇了一個過大的min_size,那麼會迫使算法合併具備不一樣根源的報警。另外一方面,若是太小,那麼聚類可能會提早結束,具備相同根源的報警可能會出如今不一樣的聚類中。

所以,設置一個初始值,能夠記做ms0。定義一個較小的值 ℇ(0 < ℇ < 1),當min_size取值爲ms0、ms0 * (1 - ℇ)、ms0 * (1 + ℇ)時的聚類結果相同時,咱們就說此時聚類是ℇ-魯棒的。若是不相同,則使ms1 = ms0 * (1 - ℇ),重複這個測試,直到找到一個魯棒的最小值。

須要注意的是,ℇ-魯棒性與特定的報警日誌相關。所以,給定的最小值,可能相對於一個報警日誌來講是魯棒的,而對於另外一個報警日誌來講是不魯棒的。

實現

1. 提取報警特徵

根據線上問題排查的經驗,運維人員一般關注的指標包括時間、機器(機房、環境)、異常來源、報警日誌文本提示、故障所在位置(代碼行數、接口、類)、Case相關的特殊ID(訂單號、產品編號、用戶ID等等)等。

可是,咱們的實際應用場景都是線上準實時場景,時間間隔比較短,所以咱們不須要關注時間。同時,Case相關的特殊ID不符合咱們但願得到一個抽象描述的要求,所以也無需關注此項指標。

綜上,咱們選擇的特徵包括:機房、環境、異常來源、報警日誌文本關鍵內容、故障所在位置(接口、類)共5個。

2. 算法實現

(1) 提取關鍵特徵

咱們的數據來源是日誌中心已經格式化過的報警日誌信息,這些信息主要包含:報警日誌產生的時間、服務標記、在代碼中的位置、日誌內容等。

  • 故障所在位置

優先查找是否有異常堆棧,如存在則查找第一個本地代碼的位置;若是不存在,則取日誌打印位置。

  • 異常來源

得到故障所在位置後,優先使用此信息肯定異常報警的來源(須要預先定義詞典支持);如不能獲取,則在日誌內容中根據關鍵字匹配(須要預先定義詞典支持)。

  • 報警日誌文本關鍵內容

優先查找是否有異常堆棧,如存在,則查找最後一個異常(一般爲真正的故障緣由);如不能獲取,則在日誌中查找是否存在「code=……,message=……」 這樣形式的錯誤提示;如不能獲取,則取日誌內容的第一行內容(以換行符爲界),並去除其中可能存在的Case相關的提示信息

  • 提取「機房和環境」這兩個指標比較簡單,在此不作贅述。

(2) 聚類算法

算法的執行,咱們以圖4來表示。

圖4 報警日誌聚類流程圖

圖4 報警日誌聚類流程圖

(3) min_size 選擇

考慮到日誌數據中可能包含種類極多,且根據小規模數據實驗代表,min_size = 1⁄5 * 報警日誌數量時,算法已經有較好的表現,再高會增長過分聚合的風險,所以咱們取min_size = 1⁄5 * 報警日誌數量,ℇ參考論文中的實驗,取0.05。

(4) 聚類中止條件

考慮到部分場景下,報警日誌可能較少,所以min_size的值也較少,此時聚類已無太大意義,所以設定聚類中止條件爲:聚類結果的報警摘要數量小於等於20或已經存在某個類別的count值達到min_size的閾值,即中止聚類。

3. 泛化層次結構

泛化層次結構,用於記錄屬性的泛化關係,是泛化時向上抽象的依據,須要預先定義。

根據實驗所用項目的實際使用環境,咱們定義的泛化層次結構以下:

圖5 機房泛化層次結構

圖5 機房泛化層次結構

 

圖6 環境泛化層次結構

圖6 環境泛化層次結構

 

圖7 錯誤來源泛化層次結構

圖7 錯誤來源泛化層次結構

 

圖8 日誌文本摘要泛化層次結構

圖8 日誌文本摘要泛化層次結構

「故障所在位置」此屬性無需泛化層次結構,每次泛化時直接按照包路徑向上層截斷,直到系統包名。

實驗

如下三個實驗均使用C端API系統。

1. 單依賴故障

實驗材料來自於線上某業務系統真實故障時所產生的大量報警日誌。

  • 環境:線上
  • 故障緣由:產品中心線上單機故障
  • 報警日誌數量:939條

部分原始報警日誌如圖9所示,初次觀察時,很難理出頭緒。

圖9 單依賴故障報警日誌節選

圖9 單依賴故障報警日誌節選 

通過聚類後的報警摘要如表1所示:

ID Server Room Error Source Environment Position (爲保證數據安全,類路徑已作處理) Summary (爲保證數據安全,部分類路徑已作處理) Count
1 全部機房 產品中心 Prod com.*.*.*.CommonProductQueryClient com.netflix.hystrix.exception.HystrixTimeoutException: commonQueryClient.getProductType execution timeout after waiting for 150ms. 249
2 全部機房 業務插件 Prod com.*.*.*.PluginRegistry.lambda java.lang.IllegalArgumentException: 未找到業務插件:全部產品類型 240
3 全部機房 產品中心 Prod com.*.*.*.TrProductQueryClient com.netflix.hystrix.exception.HystrixTimeoutException: TrQueryClient.listTrByDids2C execution timeout after waiting for 1000ms. 145
4 全部機房 對外接口(猜喜/貨架/目的地) Prod com.*.*.*.RemoteDealServiceImpl com.netflix.hystrix.exception.HystrixTimeoutException: ScenicDealList.listDealsByScenic execution timeout after waiting for 300ms. 89
5 全部機房 產品中心 Prod com.*.*.*.CommonProductQueryClient com.netflix.hystrix.exception.HystrixTimeoutException: commonQueryClient.listTrByDids2C execution timeout after waiting for 1000ms. 29
6 全部機房 產品中心 Prod com.*.*.*.ActivityQueryClientImpl com.netflix.hystrix.exception.HystrixTimeoutException: commonQueryClient.getBusinessLicense execution timeout after waiting for 100ms. 21
7 全部機房 產品中心 prod com.*.*.*.CommonProductQueryClient com.netflix.hystrix.exception.HystrixTimeoutException: commonQueryClient.getBusinessLicense execution timeout after waiting for 100ms. 21
8 全部機房 對外接口(猜喜/貨架/目的地) Prod com.*.*.*.RemoteDealServiceImpl com.netflix.hystrix.exception.HystrixTimeoutException: HotelDealList.hotelShelf execution timeout after waiting for 500ms. 17
9 全部機房 產品中心 Prod com.*.*.*.TrProductQueryClient Caused by: java.lang.InterruptedException 16
10 全部機房 產品中心 Prod com.*.*.*.TrProductQueryClient Caused by: java.lang.InterruptedException 13

咱們能夠看到前三條報警摘要的Count遠超其餘報警摘要,而且它們指明瞭故障主要發生在產品中心的接口。

2. 無相關的多依賴同時故障

實驗材料爲利用故障注入工具,在Staging環境模擬運營置頂服務和A/B測試服務同時產生故障的場景。

  • 環境:Staging(使用線上錄製流量和壓測平臺模擬線上正常流量環境)
  • 模擬故障緣由:置頂與A/B測試接口大量超時
  • 報警日誌數量:527條

部分原始報警日誌如圖10所示:

圖10 無相關的多依賴同時故障報警日誌節選

圖10 無相關的多依賴同時故障報警日誌節選

通過聚類後的報警摘要如表2所示:

ID Server Room Error Source Environment Position (爲保證數據安全,類路徑已作處理) Summary (爲保證數據安全,部分類路徑已作處理) Count
1 全部機房 運營活動 Staging com.*.*.*.ActivityQueryClientImpl [hystrix]置頂失敗, circuit short is open 291
2 全部機房 A/B測試 Staging com.*.*.*.AbExperimentClient [hystrix] tripExperiment error, circuit short is open 105
3 全部機房 緩存 Staging com.*.*.*.CacheClientFacade com.netflix.hystrix.exception.HystrixTimeoutException: c-cache-rpc.common_deal_base.rpc execution timeout after waiting for 1000ms. 15
4 全部機房 產品信息 Staging com.*.*.*.queryDealModel Caused by: com.meituan.service.mobile.mtthrift.netty.exception.RequestTimeoutException: request timeout 14
5 全部機房 產品中心 Staging com.*.*.*.CommonProductQueryClient com.netflix.hystrix.exception.HystrixTimeoutException: commonQueryClient.getBusinessLicense execution timeout after waiting for 100ms. 9
6 全部機房 產品中心 Staging com.*.*.*.getOrderForm java.lang.IllegalArgumentException: 產品無庫存 7
7 全部機房 彈性工程 Staging com.*.*.*.PreSaleChatClient com.netflix.hystrix.exception.HystrixTimeoutException: CustomerService.PreSaleChat execution timeout after waiting for 50ms. 7
8 全部機房 緩存 Staging com.*.*.*.SpringCacheManager Caused by: java.net.SocketTimeoutException: Read timed out 7
9 全部機房 產品信息 Staging com.*.*.*.queryDetailUrlVO java.lang.IllegalArgumentException: 未知的產品類型 2
10 全部機房 產品信息 Staging com.*.*.*.queryDetailUrlVO java.lang.IllegalArgumentException: 沒法獲取連接地址 1

從上表能夠看到,前兩條報警摘要符合本次試驗的預期,定位到了故障發生的緣由。說明在多故障的狀況下,算法也有較好的效果。

3. 中間件與相關依賴同時故障

實驗材料爲利用故障注入工具,在Staging環境模擬產品中心服務和緩存服務同時產生超時故障的場景。

  • 環境:Staging(使用線上錄製流量和壓測平臺模擬線上正常流量環境)
  • 模擬故障緣由:產品中心全部接口超時,全部緩存服務超時
  • 報警日誌數量:2165

部分原始報警日誌如圖11所示:

圖11 中間件與相關依賴同時故障報警日誌節選

圖11 中間件與相關依賴同時故障報警日誌節選

 

通過聚類後的報警摘要如表3所示:

ID Server Room Error Source Environment Position (爲保證數據安全,類路徑已作處理) Summary (爲保證數據安全,部分類路徑已作處理) Count
1 全部機房 Squirrel Staging com.*.*.*.cache Timeout 491
2 全部機房 Cellar Staging com.*.*.*.cache Timeout 285
3 全部機房 Squirrel Staging com.*.*.*.TdcServiceImpl Other Exception 149
4 全部機房 評論 Staging com.*.*.*.cache Timeout 147
5 全部機房 Cellar Staging com.*.*.*.TdcServiceImpl Other Exception 143
6 全部機房 Squirrel Staging com.*.*.*.PoiManagerImpl 熔斷 112
7 全部機房 產品中心 Staging com.*.*.*.CommonProductQueryClient Other Exception 89
8 全部機房 評論 Staging com.*.*.*.TrDealProcessor Other Exception 83
9 全部機房 評論 Staging com.*.*.*.poi.PoiInfoImpl Other Exception 82
10 全部機房 產品中心 Staging com.*.*.*.client Timeout 74

從上表能夠看到,緩存(Squirrel和Cellar雙緩存)超時最多,產品中心的超時相對較少,這是由於咱們系統針對產品中心的部分接口作了兜底處理,當超時發生時後先查緩存,若是緩存查不到會穿透調用一個離線信息緩存系統,所以產品中心超時整體較少。

綜合上述三個實驗得出結果,算法對於報警日誌的泛化是具備必定效果。在所進行實驗的三個場景中,均可以定位到關鍵問題。可是依然存在一些不足,報警摘要中,有的通過泛化的信息過於籠統(好比Other Exception)。

通過分析,咱們發現主要的緣由有:其一,對於錯誤信息中關鍵字段的提取,在必定程度上決定了向上泛化的準確度。其二,系統自己日誌設計存在必定的侷限性。

同時,在利用這個泛化後的報警摘要進行分析時,須要使用者具有相應領域的知識。

將來規劃

本文所關注的工做,主要在於驗證聚類算法效果,還有一些方向能夠繼續完善和優化:

  1. 日誌內容的深度分析。本文僅對報警日誌作了簡單的關鍵字提取和人工標記,未涉及太多文本分析的內容。咱們能夠經過使用文本分類、文本特徵向量類似度等,提升日誌內容分析的準確度,提高泛化效果。
  2. 多種聚類算法綜合使用。本文僅探討了處理系統錯誤日誌時表現較好的聚類算法,針對系統中多種不一樣類型的報警,將來也能夠配合其餘聚類算法(如K-Means)共同對報警進行處理,優化聚合效果。
  3. 自適應報警閾值。除了對報警聚類,咱們還能夠經過對監控指標的時序分析,動態管理報警閾值,提升告警的質量和及時性,減小誤報和漏告數量。

參考資料

  1. Julisch, Klaus. 「Clustering intrusion detection alarms to support root cause analysis.」 ACM transactions on information and system security (TISSEC) 6.4 (2003): 443-471.
  2. https://en.wikipedia.org/wiki/Cluster_analysis

做者簡介

  • 劉瑒,美團點評後端工程師。2017 年加入美團點評,負責美團點評境內度假的業務開發。
  • 千釗,美團點評後端工程師。2017 年加入美團點評,負責美團點評境內度假的業務開發。
相關文章
相關標籤/搜索