云原生在業界的發展非常迅猛且有廣泛的應用,Prometheus 作為云原生可觀測中關鍵的技術之一,在百度集團內部,以及金融等客戶中也逐步規模化應用。我本次將介紹百度云原生團隊在大客戶量級監控場景下所遇到的問題和解決方案。
百度監控發展史

百度的監控發展歷史大致分為四代。
統一監控平臺時代與開放監控平臺時代
百度最初的監控平臺始于 2007 年百度首次成立的專門運維平臺研發團隊,各個業務的運維監控統一由監控平臺提供能力。在統一監控平臺研發完成上線后,原先由各個業務自建腳本化監控采集逐步歸攏到統一建設的平臺中。
在統一監控平臺一段時間的發展過后,我們發現由單一的運維研發小組構建監控平臺并不容易。我們需要適配各類業務的不同監控需求、接口、數據源。因此,在 2012 年,我們構建了一個新的監控平臺以支持自定義與標準化能力。
-
一是用戶可通過監控平臺所支持各類型可配置、可自定義方式,以標準化接口的形式將數據統一接入;
-
二是提供不同語言、框架的監控指標標準庫,由各個業務自行在程序中集成,從而直接對接到監控平臺之中。
智能監控時代
隨著監控系統的不斷完善,各業務接入的指標逐漸增加完善。但在這個過程中,我們定位故障的效率卻沒有提升。我們發現所收集的指標并不是全部都有用的,用處最大的是業務類型黃金指標,如請求量、交易量、錯誤率、響應時間,這些指標可以從業務的層面真正反映出程序是否正常。與之相反,資源類的指標很難反映業務真實狀態,某臺機器的 CPU 異常業務可能已經自行容災,或者業務很早就異常了但資源類指標沒有任何變化。
于是在 2014 年,我們提出了基于業務指標特征構建的監控系統,即智能監控系統。圍繞業務監控,我們核心工作圍繞兩個方面開展:
-
智能異常監測。業務指標與傳統指標不同,不能限定一個固定的告警閾值,業務在不同時間段都會有波峰波谷及各種特殊情況,因此,我們基于機器學習、同環比分析,對歷史數據進行學習,預測指標趨勢與異常閾值,幫助發現業務指標是否真正異常。
-
智能故障診斷。借助業務的多維度特征,如接口信息、交易量、錯誤碼、用戶信息等維度,逐層分析和推斷故障根因。
云原生可觀測時代
業界內云原生技術在 2019 年已進入了較為廣泛的使用,百度內部也有大量業務開始了云原生架構升級。在這一階段,我們的監控平臺面臨了極大的挑戰。我們要對原有監控系統進行幾乎是顛覆性的改造,才能支持云原生的各類型服務發現、新數據模型、數據協議等。
但同時云原生所提出的兩點也與我們之前的發展思路不謀而合:
-
第一是監控標準化。Prometheus 及 OpenTelemetry 定義了一系列的監控標準方案,其中包括指標、Trace、日志等等,實現了業界數據的廣泛互通。這點非常重要,我們通過暴露 Prometheus 的監控接口,用 OpenTelemetry 的客戶端輸出 Trace、日志信息,節約了大量用于業務兼容適配的人力資源。
-
第二則是根因故障定位的進一步探索,加深指標、Trace 等標準規范的關聯分析能力。
Prometheus 業務監控遇到的挑戰
業務監控在 Prometheus 中的落地場景
業務監控的重要性遠超資源監控,是監控指標中的 Top 1。業務指標可落地的場景總結如下:
-
故障管理場景。其準確率要遠高于資源指標,其多維度屬性應能夠支撐故障分析及根因定位需求。
-
容量管理場景。基于業務指標如請求量及響應時間等數據,評估模塊容量是否應擴縮容;聯動擴縮容平臺,實現服務的自動擴縮容。
-
性能分析場景。依據業務指標中區分接口、階段等響應時間及請求量數據對應用的性能分析優化。
-
運營分析場景。可對流量成分進行分析,可用于 AB 對比實驗等場景。

在已有完善的監控系統前提下,我們仍選擇使用 Prometheus 是因為其指標類型天然適合業務監控場景:
-
Counter 以及基于 Counter 的復合指標類型、Histogram、Summary 等,很適合表達請求量、響應時間、交易量等指標,其多維度指標模型也可適用于業務指標中眾多的數據維度表達。
-
通過 PromQL 強大的數據分析能力,結合 Grafana 等數據可視化組件,上層業務得以快速地對數據進行分析,實時動態調整分析視圖。
-
將業務指標與下層各類資源、容器監控,以及移動端上指標統一匯總分析。

大規模業務監控在 Prometheus 架構下的挑戰
業務監控與普通資源監控不同,這類場景主要提出了幾點訴求:
-
高性能,其中又區分以下幾點:
-
指標承載能力。業務的大量維度致使指標規模巨大化,客戶的極端場景中甚至可能出現每秒億級的指標量。
-
查詢分析能力。以極強的數據分析及性能才能支撐全局動態分析請求量在多個維度內的對比關系。
-
數據存儲能力。業務監控中常常需要進行歷史數據對比,尤其是地圖類型業務,有時是數月,有時是幾年,甚至也有業務需要對五年前五一假期期間數據進行對比。
-
高可用。集群需盡可能自恢復單節點故障。在面對集群級別的整體故障后,應能進行人工止損及快速切換。
-
準確性。在大部分監控場景下雖然不具備多少關注度,但在金融類型或其他關注交易量的用戶中,他們對準確性有極其嚴苛的要求。

Prometheus 的標準開源方案
Prometheus 作為單機引擎,擁有集采集、存儲、查詢、計算、報警于一體的設計,非常適合于部署和運維。

受限于單機的采集能力,Prometheus 無法采集過多端口,單機的性能也限制了本地存儲量僅又單機本身的磁盤可用。此外,在高可用方面,如果單機故障,則采集失效;如果磁盤故障,則數據丟失無法恢復。

由官方提供的聯邦集群方案,讓上層的中央 Prometheus 在采集端進行數據匯總,采用雙采雙存方案,使用負載均衡器進行可用性切換,從而實現更高的可用性。該方案中的中央 Prometheus Server 仍是單機方案,因此同樣會存在存儲、寫入、性能的瓶頸及存儲量方面的挑戰。高可用方案中的單個集群故障期間的數據仍是丟失且無法恢復的。
大規模 Prometheus 業務監控解決方案
基于開源的方案無法有效解決我們所面對的問題,下面從三個角度入手,更詳細地解釋我們的考量方向。
高性能 Prometheus 實踐
我們要如何應對指標的采集量級?方案一是通過集群擴展,但集群資源有限度;方案二則是提升集群性能,但這種方案所帶來的提升也是有上限的。
指標降維
我們首先對用戶對這些指標的使用進行了分析。業務指標巨大的量級有兩部分組成,一是實例,大業務場景中模塊內可能部署了上千實例,百度集團內部也有上萬實例的極端情況;二是業務指標的維度,交易碼、錯誤碼、請求量、用戶來源省份、運營商等各類用于分析定位的維度,其結果也有十萬級別的指標量。
但在實際應用中,我們發現用戶并不需要如此大量的指標。物理維度的故障意味著某臺機器的指標異常、某個數據中心整體故障、某個機房存在問題等等,純業務的故障則是接口異常、交易類型異常等場景,故障很少會在物理維度與業務維度交叉的某一個維度。
對此,我們的第一個解決思路是去掉叉乘維度,通過增加預計算,將原始指標轉換為實例級或業務級聚合指標。通過這種方式,將原先的乘法指標量級變為加法指標量級,在應用中發送至后端的指標量減少了 90%以上。

需要注意的是,針對 Counter 類型指標的聚合處理。由于 Counter 指標有重置的情況,故直接加和是沒有意義的,故需要使用 rate()算子將 Counter 轉換為 Gauge 指標,再使用 sum 進行加和。
采集層聚合計算瓶頸優化
采集層預聚合的方法,在實際的應用中,會發現 CPU、內存使用率暴漲,導致整體采集性能下降。

問題在于預聚合的實現方式。 Prometheus 實現預聚合的方式是周期性加載全量數據進行運算,會在計算周期到到達的一瞬間會致使 CPU 和內存量的激增。
對此,我們通過一個 Adapter 服務以 sidecar 形式伴隨 Prometheus 采集端進行預聚合處理。在 Prometheus 正常采集完成寫入 wal 文件后,我們通過對該文件的讀取獲得最新數據。通過實現累加計數器,將周期性計算轉變為流式計算,有效降低降低 CPU、內存占用。
至于我們為什么選用 wal 而非 remote-write?這是因為 remote-write 需要對數據序列化和反序列化,而通過 wal 文件則可以將這部分開銷導致的性能損失也抹消掉。
采集端任務負載均衡
目前,我們已經可以讓后端接收到真正有用的數據了。但就如之前所講,單一的采集端無法承載所有指標的采集,我們需要借助集群,用基于分片的方式采集所有指標。

這一階段難點在于分片。分片一般以探測目標數為基礎,但在實際的應用中,不同的業務模塊產出指標量級不同,帶來采集端的負載嚴重不均。針對這個問題,我們利用主動的探測服務探測應用所產出的指標量,并基于該指標量動態分配每個 Prometheus 所采集的目標,實現按照指標量進行負載均衡。
流式計算
業務指標中存在很多需要數據分析的場景,我們可能仍需要面對十萬、甚至百萬級別數據量的聚合計算問題。一般場景下這類性能問題可通過預計算解決,將一次性查詢計算時的資源消耗拆分為多個周期進行。
但 Prometheus 周期性的查詢存儲的預計算實現方式,在指標量級到達一定程度時仍有可能造成存儲服務的宕機。
針對這種情況,我們的解決方案思路與先前類似:將預先計算環節從 Prometheus 中移出。為此,我們通過 Flink 實現了流式計算引擎,讀取 Prometheus 配置的所有 Recording Rule 并將其生效在 Flink 計算引擎內,真正為存儲引擎減負。

那么要如何在 Flink 計算引擎中實現 Prometheus 的算子?如果我們僅僅還原 Prometheus 的原有實現,就沒有帶來任何性能方面的提升。因此,我們需要基于流式計算以及 Flink 特點對 Prometheus 的算子進行改造。
所做的改造有三種:
-
基于 groupby 對聚合計算拆分。根據 groupby 結果維度進行并行化哈希計算,將計算分散到 Flink 集群中。
-
若 groupby 之后無法進一步拆分的數據仍然很多,則在所有 Flink 集群中首先執行本地數據聚合計算,再將計算結果匯總,避免造成局部熱點。
-
通過實現 sum()、sum(rate()) 等累加算子,每次僅加算對應數據到計數器中,而不是緩存所有數據,將內存、計算開銷分攤。
時序數據降采樣
業務數據對比往往要用到歷史數據。直接執行大跨度查詢必定導致存儲系統宕機;此外,大量數據存儲會導致不必要的高磁盤成本。

我們為此所設計的降采樣方案中包含五分鐘或一小時這兩個降采樣級別,根據用戶查詢的時間范圍動態選擇需要查詢的數據。我們也可以基于不同采樣級別或特定指標配置存儲有效期,從而降低磁盤成本。
在這套方案中我們需要讓 Prometheus 的算子與降采樣方案進行適配。常用 over_time 類型算子(sum_over_time、count_over_time 等)一般會用于 Gauge 指標,而降采樣中實際存儲的是周期內對應算子值(sum、count、avg、max、min 等),在對應算子輸入時降采樣會查詢對應值以確保最終統計數據的精確性。至于 Counter 類型算子(Increase、rate 函數等),因為直接將 Counter 值存儲毫無意義,因此我們在此需要存儲 Counter 的增量值,從而在算子輸入時通過增量值返回準確的數據。
高可用 Prometheus 實踐
采集層高可用容災
高可用一方面在于集群內組件的高可用,能夠在故障后自動恢復,從而減少人為干預。我們的 Flink、Kafka,還是實際存儲、遠程存儲,均是分布式解決方案,單實例故障后可自動容災,因此不需要過多地關注。

至于采集端,我們通過雙采,以單發模式(主備模式)向后端發送數據,借用 Kubernetes 的 Lease 組件進行選主,每個 Prometheus 上的 sidecar 對實時數據的接入情況進行匯報,若無實時數據則判定該實例故障,從而進行切主。
計算與存儲高可用保障

雖然 Kafka、Flink、TSDB 都是高可用,但卻并不能保證數據不會遺失。因此,在 Flink 或 TSDB 宕機恢復后,我們還會基于已經引入的 Kafka 實現數據重傳。因為 Flink 進行計算時會周期產出數據,如果通過 Kafka 重傳的數據不滿一周期,那么我們將向前推移多個周期并丟棄第一周期數據,從而保證最終計算數據在多次執行后均能保持一致性。
兩地三中心高可用

為保證整體集群的無故障,我們與金融行業客戶一同構建了“兩地三中心”方案。在同城構建兩套中心,數據同時發往兩套中心且其中數據完全一致,實現同城雙活。單中心故障的情況下,通過切換最終儀表盤查詢入口以獲取最新正確數據。異地保障則配合客戶業務,搭建一套實時存活災備集群,在業務流量切換至異地災備中心時,通過災備中心監控服務對其監控。
Prometheus 的數據準確性保障
通常監控的應用場景中對準確性沒有過多的要求,更多還是要靠其發現故障。但在金融之類場景中,則往往對準確性有極其嚴苛的要求。那么 Prometheus 能做到這點嗎?根據官方文檔, Prometheus 不適用于需求百分百精確性的場景。

Prometheus 的誤差主要來自兩個方面。
-
客戶端程序生成相關計數器在進程推出重啟后,我們無法即使拉取到內存中未采集到的數據。對此,我們只能提高采集周期,用更頻繁的指標采集來減少數據損失。
-
其次,Prometheus 本身的算子實現也可能導致誤差出現。這種算子帶來的誤差,是我們重點要解決的問題。

Prometheus 的 increase/rate 首點忽略會導致數據不準。舉例來說,上圖進程中的兩條曲線中,我們首先在進程啟動時收到五次失敗請求,在第 20 秒時收到 10 次成功請求,在第 20 秒時收到五次失敗請求,可以看出 Exporter 所吐出的數據隨時間變化情況。
我們期望能獲得 10 次失敗 10 次成功,共計 20 次總請求數,但 Prometheus 實際計算會得出 5,這是因為每條曲線的第一個點都被忽略了。成功率的計算同理,我們期望的成功率為 50%,但最終計算卻是 0%,因為成功曲線首次收到的 10 次成功請求沒有被統計到。
在某些場景下,這一問題會被擴大化。正常服務的運行中很少會發生錯誤,而錯誤一旦發生則必定是第一次出現,那么這次數據 Prometheus 雖然能采集到,但我們卻看不到,導致故障的漏報或延遲。
Prometheus 的首點忽略主要為解決在應用啟動后進行監控采集,采集到 Counter 的中間值問題,此時采集到的是業務自啟動開始到當前的累加值,并不只有當前周期的數據,因此 Prometheus 會對其忽略。對此,我們的解決方案針對 Prometheus 的這個策略,我們將應用 Target 采集狀態記錄下來,Target 第一次采集之后,所有點都可判斷為是正常新增點。基于正確新增點,我們將首點減 0 即可得到當前點的增量。

除此之外, Prometheus 中還存在許多近似計算。以 increase/rate 為例,其計算過程包含的擬合計算可能導致統計數據中實際的正整數被以小數形式輸出,甚至可能損失精度。對此,我們可以將 Counter 類型數據轉換為 Gauge 類型,再使用 over_time 類型算子計算以避免誤差。
此外,sum_over_time 實際是雙向閉區間統計,對于定期從 Prometheus 采集數據并自行進行匯總計算的報表系統來說,中間疊加的部分會很多。解決方法是可以新增單向閉區間算子,業務上使用新的算子來進行計算。
總結
在 Prometheus 的解決方案中主要介紹了三部分:
-
在高性能部分重點提出了數據降維、動態分片采集、流式計算、存儲降采樣方案
-
在高可用方面介紹了集群的高可用,以及兩地三中心的跨集群高可用方案
-
在數據準確性方面,我們探討了 Prometheus 算子帶來的誤差及可能解決方案

這些方案不僅可以有機整合為整體,也能單獨應用。希望能拋磚引玉,給大家帶來一些 Prometheus 應用過程中解決問題的思路。