背景
阿裡集團針對故障處理提出了“1/5/10”的目标-- 1 分鐘發現、5 分鐘定位、10 分鐘恢複,這對我們的定位能力提出了更高的要求。
EMonitor 是一款內建 Tracing 和 Metrics、服務于餓了麼所有技術部門的一站式監控系統,其覆寫了
- 前端監控、接入層監控;
- 業務 Trace 和 Metric 監控;
- 所有的中間件監控;
- 容器監控、實體機監控、機房網絡監控。
每日處理總資料量近 PB,每日寫入名額資料量幾百 T,日均幾千萬的名額查詢量,内含上萬個圖表、數千個名額看闆,并且已經将所有層的監控資料打通并串聯了起來。但是在故障來臨時,使用者仍然需要花費大量時間來檢視和分析 EMonitor 上的資料。
比如阿裡本地生活的下單業務,涉及到諸多應用,每個應用諸多 SOA 服務之間錯綜複雜的調用關系,每個應用還依賴 DB、Redis、MQ 等等資源,在下單成功率下跌時,這些應用的負責人都要在 EMonitor 上檢視名額曲線以及鍊路資訊來進行人肉排障以自證清白,耗時耗力,是以自動化的根因分析必不可少。
根因分析模組化
業界已經有好多在做根因分析的了,但是大都準确率不高,大部分還在 40% 到 70% 之間,從側面說明根因分析确實是一個難啃的骨頭。
根因分析看起來很龐大,很抽象,無從下手,從不同的角度(可能是表象)去看它,就可能走向不同的路。那如何才能透過表象來看到本質呢?
我這裡并不會一開始就給你列出高大上的算法解決方案,而是要帶你重新認知根因分析要解決的問題到底是什麼。其實好多人對要解決的問題都模糊不清,你隻有對問題了解清楚了,才能有更好的方案來解決它。
要解決什麼樣的問題
舉個例子:現有一個應用,擁有一堆容器資源,對外提供了諸多 SOA 服務或者 Http 服務,同時它也依賴了其他應用提供的服務,以及 DB 資源、Redis 資源、MQ 資源等等,具體見下圖;那我們如何才能夠全面的掌控這個應用的健康狀況呢?

我們需要掌控:
- 掌控這個應用的所有入口服務的「耗時」和「狀态」
- 掌控每個入口服務之後每種操作的「耗時」和「狀态」
一個應用都有哪些入口?
- SOA 服務入口;
- Http 服務入口;
- MQ 消費消息入口;
- 定時 job 入口;
- 其他的一些入口。
進入每個入口之後,都可能會有一系列的如下 5 種操作和 1 種行為(下面的操作屬性都是以阿裡本地生活的實際情況為例,并且都包含所在機房的屬性):
- DB 遠端操作:有 dal group、table、operation(比如select、update、insert等)、sql 的操作屬性;
- Redis 遠端操作:有 command 的操作屬性;
- MQ 遠端操作(向MQ中寫消息):有 exchange、routingKey、vhost 的操作屬性;
- RPC 遠端操作:有 遠端依賴的 appId、遠端 method 的操作屬性;
- Local 操作(即除了上述4種遠端操作之外的本地操作): 暫無屬性;
- 抛出異常的行為:有異常 name 的屬性。
那我們其實就是要去統計每個入口之後的 5 種操作的耗時情況以及狀态情況,以及抛出異常的統計情況。
針對遠端操作其實還要明細化,上述遠端操作的每一次耗時是包含如下 3 大部分:
- 用戶端建立連接配接、發送請求和接收響應的耗時;
- 網絡的耗時;
- 伺服器端執行的耗時。
有了上述三要素,才能去确定遠端操作到底是哪出了問題,不過實際情況可能并沒有上述三要素。我們的實際情況也是沒有的,隻能找簡單的替換方案,通過判斷每個應用的tcp重傳是否抖動上升來簡易判斷是否存在網絡問題
可以簡單抽象成如下幾種類型的名額:
名額名 | tags | fields |
---|---|---|
{appId}.soa_provider | ||
appId的SOA服務總耗時名額 | ezone:所在機房 |
clientApp:調用方appId
hostName:服務方的機器名
method:服務方的方法名 | timerCount:總耗時
timerSum:總次數
timerSum/timerCount:平均耗時 |
| {appId}.soa_exception
appId的SOA服務跑出異常的情況 | ezone:所在機房
hostName:抛出異常的機器
method:抛出異常的soa服務
name:抛出異常的異常名 | count:抛出異常的次數 |
| {appId}.soa_span
詳細記錄了每個method中各個span環節的耗時 | ezone:所在機房
method:服務方的方法名
spanType:操作類型,即上述的DB、Redis、MQ、RPC、Local | timerCount:總耗時
| {appId}.db
詳細記錄了每個入口SOA method的DB操作耗時 | ezone:所在機房
method:目前db調用是在哪個soa服務的method中觸發的
table:db操作的表
operation:db操作的類型,如insert、update、select
sql:db操作的sql | timerCount:總耗時
| {appId}.rpc
詳細記錄了每個入口SOA method的rpc操作耗時 | ezone:所在機房
method:目前rpc調用是在哪個soa服務的method中觸發的
serviceApp:依賴的遠端調用的appId
serviceMethod:依賴的遠端調用method | timerCount:總耗時
| {appId}.redis
詳細記錄了每個入口SOA method的redis操作耗時 | 類同上面 | 同上 |
| {appId}.mq
詳細記錄了每個入口SOA method的mq操作耗時 | 類同上面 | 同上 |
故障的結論
針對故障的結論,我們并不是隻給出一個最終的根因,而是需要能夠詳細呈現整個故障鍊路,以及每個鍊路節點中故障的詳細資訊
有了上述資料的全面掌控,當一個故障來臨的時候,我們可以給出什麼樣的結論?
- 哪些入口受到影響了?
- 受影響的入口的本地操作受到影響了?
- 受影響的入口的哪些遠端操作受到影響了?
- 具體是哪些遠端操作屬性受到影響了?
- 是用戶端到伺服器端的網絡受到影響了?
- 是伺服器端出了問題嗎?
- 受影響的入口都抛出了哪些異常?
上述的這些結論是可以做到非常高的準确性的,因為他們都是可以用資料來證明的。
然而第二類根因,卻是無法證明的:
- GC 問題;
- 容器問題。
他們一旦出問題,更多的表現是讓上述 5 種操作耗時變長,并且是沒法給出資料來明确證明他們和 5 種操作之間的關聯,隻是以一種推測的方式來懷疑,從理論上來說這裡就會存在誤定位的情況。
還有第三類更加無法證明的根因:
- 變更問題
昨天的變更或者目前的變更到底和目前的故障之間有沒有關聯,更是無法給出一個證明的,是以也隻能是瞎推測。
我們可以明确的是 5 種操作的根因,還需要進一步去檢視是否是上述第二類根因或者第三類根因,他們隻能作為一些輔助結論,因為沒法給出嚴謹的資料證明。
我們的特點
針對鍊路拓撲,我們并不會使用所謂的高大上的知識圖譜的方案,我們隻需要将調用關系存放在相關名額的tag上即可,我們就可以通過實時的名額資料來擷取實時的鍊路拓撲,比如名額 appId1.rpc中包含如下tag組合
- method:metho1-1, serviceApp:appId2,serviceMethod:method2-1
- method:metho1-1, serviceApp:appId3,serviceMethod:method3-1
比如我們查詢 appId1.rpc名額,過濾條件為method=method1-1,group by serviceApp,serviceMethod,從查詢結果中我們就自然得知appId1依賴了appId2、appId3以及依賴的方法。類似的,從調用名額上{appId}.soa_provider,我們也可以看到是哪些client appId在調用目前appId的服務的哪些method服務
針對因果關系,我們并不會去采用所謂的曲線相似性檢測來識别相關的名額,而是通過更加嚴謹的邏輯關系來證明而不是所謂的推測。上帝從來都沒有告訴你曲線相似的名額是有關聯的,曲線不相似的名額是沒有關聯的,比如某個appId的SOA method1曲線抖動了,它的DB也抖動了,難道是DB抖動引起了SOA method1的抖動?這2者到底有沒有關聯?(有可能是别的SOA服務調用DB抖動引起DB抖動,而目前SOA服務抖動可能是因為依賴的RPC調用抖動),我們不是靠曲線相似性檢測而是通過實實在在的資料來證明他們的關聯性。我們會記錄每個SOA服務的各個環節的耗時,比如名額appId1.soa_span,包含如下tag組合
- method:metho1,spanType=DB
- method:metho1,spanType=Redis
- method:metho1,spanType=RPC
那麼我們隻需要查詢appId.soa_span名額,過濾條件method=method1,group by spanType,就是可以從中看得到method1服務抖動的根因到底是不是DB類型的抖動引起的
根因分析實作
在明确了我們要解決的問題以及要求的故障結論之後,我們要采取什麼樣的方案來解決呢?下面首先會介紹一個基本功能「名額下鑽分析」。
名額下鑽分析
一個名額,有多個 tag,當該名額總體波動時,名額下鑽分析能夠幫你分析出是哪些 tag 組合導緻的波動。
比如用戶端的 DB 操作抖動了,利用名額下鑽分析就可以分析出
- 哪個機房抖動了?
- 哪個 dal group 抖動了?
- 哪張表抖動了?
- 哪種操作抖動了?
- 哪個 sql 抖動了?
再比如遠端 RPC 操作抖動了,利用名額下鑽分析就可以分析出
- 哪個遠端 appId 抖動了?
- 哪個遠端 method 抖動了?
其實這個就是去年 AIOPS 競賽的題目,詳細見:
http://iops.ai/competition_detail/?competition_id=8&flag=1我們的方案:
什麼叫抖動?某個SOA服務耗時500ms,某個RPC環節耗時250ms,那麼這個RPC環節一定是有問題的嗎?不一定,必須要跟它正常情況進行一個對比才能知道它的這個耗時是否正常。是以需要先去學習一個正常水準,然後基于正常水準的對比才能确定某個環節是否抖動了
我們的要求:在圈定的曲線範圍内,前一半時間範圍要正常,異常抖動存在于後一半時間範圍内
通過對前一半正常時間範圍的學習,然後基于學習的水準來判定識别後一半時間範圍的異常抖動
步驟1 對整體的曲線确定波動範圍
關鍵點1.1
對前一半時間範圍提取資料抖動特征,運用二階指數平滑算法進行資料預測,根據預測值和實際值的波動資訊,提取如下3個資訊
- 原始資料的最大值
- 原始資料的最小值
- 原始資料和預測資料之間的波動方差
問題1:為什麼不對原始資料求波動方差?
要對一些趨勢性的曲線能夠适應,比如臨近高峰期,平均響應時間也是在正常範圍内緩慢上升的
關鍵點1.2
對後一半資料仍然使用二階指數平滑算法進行資料預測,根據預測值和實際值的波動,驗證波動是否超過上述标準波動方差的3倍,如果超過則為波動點,并提取如下6個資訊:
- 第一次波動點:第一次波動的點
- 最大的波動點:波動值最大的點
- 最後一次波動點:波動值最大的點
- 初始點:它和波動值最大的點關于第一次波動點對稱
- 最後一次波動點
- 向上波動還是向下波動
那麼初始點~第一次波動點之間的資料是正常範圍,第一次波動點~最大的波動點是異常範圍
步驟2 在計算範圍内算出每根時間線的波動值
關鍵點2.1
對每根時間線,先求出正常範圍下的平均值,然後基于這個正常的平均值來計算該時間線在0到最大波動點的波動方差(加上權重比例,越靠後的點權重比例越大),并且方差要除以該時間線在正常範圍内的平均值的開方
問題2:為什麼要除以平均值的開方?
方差針對場景是:各個時間線都在統一的平均值下,可以使用方差來判定各個時間線的抖動情況。然而真實情況下,各個時間線的平均值都不一樣,這就會造成時間線1在某個時刻從1抖動到100,然而時間線2在哪個時刻從1000抖動到1200,明顯時間線1抖動更大,然而方差卻是時間線2比較大。
是以對方差除以平均值,但是為什麼又選擇了平均值的開方而不是平均值?
其實是否除以平均值,不管除不除都是2個極端,假如除以那麼也會将一些抖動小的案例的波動值提高了,是以應該是取一個權衡
關鍵點2.2
對于每個時間線的第一次波動點、最大波動點、最後一次波動點減去正常範圍的平均值,然後再除以正常範圍的平均值求出3個波動比率
步驟3 對所有時間曲線進行一些過濾
關鍵點3.1
假如整體是向上波動,那麼對于上述3個波動比率均<0則過濾掉
假如整體是向下波動,那麼對于上述3個波動比率均>0則過濾掉
關鍵點3.2
求出top10的波動比率,然後在top10中去掉一個最大值和一個最小值,求出一個平均波動比率,再按照一定比率比如1/10,算出一個基準波動比率,對于小于該基準波動比率的都可以過濾掉
同時判斷0到第一個異常點之前的資料,都要小于第一個異常點,同時容忍一定比例的大于第一個異常點,假如不符合的話,都可以過濾掉
步驟4 對過濾後的時間曲線按照波動值進行 KMeans 聚類
對于過濾後的所有時間線按照波動方差進行KMeans聚類,目前N的選擇是根據資料量的大小來進行選擇的
比如時間線個數小于等于10則聚3類
步驟5 從排名靠前的分類中挑選出方案,為每個方案計算方案分數
然後從對聚類靠前的時間線提取公共特征,公共特征其實就是一個方案
将前2個聚類劃分為一個叢集A,将所有聚類劃分成一個叢集B
周遊上述所有方案,計算每個方案在上述叢集A、B中的表現行為打分,打分項有如下幾點:
- 每個方案在目标叢集A中的占比
- 每個方案在目标叢集A中滿足的個數與在目标叢集B中滿足的個數占比
- 每個方案的大小分數,每個方案包含的内容越多,分數越小,也就是說傾向于用最簡單的方案
- 每個方案,波動比率的滿足率,高于基準波動率的組合滿足,否則不滿足,進而計算出該方案的一個滿足率
綜上所有項的乘積結果算出來的分數即為方案的分數
分數最高的方案即為我們最終求解的方案
根因分析流程
有了名額下鑽分析功能之後,我們來看如何進行根因分析:

- 假如QPS下跌但是成功率并未下跌,那麼就需要上鑽分析,通過名額下鑽分析算法{appId}.soa_provider分析出QPS下降的clientApp,繼續遞歸去分析這個clientApp的SOA服務狀況
- 執行下鑽分析,對名額{appId}.soa_span執行名額下鑽分析算法,得到異常抖動的ezone、spanType屬性,比如WG機房的DB操作抖動了,比如WG機房的RPC操作抖動
- 然後到對應操作類型的名額中再次進行名額下鑽分析,比如DB操作抖動則分析{appId}.db,比如RPC抖動則分析{appId}.rpc,得出該操作下:
- 哪些入口受到該操作的波動影響了?
- 哪些操作屬性異常波動了?
- 假如該操作是遠端操作,還要繼續深入伺服器端進行分析
假如是DB操作,那麼可以繼續到遠端伺服器端再深入分析,比如我們的遠端伺服器端是DAL中間件代理層,可以繼續分析它相關的名額,可以分析出相關的抖動屬性,比如
- 某個 table 的所有操作耗時抖動?
- 某條sql操作耗時抖動?
- 某台具體DB執行個體抖動?
- SQL的停留時間 or 執行時間抖動?
假如是RPC操作,那麼可以繼續到遠端appId端再遞歸分析
- 針對受影響的這些入口使用名額下鑽分析{appId}.soa_exception,哪些異常也抖動了(有些異常一直在抛,但是跟本次故障無關);
- 再次檢視上述抖動的操作是否是由 GC 問題、容器問題、變更問題等引起的。比如對名額{appId}.soa_provider進行名額下鑽分析,分析其抖動是不是僅僅由于某個hostName導緻的,假如是某個hostName導緻,則可以繼續分析是不是GC抖動導緻的
落地情況
阿裡本地生活的根因分析能力,1 個月内從産生根因分析的想法到實作方案上線到生産(不包括名額下鑽分析的實作,這個是之前就已經實作好的了),2個月内在生産上實驗和優化并推廣開來,總共 3 個月内實作了從 0 到 1 并取得了如下成績
- 85 個詳細記載的案例中準确定位 81 次,準确率 95%;
- 最高一天執行定位 700 多次;
- 平均定位耗時 1 秒;
- 詳細的定位結果展示。
下圖即為從4月1号到6月23号每天根因分析次數的統計情況

我們對定位準确性的要求如下:
- 要明确給出受到影響的入口服務有哪些;
- 每個受到影響的入口服務抖動的操作根因以及操作屬性都要正确;
每個入口服務抖動的根因很可能不一樣的,比如目前應用的 SOA1 是由于 Redis 操作抖動,目前應用的 SOA2 是由于遠端 RPC 依賴的其他 SOA3 抖動導緻,SOA3 是由于 Redis 操作抖動導緻;
- 用戶端操作耗時抖動到底是用戶端原因還是伺服器端原因要保證正确;
- 容器問題時,要保證不能定位到錯誤的容器上。
準确率為什麼這麼高?
我認為主要是以下 3 個方面:
資料的完整度
假如是基于采樣鍊路去分析,必然會存在因為漏采導緻誤判的問題。
我們分析的資料是全量鍊路資料轉化成的名額資料,不存在采樣的問題,同時在分析時可以直接基于名額進行快速分析,臨時查采樣的方式分析速度也是跟不上的。
模組化的準确性
你的模組化方案能回答出每個 SOA 服務抖動的根因分别是什麼嗎?
絕大部分的模組化方案可能都給不出嚴謹的資料證明,以 DB 是根因為例,他們的模組化可能是 SOA 服務是一個名額,DB 操作耗時是一個名額,2 者之間是沒有任何關聯的,沒有資料關聯你就給不出嚴謹的證明,即沒法證明 DB 的這點抖動跟那個 SOA 服務之間到底有沒有關聯,然後就隻能處于瞎推測的狀态,這裡就必然存在誤判的情況。
而我們上述的模組化是建立了相關的關聯,我們會統計每個入口後的每種操作的耗時,是可以給到嚴謹的資料證明。
異常判定的自适應性
比如 1 次 SOA 服務整體耗時 1s,遠端調用 RPC1 耗時 200ms,遠端調用 RPC2 耗時 500ms,到底是哪個 RPC 調用耗時抖動呢?耗時最長的嗎?超過一定門檻值的 RPC 嗎?
假如你有門檻值、同環比的限制,那麼準确率一定不高的。我們的名額下鑽分析在解決此類問題的時候,是通過目前情況下的波動貢獻度的方式來計算,即使你耗時比較高,但是和之前正常情況波動不大,那麼就不是波動的根因。
速度為什麼這麼快?
我認為主要是以下 2 方面的原因:
業内領先的時序資料庫 LinDB
根因分析需要對諸多名額的全量次元資料進行 group by 查詢,是以背後就需要依靠一個強大的分布式時序資料庫來提供實時的資料查詢能力。
LinDB 時序資料庫是我們阿裡本地生活監控系統 E-Monitor 上一階段的自研産物,在查詢方面:
- 強悍的資料壓縮:時序資料原始資料量和實際存儲量之比達到 58:1,相同 PageCache 的記憶體可以比别的系統容納更多的資料;
- 高效的索引設計:索引的預過濾,改造版的 RoaringBitmap 之間的 and or 操作來進行高效的索引過濾;
- 單機内充分并行化的查詢機制:利用 akka 架構對整個查詢流程異步化。
整體查詢效率是 InfluxDB 的幾倍到幾百倍,詳細見文章
分布式時序資料庫 - LinDB
https://zhuanlan.zhihu.com/p/35998778名額下鑽分析算法的高效
- 我們不需要每個時間線都進行預測;
- 實際要計算的方案個數非常之少;
- 方案算分上可以适應于任何加減乘除之類的名額計算上,比如根因定位中的平均響應時間 = 總時間 / 總次數
SOA1 的平均響應時間 t1 和 SOA2 的平均響應時間 t2,SOA1 和 SOA2 的總體平均響應時間并不是 t1 和 t2 的算術平均而是權重平均,如果是權重平均,那麼久需要多存儲一些額外的資訊,并且需要進行額外的權重計算
實際案例
案例1-Redis抖動
故障現場如下,某個核心應用的 SOA 服務抖動上升:

直接點選根因分析,就可以分析到如下結果

AppId1 的 SOA 服務在某個機房下抖動了
- 依賴的 AppId2 的 SOA 服務抖動
- 依賴的 AppId3 的 SOA 服務抖動
- 依賴的 AppId5 的本地處理和 Redis 操作耗時抖動
- 依賴的 AppId6 的本地處理和 Redis 操作耗時抖動
- 依賴的 AppId4 的本地處理和 Redis 操作耗時抖動
- 依賴的 AppId3 的 SOA 服務抖動
這裡的本地處理包含擷取 Redis 連接配接對象 Jedis 的耗時,這一塊沒有耗時打點就導緻劃分到本地處理上了,後續會進行優化。這裡沒有給出詳細的 Redis 叢集或者機器跟我們的實際情況有關,可以暫時忽略這一點。
點選上面的每個應用,下面的表格都會列出所點選應用的詳細故障資訊
- 受影響的 SOA 服務有哪些,比如 AppId1 的 3 個 SOA 服務受到影響;
- 每個 SOA 服務抖動的根因,比如 AppId1 的 3 個 SOA 服務都受到 AppId2 的 1 個 SOA 服務的抖動影響;
- 表格中每一個連結都可以跳轉到對應的名額曲線監控面闆上。
再比如點選 AppId4,顯示如下:

AppId4 的 1 個 SOA 方法抖動
- 該方法的本地處理抖動(實際是擷取 Redis 連接配接對象 Jedis 的耗時抖動);
- 該方法依賴的 Redis 操作抖動;
- 該方法抛出 Redis 連接配接異常;
案例2-DB執行個體抖動
故障現場如下,某個核心應用的 SOA 服務抖動上升

點選根因分析,就可以幫你分析到如下結果
-
- 依賴的 DB 服務抖動
-
-
點選 AppId2,可以看下詳細情況,如下所示:

從表格中就可以看到,根因是 DB 的一台執行個體抖動導緻這個 dal group 所有操作抖動。
案例3-上鑽分析案例
故障現場如下,某個核心應用的 SOA 服務調用量下降,成功率并未變化


就是這個appId的上遊的上遊操作了相關的變更導緻調用量下跌
案例4-共同依賴抖動
故障現場如下,某個核心應用的 SOA 服務相應時間抖動


均是因為依賴圖中标出的appId1而導緻抖動
案例5-整體機房網絡抖動
故障現場如下,某個核心應用SOA服務相應時間抖動、成功率抖動


鍊路中的各個應用SOA調用均出現TCP重傳過高的問題,點選每個應用的詳情,檢視TCP重傳的名額曲線如下

确實是TCP重傳過高
案例6-單容器抖動
故障現場如下,某個核心應用SOA服務相應時間抖動

點選根因分析,可以分析到如下結果

目前appId遠端依賴的另一個appId的容器抖動導緻的
作者
李剛,網名乒乓狂魔,餓了麼監控組研發專家,餓了麼内部時序資料庫 LinDB 的項目負責人,餓了麼根因分析項目負責人,目前緻力于監控的智能分析領域以及下一代全景監控的體系化建構;
林濱(予譜),餓了麼監控組前端工程師,現負責一站式監控系統 EMonitor 的前端開發,旨在将繁雜的資料以高可視化輸出。