部門的業務線愈來愈多,任何一個線上運行的應用,均可能由於各類各樣的緣由出現問題:好比業務層面,訂單量比上週減小了,流量忽然降低了;技術層面的問題,系統出現 ERROR ,接口響應變慢了。拿大交通業務來講,一個明顯的特色是依賴不少供應商的服務,因此咱們還須要關注調用供應商接口是否出現異常等等。數據庫
爲了讓大交通下的各業務線都可以經過報警儘早發現問題、解決問題,進而提高業務系統的服務質量,咱們決定構建統一的監控報警系統。一方面在第一時間發現已經出現的系統異常,及時解決;另外一方面儘早發現一些潛在的問題,好比某個系統目前來看沒有影響業務邏輯的正常運轉,可是一些操做耗時已經比較長等,這類問題若是不及時處理,未來就極可能影響業務的發展。緩存
本文主要介紹馬蜂窩大交通業務監控報警系統的定位、總體架構設計,以及咱們在落地實踐過程當中的一些踩坑經驗。bash
咱們但願監控報警系統主要具有如下三個能力:微信
1. 經常使用組件自動報警:對於各業務系統經常使用的框架組件(如 RPC ,HTTP 等)建立默認報警規則,來方便框架層面的統一監控。網絡
2. 業務自定義報警:業務指標由業務開發自定義埋點字段,來記錄每一個業務和系統模塊的特殊運行情況。架構
3. 快速定位問題:發現問題並非目的,解決纔是關鍵。咱們但願在完成報警消息發送後,可讓開發者一目瞭然地發現問題出如今什麼地方,從而快速解決。app
在這樣的前提下,報警中心的總體架構圖和關鍵流程以下圖所示:框架
縱向來看,Kafka 左側是報警中心,右側是業務系統。運維
報警中心的架構共分爲三層,最上層是 WEB 後臺管理頁面,主要完成報警規則的維護和報警記錄的查詢;中間層是報警中心的核心;最下面一層是數據層。業務系統經過一個叫作 mes-client-starter 的 jar 包完成報警中心的接入。分佈式
咱們能夠將報警中心的工做劃分爲五個模塊:
咱們採用指標採集上報的方式來發現系統問題,就是將系統運行過程當中咱們關注的一些指標進行記錄和上傳。上傳的方式能夠是日誌、 UDP 等等。
首先數據收集模塊咱們沒有重複造輪子,但是直接基於 MES (馬蜂窩內部的大數據分析工具)來實現,主要考慮下面幾個方面的緣由:一來數據分析和報警在數據來源上是類似的;二來能夠節省不少開發成本;同時也方便報警的接入。
那具體應該採集哪些指標呢?以大交通業務場景下用戶的一次下單請求爲例,整個鏈路可能包括 HTTP 請求、Dubbo 調用、SQL 操做,中間可能還包括校驗、轉換、賦值等環節。一整套調用下來,會涉及到不少類和方法,咱們不可能對每一個類、每一個方法調用都作採集,既耗時也沒有意義。
爲了以最小的成原本儘量多地發現問題,咱們選取了一些系統經常使用的框架組件自動打點,好比 HTTP、SQL、咱們使用的 RPC 框架 Dubbo ,實現框架層面的統一監控。
而對於業務來講,每一個業務系統關注的指標都不同。對於不一樣業務開發人員須要關注的不一樣指標,好比支付成功訂單數量等,開發人員能夠經過系統提供的 API 進行手動埋點,本身定義不一樣業務和系統模塊須要關注的指標。
對於採集上來的動態指標數據,咱們選擇使用 Elasticsearch 來存儲,主要基於兩點緣由:
**一是動態字段存儲。**每一個業務系統關注的指標可能都不同,每一箇中間件的關注點也不一樣,因此埋哪些字段、每一個字段的類型都沒法預知,這就須要一個能夠動態添加字段的數據庫來存儲埋點。Elasticsearch 不須要預先定義字段和類型,埋點數據插入的時候能夠自動添加。
二是可以經得起海量數據的考驗。每一個用戶請求進過每一個監控組件都會產生多條埋點,這個數據量是很是龐大的。Elasticsearch 能夠支持大數據量的存儲,具備良好的水平擴展性。
此外,Elasticsearch 還支持聚合計算,方便快速執行 count , sum , avg 等任務。
有了埋點數據,下一步就須要定義一套報警規則,把咱們關注的問題量化爲具體的數據來進行檢查,驗證是否超出了預設的閾值。這是整個報警中心最複雜的問題,也最爲核心。
以前的總體架構圖中,最核心的部分就是「規則執行引擎」,它經過執行定時任務來驅動系統的運行。首先,執行引擎會去查詢全部生效的規則,而後根據規則的描述到 Elasticsearch 中進行過濾和聚合計算,最後將上一步聚合計算得結果跟規則中預先設定的閾值作比較,若是知足條件則發送報警消息。
這個過程涉及到了幾個關鍵的技術點:
1). 定時任務
爲了保證系統的可用性,避免因爲單點故障致使整個監控報警系統失效,咱們以「分鐘」爲週期,設置每一分鐘執行一次報警規則。這裏用的是 Elastic Job 來進行分佈式任務調度,方便操控任務的啓動和中止。
2). 「三段式」報警規則
咱們將報警規則的實現定義爲「過濾、聚合、比較」這三個階段。舉例來講,假設這是一個服務 A 的 ERROR 埋點日誌:
app_name=B is_error=false warn_msg=aa datetime=2019-04-01 11:12:00
app_name=A is_error=false datetime=2019-04-02 12:12:00
app_name=A is_error=true error_msg=bb datetime=2019-04-02 15:12:00
app_name=A is_error=true error_msg=bb datetime=2019-04-02 16:12:09
複製代碼
報警規則定義以下:
過濾:經過若干個條件限制來圈定一個數據集。對於上面的問題,過濾條件多是:app_name=A , is_error=true , datetime between '2019-14-02 16:12:00' and '2019-14-02 16:13:00'.
聚合:經過 count,avg,sum,max 等預先定義的聚合類型對上一步的數據集進行計算,獲得一個惟一的數值。對於上面的問題,咱們選擇 count 來計算出現 ERROR 的次數。
比較:把上一步獲得的結果與設定的閾值比較。
對於一些複雜條件的報警,好比咱們上邊提到的失敗率和流量波動,應該如何實現呢?
假設有這樣一個問題:若是調用的 A 服務失敗率超過 80%,而且總請求量大於 100,發送報警通知。
咱們知道,失敗率其實就是失敗的數量除以總數量,而失敗的數量和總數量能夠經過前面提到的**「過濾+聚合」**的方式獲得,那麼其實這個問題就能夠經過以下的公式描述出來:
failedCount/totalCount>0.8&&totalCount>100
複製代碼
而後咱們使用表達式引擎 fast-el 對上面的表達式進行計算,獲得的結果與設定的閾值比較便可。
3) 自動建立默認報警規則
對於經常使用的 Dubbo, HTTP 等,因爲涉及的類和方法比較多,開發人員能夠經過後臺管理界面維護報警規則,報警規則會存儲到 MySQL 數據庫中,同時在 Redis 中緩存。
以 Dubbo 爲例,首先經過 Dubbo 的 ApplicationModel 獲取全部的 provider 和 consumer,將這些類和方法的信息與規則模板結合(規則模板能夠理解爲剔除掉具體類和方法信息的規則),建立出針對某個類下某個方法的規則。
好比:A 服務對外提供的 dubbo 接口/ order / getOrderById 每分鐘平均響應時間超過 1 秒則報警;B 服務調用的 dubbo 接口/ train / grabTicket /每分鐘範圍 false 狀態個數超過 10 個則報警等等。
目前在報警規則觸發後主要採用兩種方式來發生報警行爲:
郵件報警:經過對每一類報警制定不一樣的負責人,使相關人員第一時間獲悉系統異常。
微信報警:做爲郵件報警的補充。
以後咱們會持續完善報警行爲的策略,好比針對不一樣等級的問題採用不一樣的報警方式,使開發人員既能夠迅速發現報警的問題,又不過多牽扯在新功能研發上的精力。
爲了可以快速幫助開發人員定位具問題,咱們設計了命中抽樣的功能:
首先,我把命中規則的 tracer_id 提取出來,提供一個連接能夠直接跳轉到 kibana 查看相關日誌,實現鏈路的還原。
其次,開發人員也能夠本身設置他要關注的字段,而後我會把這個字段對應的值也抽取出來,問題出在哪裏就能夠一目瞭然地看到。
技術實現上,定義一個命中抽樣的字段,這個字段裏面容許用戶輸入一個或者多個 dollar 大括號。好比咱們可能關注某個供應商的接口運行狀況,則命中抽樣的字段可能爲下圖中上半部分。在須要發送報警消息的時候,提取出裏面的字段,到 ES 中查詢對應的值,用 freemarker 來完成替換,最終發送給開發人員的消息是以下所示,開發人員能夠快速知道系統哪裏出了問題。
大交通業務監控報警系統的搭建是一個從 0 到 1 的過程,在整過開發過程當中,咱們遇到了不少問題,好比:內存瞬間被打滿、ES 愈來愈慢、頻繁 Full GC ,下面具體講一下針對以上幾點咱們的優化經驗。
1. 內存瞬間被打滿
任何一個系統,都有它能承受的極限,因此都須要這麼一座大壩,在洪水來的時候可以攔截下來。
報警中心也同樣,報警中心對外面臨最大的瓶頸點在接收 Kafka 中傳過來的 MES 埋點日誌。上線初期出現過一次因爲業務系統異常致使瞬間大量埋點日誌打到報警中心,致使系統內存打滿的問題。
解決辦法是評估每一個節點的最大承受能力,作好系統保護。針對這個問題,咱們採起的是限流的方式,因爲 Kafka 消費消息使用的是拉取的模式,因此只須要控制好拉取的速率便可,好比使用 Guava 的 RateLimiter :
messageHandler = (message) -> {
RateLimiter messageRateLimiter = RateLimiter.create(20000);
final double acquireTime = messageRateLimiter.acquire();
/**
save..
*/
}
複製代碼
2. ES 愈來愈慢
因爲 MES 日誌量比較大,也有冷熱之分,爲了在保證性能的同時方便數據遷移,咱們按照應用 + 月份的粒度建立 ES 索引,以下所示:
3. 頻繁 Full GC
咱們使用 Logback 做爲日誌框架,爲了可以蒐集到 ERROR 和 WARN 日誌,自定義了一個 Appender。若是想蒐集 Spring 容器啓動以前(此時 TalarmLogbackAppender 還未初始化)的日誌, Logback 的一個擴展 jar 包中的 DelegatingLogbackAppender 提供了一種緩存的方式,內存泄漏就出在這個緩存的地方。
正常狀況系統啓動起來以後,ApplicationContextHolder 中的 Spring 上下文不爲空,會自動從緩存裏面把日誌取出來。可是若是由於種種緣由沒有初始化這個類 ApplicationContextHolder,日誌會在緩存中越積越多,最終致使頻繁的 Full GC。
解決辦法:
1. 保證 ApplicationContextHolder 的初始化
2. DelegatingLogbackAppender 有三種模式:OFF SOFT ON ,若是須要打開,儘可能使用 SOFT模式,這時候緩存被存儲在一個由 SoftReference 包裝的列表中,在系統內存不足的時候,能夠被垃圾回收器回收掉。
目前這個系統還有一些不完善的地方,也是將來的一些規劃:
更易用:提供更多的使用幫助提示,幫助開發人員快速熟悉系統。
更多報警維度:目前支持 HTTP,SQL, Dubbo 組件的自動報警,後續會陸續支持 MQ,Redis 定時任務等等。
圖形化展現:將埋點數據經過圖形的方式展現出來,能夠更直觀地展現出系統的運行狀況,同時也有助於開發人員對於系統閾值的設置。
總結起來,大交通業務監控報警系統架構有如下幾個特色:
支持靈活的報警規則配置,豐富的篩選邏輯
自動添加經常使用組件的報警,Dubbo、HTTP 自動接入報警
接入簡單,接入 MES 的系統均可以快速接入使用
線上生產運維主要作 3 件事:發現問題、定位問題、解決問題。發現問題,就是在系統出現異常的時候儘快通知系統負責人。定位問題和解決問題,就是可以爲開發人員提供快速修復系統的必要信息,越精確越好。
報警系統的定位應該是線上問題解決鏈條中的第一步和入口手段。將其經過核心線索數據與數據回溯系統( tracer 鏈路等),部署發佈系統等進行有機的串聯,能夠極大提高線上問題解決的效率,更好地爲生產保駕護航。
無論作什麼,咱們最終的目標只有一個,就是提升服務的質量。
本文做者:宋考俊,馬蜂窩大交通平臺高級研發工程師。
(題圖來源:網絡)