想要4個9?掌握這些監控告警的關鍵技巧

Linksla發表於2024-04-12

告警的本質

沒有多少系統的告警是設計得當的。良好的告警設計是一項非常困難的工作。如何知道你收到的告警是糟糕的?多少次你收到了告警之後,立即就關掉了的?是不是成天被這些然而並沒有什麼卵用的東西給淹沒?最常見的告警設定: cpu使用率 超過90%,然後告警。這種設定在大部分場合下是沒有辦法提供高質量的告警的。

高質量的告警應該是這樣的:每次收到之後你可以立即評估影響的範圍,並且每一個告警需要你做出分級響應。所謂每個告警都應該是,actionable的。

告警的實質可以用下圖表明:

圖片

伺服器的設計應該是以這樣的無人值守為目的的。假設所有的運維全部放假了,服務也能7*24自動運轉。

圖片

告警的實質就是“把人當服務用”。在一些事情還沒有辦法做到程式化執行的時候,用告警通知人的方式去幹預系統達到修正的目的。一次告警就像一次服務呼叫一樣。如果告警了,但是收到告警的人並不需要做任何處理,那麼這就是一種DDoS攻擊,攻擊的是運維的幸福生活。

很多時候,告警通知人去幹的事情是真的可以被自動化掉的。比如伺服器掛了,換一臺上來。在小一點的系統裡,可能就是停機一會,人工來處理換一臺冷備的機器上去。大一點的系統,因為伺服器多了,天天都掛可不行,必須是熱備的,系統自動切換到備機。 再大一點的系統,因為切換實在太頻繁了,故障機的退庫,備機的保有都變成了一種管理負擔,那麼可以和其他的運維流程打通變成完全自動化的系統。只是因為業務處理不同階段,選擇不同的實現策略而已。業務量小,拿血肉當機器用,有的時候更經濟而已。當然對於那個被當成機器人來用的哥們來說,生活確實有點不公平。

告警物件

告警物件可以分為兩種:

  • 業務規則監控
  • 系統可靠性監控


對於業務規則監控可以舉一個遊戲的例子。比如DNF的遊戲角色在一定裝備的情況下,單次打擊的傷害輸出應該是有一個上限,如果超過了就說明有作弊的情況。又比如鬥地主遊戲裡一個人的連勝場次是有一定上限的,每天的勝率是有一定上限,如果超出平均值太多就可能是作弊。業務規則監控的不是硬體,也不是軟體是否工作正常。而是軟體是否按照業務規則實現的,是否有漏洞。也可以理解為對“正確性”的監控。


系統可靠性監控是最常見的監控形式,比如發現是不是伺服器掛掉了,服務是不是過載了等等。對於大部分後臺服務,系統可以抽象建模成這個樣子:
圖片

對於這樣的系統可以採集什麼指標?

  • 請求數,請求到達速率

  • 正常響應數,正常響應占比

  • 錯誤響應數,錯誤響應占比

  • 響應延時

  • 佇列長度,排隊時間

實際的情況是,幾乎任何系統都不是孤立執行的。而是這樣的:

圖片


一個DB會依賴於底層的cpu,記憶體,磁碟等資源。一個Http服務會依賴於底層的DB服務。一個應用會依賴於數個底層的 RPC 服務。於是又多了幾個指標

  • 資源A的呼叫量(比如CPU使用率)

  • 資源B的呼叫量(比如記憶體分配和釋放)

  • 資源C的呼叫量(比如網路傳送包量)

這種層次結構,一般來說簡單來說可以分為四層:

  • 產品策略和營銷:它們決定了根本的請求到達的速率
  • 應用層(更粗俗一點可以叫web層):最上層的膠水
  • 服務層:db,各種RPC服務,以及層層巢狀的服務
  • 硬體層:cpu,記憶體,磁碟,網路

圖片
因為這樣的一個依賴層次。上一層對下一層的資源消耗量變成了下一層的請求數。比如Http服務消耗了多少DB的資源,就對應了DB服務需要處理多少請求數。DB繁忙與否取決於Http服務請求,Http服務請求繁忙與否取決於多少人開啟客戶端,多少人開啟客戶端又取決於產品策略和營銷活動。這種層次結構決定了單純跟蹤一個指標,比如絕對請求數,很難說明這一層的服務是否出現了故障。
有這麼多層次,每層又有很多指標可以採集。那麼應該採集什麼指標,用什麼告警策略去告警呢?最前面已經提到了告警必須是actionable的,但是實際情況下只有這種綱領性要求仍然是不好操作的。至少可以提幾點不應該做的事情:

  • 不應該用採集的難度決定你使用什麼指標去告警。很多情況下cpu使用率可能是最好採集的,但是未必是最值得告警的。
  • 不要給運維他們想要的告警,而是要做“真正”想要的告警。大部分情況下,人們告訴你的是一個解決方案。運維告訴你它需要對db程序的cpu使用率超過x%的時候告警,它給你的是一個他認為最優的解決方案。但是他真正想要的是知道db服務是否有異常,cpu使用率超過x%未必是最好的告訴你服務是否出現異常的指標。

盲目地採集那些容易獲取的指標,並隨意地設定閾值告警是大部分糟糕的告警質量的根源。

監控的指標和策略

那到底應該採集什麼指標呢?我認為大部分的系統可靠性監控不外乎三個目標:

  • is the work getting done?系統是否在持續完成其設定的工作。
  • is the user having good experience?使用者體驗是否好。
  • where is the problem/bottleneck?問題或者瓶頸在哪裡。

其中最核心最關鍵的是第一個問題,is the work getting done。對於資料庫來說,我們可以採集:

  • cpu 使用率
  • 網路頻寬大小
  • db請求數
  • db響應數
  • db錯誤響應數
  • db請求延遲

顯然要回答一個db是否完成了其指定的工作,更應該關注的指標是這兩個:

  • db請求數的絕對量
  • db正確響應相對請求數的佔比

這兩個指標相對於採集什麼cpu使用率更能說明問題。不僅僅是db,各個層次的服務都可以用請求量和正確響應占比來反映其工作狀況。比如http請求數(對比http正確響應數),比如app開啟次數(對比服務端記錄的線上人數)等等。
為什麼cpu使用率不能說明問題?大部分時候,我們並不關心cpu本身,而關心使用cpu為資源的服務。所以cpu使用率只是一種資源的請求數而已。與請求數相關的一個概念是saturation(上限),當上限達到的時候,處理開始排隊,延遲開始變長,錯誤率開始升高。那麼cpu使用率是不是能夠說明上限呢?cpu使用率的上限以100%記,那麼90%開始告警不是很合理嗎?畢竟cpu 100%了幾乎可以等同於db無法正常處理請求了。
這種利用底層資源呼叫量,評估其是否達到上限的做法有兩個根本缺陷:

  • 你無法知道上層服務可以把底層資源利用到什麼程度
  • 底層資源的 saturation 未必可以容易度量

具體來說,db是不是可以真的100%利用cpu是位置的。假如請求裡鎖,或者sleep,那麼也許cpu永遠也無法達到100%。90%可能就是極限了。而且現代的cpu是多核的,如果請求處理只能利用單核,處理在多個核之間跳躍,對於一個核來說永遠也不會一直保持100%。
對於cpu可能其上限真的有一個100%的值。但是對於很多非硬體的服務,比如你是一個登陸服務,依賴於一個db。那麼這個db每秒可以處理的不同sql組合數是很難度量的,絕非和磁碟一樣有一個mb/s的極限絕對值可以做為對比。
而且度量底層資源的使用還有一個缺陷是你無法列舉出所有依賴的資源的。所以與其這麼繞彎子地透過底層資源來間接監控上層服務是否正常,還不如直接測量work是不是getting done呢。
對於第二個問題,is the user having good experience?可以採集的指標為

  • 平均排隊時間,平均總響應延遲
  • 99/95/90 percentile的排隊時間,99/95/90 percentile的響應延遲

這裡的使用者不一定是指人或者玩家,可能是上一層的服務呼叫方,另外一個系統。
第三個問題就是所謂的故障定位。要是人工來做的話,最常見的做法是收到了告警,然後登陸CRT,開始敲各種命令查詢原因。對於系統來說,最合適的做法不是出了問題再去執行一堆命令,而是:

  1. 每個層次都對自己做告警
  2. 頂層服務出了告警觸發自動定位程式
  3. 按照服務的依賴關係和大致的時間範圍,定位到告警之間的關聯,從而找到出問題或者瓶頸的地方

當然實際情況是很複雜的。很多原因和結果是互為因果的。兩個告警是兩個現象,還是一個原因一個現象實際上很難說得清楚。
從告警演算法的角度來講,對成功請求率,或者平均響應延遲做告警是非常容易的。靜態閾值大家看不起,覺得簡單。但是大部分告警用靜態閾值就可以解決問題。

理論與現實

那告警要不要高難度的演算法?我的觀點是採集到了正確的指標,是不需要複雜演算法的,就是靜態閾值都可以搞得定。但是至少有三種場合需要演算法:

  • 無法直接採集到錯誤數:需要對錯誤日誌的自動分類
  • 無法直接採集到請求成功率:需要對請求數或響應數的絕對值做
    異常檢測
  • 只有總數,無法採集到其中的每個細分構成項的佔比:需要對參與的factor進行演算法擬合

其實這三項都是一個主題的,當你無法直接獲取到告警所需的指標的時候,事情會變得複雜很多。有一個比喻是:最近NASA宣佈的地球孿生兄弟 Kepler 452b 。如果我們的探測器可以跑到1400光年之外,發現他將是非常容易的事情。正式因為直接獲得資料非常困難,所以科學家才需要根據行星阻擋 恆星時 引起的亮度變化(所謂掩星法)來發現這些遙遠的星球。
採集所需的指標的困難可能是幾方面的因素。一種原因是採集本身是非常消耗資源的事情。比如獲取每個mysql查詢所消耗的cpu。跟蹤每個請求處理過程是不可能的。這個時候就需要演算法的幫助了,可以仔細看一下vividcortex的影片:http://www.youtube.com/watch?v=szAfGjwLO8k
更多情況是採集指標困難是D/O分離造成的溝通問題,運維需要的指標需要開發去埋點,而開發埋點的地方又需要運維去做告警。很多時候退而求其次就會造成,有什麼指標就用什麼指標的狀況。比如雖然沒有請求響應的錯誤數,但是錯誤基本上都會有錯誤日誌記錄,根據錯誤日誌滾動的快慢可以大致知道是不是出了問題。這就引入了一個非常困難的日誌分類問題,什麼日誌代表了正常,什麼日誌代表了異常,異常又非了哪些型別?這個方面演算法做得好的是summo logic公司:
https://www.sumologic.com/ 。為什麼這種opsdev(嘲諷devops那)公司如此熱衷於演算法?對於他們來說好處是顯而易見的,客戶需要做的改動越少,接入成本越低,客戶面就越廣。但是拿機器演算法去挖掘海量日誌真的是回答:is the work getting done?的手段?顯然不是。這就是大炮打蚊子。日誌的存在是用於解決問題,而不是有了海量日誌了,如何用好“它們”變成了問題本身。
第三類情況是沒有辦法採集到請求成功率,只能對絕對的處理成功的量。只有這類資料要告警,就無法做簡單的靜態閾值了。對於延遲,一般可以定一個業務上可以接受的延遲上限。對於成功率,也可以定一個可接受的成功率上限。但是對於絕對的處理量,是沒有辦法簡單地比較一個靜態閾值就可以判斷是正常還是異常的。
在討論如何實現之前,再強調兩點:

  • 處理成功的量不是度量is work getting done的指標。費事費力去搞演算法,不如直接把成功率指標給採集了。

  • 處理成功的量,還取決於請求數。而請求數根本上是取決於上層服務了。你是一個dba,發現db的每秒處理的請求數陡降了。這說明是db故障了?還是app故障了?都有可能……最最上層是產品和營銷。你發現一個業務的註冊量相對前幾天變少了,這個是不是說明註冊服務出問題了?也需是產品太爛了,遊戲根本沒有人來玩。也可能是營銷手段的營銷,不送金幣了,玩家沒積極性了。

異常檢測

只有請求數,沒有參考的上限值(saturation),也沒有成功率,沒有失敗率,怎麼檢測異常?

圖片

上圖的黃線是昨天的值,綠線是今天的值,大部分服務監控的曲線圖都長這樣。可以得出四個思路:

  • 曲線平滑:故障一般是對近期趨勢的一個破壞,視覺上來說就是不平滑
  • 絕對值的時間週期性:兩條曲線幾乎重合
  • 波動的時間週期性:假設兩個曲線不重合,在相同時間點的波動趨勢和振幅也是類似的
  • 有一個長度可觀的坑:當曲線開始回升到歷史範圍的時候,一般可以確認這個時間段是真的故障了

從這四種直覺展開,可以得出各種或複雜或簡單的演算法。下面要講的演算法都是非常簡單的,無需很高深的數學知識。

基於曲線的平滑性的檢測

這種檢測的根據是在一個最近的時間視窗,比如1個小時。曲線會遵循某種趨勢,而新的資料點打破了這種趨勢,使得曲線不光滑了。也就是說,這種檢測利用的是時間序列的temporal dependency,T對於T-1有很強的趨勢依賴性。業務邏輯上來說,8:00 有很多人登陸,8:01 也有很多人來登陸的機率是很高的,因為吸引人來登陸的因素是有很強的慣性的。但是7.1很多人來登陸,8.1也有很多人來登陸的慣性就要差很多。
基於近期趨勢做告警,就需要對曲線的趨勢進行擬合。擬合有兩種方式,moving average 或者 regression。這兩種擬合方式有不同的bias(傾向)。
圖片
這就是一種 moving average 的演算法圖,叫做 exponentially weighted moving average。它的計算非常簡單
圖片
x是實際值,s是ewma計算出來的平均值。也就是下一點的平均值是由上一點的平均值,加上當前點的實際值修正而來。這個修正的比例,就取決月這個alpha的decay factor的大小。視覺上來說就是ewma曲線是否緊跟實際曲線,也就是平滑程度。

圖片

有了平均值之後可以 計算方差 ,方差乘以一定的倍數可以得出對於振幅的容忍範圍。比較實際的值是否超出了這個範圍就可以知道是否可以告警了。超出了上界,可能是突然使用者量突然激增了。超出了下屆,可能是營銷活動結束了,使用者快速離開,也可能是光纖斷了,玩家掉線了。想要了解更多關於ewma的演算法細節:關注Baron Schwartz(http://www.slideshare.net/vividcortex/statistical-anomaly-detection-fo...)
moving average認為曲線是趨向於歷史的,如果曲線的勢頭是上升,那麼它認為下一個點應該是開始下降的。regression認為曲線是趨向於未來的,如果曲線的勢頭是上升,那麼它認為下一個點應該是保持這個上升勢頭。還有更復雜的模型是綜合了moving average和regression的。無論是哪種演算法,用過去10分鐘預測下10分鐘是不可能精確的。如果這種預測可以精確,那麼股神早就誕生了。使用moving average,可能會掩蓋故障產生的下降(因為其bias是下降)。如果使用regression,那麼又有可能把沒有上升得那麼快當成故障了(因為其bias是上升)。
這種基於近期趨勢計算方差的演算法還有一個缺陷是當前面幾個點振動很大的時候,方差值會被搞大。後面的故障就被掩蓋了,使得連續的故障點無法被檢測到。其實也就是演算法對於什麼是正常是沒有概念的,它認為過去的歷史就是正常。如果過去幾分鐘處於故障中,那麼故障的曲線就是正常。
實際使用中發現這種基於曲線平滑度的演算法的優點有:

  • 依賴的資料少,只需要近期的歷史,不依賴於週期性
  • 非常敏感,歷史如果波動很小,方差就很小,容忍的波動範圍也會非常小

缺點也是顯著的:

  • 過於敏感,容易誤報。因為方差會隨著異常點的引入而變大,所以很難使用連續三點才告警這樣的策略
  • 業務曲線可能自身有規律性的陡增和陡降

的使用方式是不用一根曲線做告警。結合幾條相關的曲線,如果同時出現平滑度破壞的情況,而且與業務規律的趨勢相背離(比如線上人數降低,登陸請求數增高)則可以認定為業務出現故障。

基於絕對值的時間週期性

圖片
上圖中不同的顏色代表了不同日期的曲線。很多監控曲線都有這樣以一天為週期的週期性(早上4點最低,晚上11點最高之類的)。一種利用時間週期性的最簡單的演算法

min(14 days history) * 0.6

對歷史14天的曲線取最小值。怎麼個取最小值的方法?對於12:05分,有14天對應的點,取最小值。對於12:06分,有14天對應的點,取最小值。這樣可以得出一條一天的曲線。然後對這個曲線整體乘以0.6。如果幾天的曲線低於這條參考線則告警。
這其實是一種靜態閾值告警的升級版,動態閾值告警。過去靜態閾值是一個根據歷史經驗拍腦袋的產物。用這個演算法,其實是把同時間點的歷史值做為依據,計算出一個最不可能的下界。同時閾值不是的一個,而是每個時間點有一個。如果1分鐘一個點,一天中就有1440個下界閾值。
實際使用中0.6當然還是要酌情調整的。而且一個嚴重的問題是如果14天曆史中有停機發布或者故障,那麼最小值會受到影響。也就是說不能把歷史當成正常,而是要把歷史剔除掉異常值之後再進行計算。一個務實的近似的做法是取第二小的值。
為了讓告警更加精確,可以累積計算實際曲線和參考曲線的差值之和。也就是相對於參考曲線下跌的面積。這個面積超過一定的值則告警。對於深度下跌,則累積幾個點就可以告警。對於淺度下跌,那麼多累幾個點也可以告警出來。翻譯成人話就是,一下在跌了很多,則很有可能是故障了。或者連續好久都偏離正常值,那麼也很有可能是出問題了。

優點:

  • 計算簡單

  • 可以確保發現大的故障,出了告警一定是大問題,可以直接打電話

缺點:

  • 依賴週期性的歷史資料,計算量大,而且無法對新接入的曲線告警

  • 非常不敏感,小波動無法發現

基於振幅的時間週期性

圖片
有些時候曲線是有周期性,但是兩個週期的曲線相疊加是不重合的。比如上圖這樣的,曲線整體的趨勢是網上的。兩個週期的曲線一疊加,一個會比另外一個高出一頭。對於這種情況,利用絕對值告警就會有問題。
比如今天是10.1日,放假第一天。過去14天的歷史曲線必然會比今天的曲線低很多。那麼今天出了一個小故障,曲線下跌了,相對於過去14天的曲線仍然是高很多的。這樣的故障如何能夠檢測得出來?一個直覺的說法是,兩個曲線雖然不一樣高,但是“長得差不多”。那麼怎麼利用這種“長得差不多”呢?那就是振幅了。
與其用x(t)的值,不如用x(t) - x(t-1)的值,也就是把絕對值變成變化速度。可以直接利用這個速度值,也可以是 x(t) - x(t-1) 再除以 x(t-1),也就是一個速度相對於絕對值的比率。比如t時刻的線上900人,t-1時刻的線上是1000人,那麼可以計算出掉線人數是10%。這個掉線比率在歷史同時刻是高還是低?那麼就和前面一樣處理了。
實際使用中有兩個技巧:可以是x(t) - x(t-1),也可以是x(t) - x(t-5)等值。跨度越大,越可以檢測出一些緩慢下降的情況。
另外一個技巧是可以計算x(t) -x(t-2),以及x(t+1) - x(t-1),如果兩個值都異常則認為是真的異常,可以避免一個點的資料缺陷問題。
優點:

  • 比絕對值要敏感
  • 利用了時間週期性,規避了業務曲線自身的週期性陡降

缺點:

  • 要求原曲線是光滑的
  • 週期性陡降的時間點必須重合,否則誤警
  • 按百分比計算容易在低峰時期誤警
  • 陡降不一定代表故障,由上層服務波動引起的衝高再回落的情況時有發生

這種異常告警演算法是比較優秀的。缺點也很多。所以可以進行一些修補湊合用。為了避免低峰時期,基於振幅百分比容易誤警,可以加入絕對振幅的下限。業務上來說,就是小波動如果相對比率大,但是絕對影響範圍小也是沒關係的。對於衝高回落的問題,可以判斷一下衝高的情況,對於衝高之後遮蔽一段時間。

基於曲線回升的異常判斷

圖片

圖片

當我們看見圖2的時候比圖1更確認是故障了。為什麼?因為圖2中有一個明顯的回升。演算法其實和人眼一樣。如果多等幾個時間點,發現曲線回升了可以更很準確地判斷“曾經”有一個故障。但是這種基於回升的異常檢測是沒有多少“告警”意義上的機制的。告警的作用就是讓人參與干預,去幫助曲線回升。如果曲線已經開始回升,再告警不是事後諸葛了嗎?
這種檢測的意義在於機器複製告警的確認。當我們需要統計誤警率,漏警率的時候。用另外一種視角的演算法重新跑一遍可以統計出很多原演算法的問題。同時也可以用半自動化的方式建立一個歷史故障的樣本庫。這個樣本庫可以變成更復雜的機器學習演算法的訓練集。

總結

Key take away

  • 高質量的告警是actionable的
  • 不應該用採集的難度決定你使用什麼指標去告警
  • 不要別人做什麼告警,你就做什麼,要做“真正”有用的告警:特別是cpu使用率告警
  • is work getting done:請求數 + 成功率
  • is the user having good experience:響應延遲
  • 只要採集對了指標,大部分時候告警不需要複雜演算法
  • 基於演算法的異常檢測:演算法不難,實在必要也是可以做到的


來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70013542/viewspace-3012393/,如需轉載,請註明出處,否則將追究法律責任。

相關文章