1.前言
随着網際網路的迅速發展,各個公司都建立了自己的監控體系,用于提前發現問題降低損失,攜程亦是如此。然而攜程的監控體系存在以下三個問題:
- 監控系統繁多
- 監控告警配置複雜
- 沒有統一規範
首先攜程目前光公司級别的監控系統就有三套,各個 BU 為了滿足自己的業務監控需求也陸續開發了許多自己的監控系統。其次這些監控系統都是基于規則來判斷是否存在異常,比如當滿足同環比連續幾個點上升或下降到使用者配置的門檻值時觸發告警。最後是沒有統一的規範,這裡指的是兩個規範,第一,沒有統一的規則告警配置規範,不同的監控系統都帶有不同的規則告警配置方式;第二,沒有統一的異常判斷規範,研發人員或 QA 人員都是根據自己對業務的了解,通過主觀判斷名額達到一定閥值時監控系統需要進行告警。
基于以上的三點問題給使用者帶來了諸多不便,首先是規則告警維護成本高,使用者時常需要基于多個監控系統以不同的方式配置規則告警,而且還需要根據告警的情況持續調整門檻值,導緻一個規則告警從配置到最終能夠産生較好的效果需要一個很長的周期。其次,基于規則告警往往表現不盡如人意,會導緻準确率低、覆寫率低和時效性低的三低狀況。使用者很多情況下為了提高異常的覆寫率降低漏報的情況,不得不将規則告警的閥值設定的非常敏感,雖然這樣能夠覆寫更多的異常場景,卻導緻了大量的誤報,規則告警的準确性也就大大折扣。

為了應對上述的諸多問題,攜程打造了自己的實時智能異常檢測平台 Prophet。簡單概括,Prophet 是一個基于時序類型資料、以平台為接入對象、去規則化為目标的異常檢測系統,基于深度學習算法實作異常的智能檢測,基于實時計算引擎實作異常的實時檢測,提供了統一的異常檢測解決方案。接下來的文章會詳細介紹我們是如何依次實作了異常的智能化、實時化檢測以及平台的建構。
2. 智能化
2.1 深度學習算法選擇
目前業界采用比較多的方式是引入統計分析的各種方法,框定一個滑動的樣本集,對這個樣本集進行一些資料處理和轉化,經過歸一化,去周期,去趨勢,再将最新采集到的資料點經過同樣的轉換,和樣本集的殘差序列的統計量進行比較,比如距離、方差、移動平均、分位數等,超出一定的範圍就判斷為異常,或是綜合各種離群點計算的方法來做個投票,多數算法認為異常則報異常。起初我們也借鑒了這種做法,卻發現雖然可以不用維護告警規則了,但報警的品質并沒有提升。
我們需要設計一套新的算法,降低報警總量到可以人工逐個處理的程度,同時不能以增加漏報真正的生産訂單故障為代價,并且這套算法的設計還不能太複雜,影響到告警的實時性,最好還能做到算法即服務,有較強的可移植性,提供給其他的監控系統使用。自然而然的,基于神經網絡的深度學習算法 成為我們進一步探索的工具。
RNN 算法比較适合處理序列變化的資料,符合我們時序特征的場景,但是存在梯度消失和過拟合的現象。而他的改進版 LSTM 算法,能夠通過控制傳輸狀态來選擇性地記住較重要的長期資料,能在更長的序列上有良好的表現,業界也有很多成功的應用。LSTM 算法的異常檢測方式是基于名額的曆史資料訓練出模型并基于現有資料預測名額未來的走勢,基于預測資料與現實資料各種偏差來判斷名額是否有異常。這樣好處在于每個名額都會訓練一個自己的模型,能夠達到很高的精度,但是也帶來了一定的弊端,需要消耗較多的訓練與檢測資源。
DNN 算法的檢測方式與 LSTM 的方式不同,我們基于小波變換算法提取監控名額不同頻域的特征喂給 DNN 模型,直接輸出是否存在異常。這種的好處在于一個 DNN 模型就能夠滿足所有異常檢測場景的需求,但是相對的特征工程也要複雜很多,我們需要大量的人工标記資料來提高模型的精度。
最後無論是基于 LSTM 算法還是 DNN 算法實作的異常檢測需要根據各自所需的不同場景來決定使用哪個。在攜程,對于最重要的訂單、支付類名額,我們都是采取 LSTM 算法,單個名額訓練單個模型,對于其他一些非重要的名額可以使用 DNN 算法。
2.2 模型訓練
標明好深度學習算法之後,我們也就開始嘗試模型的訓練。我們首先取得監控名額的曆史資料對其進行清洗,其中需要對一些空值進行插補,節假日資料對于資料模型的影響很大,導緻訓練出來的資料有偏差,我們也選擇性的剔除節假日期間的資料;如果曆史資料中的某個區間資料是異常區間,我們也需要使用預測值替換異常區間的數值。
做完資料清洗之後,也就需要實作特征工程。我們使用了多尺度滑動視窗時序特征的方法,将一個滑動視窗内的資料和前 n 個周期做統計量上的對比,均值、方差、變化率等這些,這樣基本上就可以把明顯的周期性和平穩型資料給分離出來。剩下的時序中,有些是波動很大的随機序列,有的則是帶有趨勢的周期性序列,通過時序分析法把周期性去掉,再用頻域分析嘗試分解成頻譜。對于帶有明顯頻譜的,則歸類為周期型時序,而頻譜雜亂的,則歸類為非周期性。
在做完特征提取與名額分類之後,我們也就根據名額的類型使用不同的算法進行模型訓練。我們根據線上的人工标注資料持續性的優化我們的模型。我們經曆過初期不停的調參和驗證之後,我們将模型訓練的頻率設為了兩周,我們每兩周重新走下圖中的整個流程,這個也是根據我們業務變更的頻率所做的考慮。
3. 實時化
3.1 Why Flink?
在解決了智能化異常檢測的問題後,我們開始考慮提高我們的時效性。以往的規則告警,從資料産生到落地到監控系統,再到觸發規則判斷,期間已經經曆了一定延遲。并且很多規則告警往往需要連續 3 個點或則 5 個點觸發下跌或上升規則判斷才會告警,這樣如果一個名額的采集粒度是一分鐘,那麼異常往往需要過好幾分鐘才會被發現。為了解決時效性的問題,我們嘗試引入實時計算引擎。現在常見的實時計算引擎有 Storm、Spark Streaming 以及 Flink,那麼為什麼我們最終選擇了 Flink?
首先第一點就是 Flink 提供了強大的容錯保障,所有的實時作業無論提供了多麼繁多的功能,如果在作業的容錯保障上做的不好,對于使用者都是不可接受的。我們的資料源是 Kafka,基于 Flink 的 Checkpoint 與 Kafka 的 Offset 回溯功能能夠實作資料源到執行引擎層面的 Exactly Once 的語義保證,基于幂等或事物保證最終輸出的 Exactly Once 語義。
第二點,Flink 提供了高效的狀态管理,我們在做異常檢測的時候需要儲存異常區間的預測資料用于下一輪的異常檢測,這個後續會講到。
第三點與第四點放在一起講就是,Flink 提供了基于 Event Time 的豐富視窗函數,Spark Streaming 雖然也提供了對視窗的支援,但是其本質上還都是基于 Processing Time 的資料處理。終上所述,我們最終選擇了 Flink 作為我們的實時計算引擎。
3.2 實時檢測
在選擇好實時計算引擎後,我們也就開始嘗試在 Flink 中加載 Tensorflow 的模型用來實時做異常檢測。首先我們将所有訓練好的 Tensorflow 模型以.pb 的格式上傳到 HDFS 并将新增或更新的模型配置更新到配置中心 QConfig 上。Flink 作業在啟動或運作中時,監聽配置中心中需要監控的名額并嘗試從 HDFS 上加載模型。由于後期模型較多,為了避免重複加載和負載均衡,所有名額會先根據 id keyBy 分發到不同的 TaskManager 上,每個 TaskManager 隻加載屬于自己那部分的模型。
模型加載完畢後,我們基于 Flink 滑動視窗與 Event Time 實作資料實時消費與預測。視窗滑動的時間為名額的時間粒度(下圖中為 1 分鐘),視窗長度為十個名額時間粒度(下圖中為 10 分鐘)。一個視窗中總計 10 條資料,我們采用前面 5 條資料預測第 6 個位置的資料,然後基于 2 到 4 的實際數值加上第 6 條的預測資料預測第 7 個資料。依此類推,最終我們擷取到了視窗中後 5 位的預測值與實際值,基于 5 個預測值與實際值對比檢測是否存在異常。
然而實際的消費過程中并不會像上面說的那麼簡單,首先一個視窗内可能存在缺失資料的情況,我們采用視窗内其餘資料的均值與标準差補齊。其次,在上個時間段如果存在異常,我們無法直接使用原始的值去預測數值,因為這個原始值可能是一個異常值,我們需要使用上個時間段的預測值來替換這個異常值,這樣能夠保證我們的預測線不被帶跑偏。上一個視窗的預測值我們采用 flink 中的 state 來存儲。
在取得目前視窗後 5 個預測值與實際值之後,我們就開始進異常檢測了。我們會根據異常的類型(比如上升或下降)與敏感度來做不同的判斷,下圖中的三個異常曲線分别對應了高中低三個敏感的場景,在使用高敏度時,可能隻要有一個下跌的抖動,我們可能就認為其是一個潛在的異常,中敏感度需要連續兩個下跌的情況,低敏感度則需在下降幅度非常大的情況下才會認定為潛在異常。
我們會基于預測值與實際資料的偏差來先做一個潛在判斷,當認定它是一個潛在異常時,我們會在基于預測值與曆史同期資料的均值與标準差做判斷,這樣最終得出目前的視窗是否存在異常。我們這邊在異常判斷的時候還是采用了統計學作為判斷方式,如果在樣本足夠的情況下,完全可以使用機器學習,訓練一個異常檢測模型來判斷是否存在異常。
4. Prophet
4.1 Prophet 系統架構
在講述完如何實作智能化與實時化異常檢測之後,相信大家對于 Prophet 已經有了一定的認知。下圖展示了整個 Prophet 平台的系統架構,首先是最底層的 Hadoop 叢集承擔了分布式存儲與資源排程的功能,HDFS 用來存儲 Tensorflow 訓練好的模型,所有 Flink 作業運作在 Yarn 叢集上。中間層的消息隊列承擔了實時資料源的作用,所有名額的曆史資料存儲在時序資料庫中,實時化與智能化檢測依托于 Flink 與 Tensorflow 兩套引擎實作。最上層的 Prophet 以平台的方式對外提供服務,Clog 用于日志存儲與排障,Muise 是我們的實時計算平台,Qconfig 用于存儲于監控名額相關的配置資訊,最後 Hickwall 用于監控作業的各項名額。
4.2 Prophet 操作流程
一個使用者想要配置智能告警隻需要做兩件事,首先在我們的平台上配置智能告警,由于我們大部分對接的是監控平台,是以使用者大多是在各個監控平台上配置智能告警,然後監控平台調用我們的服務注冊監控名額。然後使用者需要按照我們定義好的格式将原始資料發送到我們的 Kafka 消息隊列,這一步在對接平台時,也由平台做了,是以直接在我們平台上配置監控名額的使用者很少。當一個使用者注冊好監控名額後,我們平台會先檢測該名額的曆史資料是否足夠,如果足夠則觸發模型訓練的流程,訓練好的模型會上傳到 HDFS。如果曆史資料不足,Prophet 會持續實時存儲使用者名額的資料,當滿足資料量的需求時,重新觸發模型訓練。當模型訓練完成後,我們會更新配置中心,告知 Flink 作業有新的或更新的名額模型已經就位。
實時這塊的流程是 Flink 啟動或運作中一旦監聽到有新的或更新的模型,作業會重新加載模型。另外 Flink 會實時從 Kafka 中消費資料,實時的過模型做異常檢測,最終将異常告警回吐到 Kafka,各個平台消費自己的異常告警資料并給相關的負責人發送告警通知。
4.3 平台現狀
目前 Prophet 已經覆寫了攜程所有的業務線,接入了十餘個監控平台,其中包含公司級的監控系統 Sitemon 與 Hickwall,監控了 7000+ 個業務名額,包含訂單、支付、應用、服務等多種業務類型名額。
在平台運作的半年時間内,我們的算法能夠達到 90% 的召回率(也就是異常覆寫率);由于我們業務方需求是盡量覆寫更多的異常,不要漏報,是以我們的準确率保持在 75% 左右;在引入了 Flink 實時消費資料與檢測,極大的降低了我們告警的延遲,達到了毫秒級的延遲;對比規則告警,我們幫助使用者降低了 10 倍的告警數量,提升了 10 倍的使用者效率。
下圖展示了從 18 年 10 月 Prophet 上線以來至 19 年 4 月底,智能告警與規則告警對異常的覆寫率對比。總計發生 176 起異常,其中 Prophet 圖表中顯示的是覆寫了 90% 的異常,但其實真正的覆寫率要高于 90%,其中 18 個未覆寫異常有 15 個是由于初期算法一直處于調整階段導緻了漏報。在 19 年之後,我們的異常覆寫率能夠達到接近 100%。相比較規則告警,我們的覆寫率上升了 22%,及時的幫助使用者降低損失。
下圖展示了智能告警與規則告警在告警數量上的對比,規則告警的數量基本是智能告警的 2 到 5 倍,但是這并非是站在同一層面上的對比,其中智能告警的數量是基于 800 監控名額,而規則告警是基于 200 個監控,如果規則告警的名額數量與智能告警的持平,那智能告警降低的告警數量會更為顯著。告警數量對于使用者的效率提升是十分明顯的,以往使用者每天需要花費大量的精力去排查每一個告警郵件,在使用了智能告警後,這部分幫助使用者減少的時間是實實在在的效率提升。
5. 挑戰與展望
Prophet 在攜程投入生産使用已有半年之久,在這期間我們也遇到過形形色色的挑戰。
首先,基于 LSTM 算法的異常檢測方式存在一個明顯的弊端,我們需要對每一個名額訓練一個模型,這樣無論是模型訓練所需的資源以及實時作業加載模型所需的資源都消耗比較大。
其次,LSTM 算法對于波動劇烈的非周期型名額表現不是十分良好,有一些業務會不定期的做一些活動導緻業務名額的突增或突減,這種趨勢是無法從曆史資料中學習到。
然後,對于一些系統性能名額類型的資料也無需使用智能告警,規則告警可能更加友善,比如當伺服器的 cpu 使用率達到 95% 的時候就告警。
最後,節假日對于智能告警的影響十分之大,業務名額通常會在節假日前呈倍數的增長,假日期間又曾倍數的下降,這樣導緻了大量漏報或誤報。
針對以上的問題,我們也在持續的改進之中。首先,基于 DNN 算法的通用模型已經線上下陪跑了數月之久,雖然在精度上比 LSTM 算法的異常檢測方式稍有遜色,但在我們持續優化之後已經基本能夠 hold 住線上非重要名額的告警需求,實作單個模型監控數千個名額的功能,大大降低了資源損耗。我們在應對節假日對智能檢測影響時引入了增長系數的概念,用來拉升或降低預測值,并且采用一定方式将增長系數持續衰減,防止增長系數導緻預測值的跑偏。關于算法的細節以及各種場景下的應對方式由于篇幅關系無法在本篇文章中一一展開,如果對算法相關細節感興趣的朋友可以在評論區留言,我們這邊也會考慮讓算法同僚另起爐竈,詳細的介紹算法、特征工程等相關話題。
Prophet 後續也會陸續的接入攜程所有的監控系統,這也是我們一直努力在做的事。實時計算與人工智能不光在異常檢測這個場景下有很好的發揮,在很多其他的場景下也能夠有亮眼的表現,比如風控、個性化推薦、排序等,本篇文章也算是抛磚引玉,希望給大家能夠帶來一些其法,這樣可以将這套方式更多的使用在其他的場景下。
最後
GitHub Flink 學習代碼位址:https://github.com/zhisheng17/flink-learning