B 站監控 2-0 架構落地實踐

衆所周知,Metrics 指標數據是可觀測重要的基石之一,在 2021 年底的時候,B 站基於 Promtheus+Thanos 方案,完成了統一監控平臺的落地。但隨着 B 站業務迅猛發展,指標數據級也迎來了爆炸式增長,不僅給現有監控系統的穩定 (可用性, 雲上監控數據質量等) 帶來衝擊,也無法滿足公司層面可觀測穩定性建設 (1-5-10) 目標。當前架構面臨的痛點總結如下:

  1. 穩定性差: 由於當時告警計算是基於 Promtheus 本地計算的,target 監控實例在調度時,一個應用的所有實例必須要被同一個 Promtheus 採集, 每當一些大應用發佈時,pod name 等一些元數據指標 label 就會發生變化,重新構建 timeseries 索引時,會佔用大量內存,導致 Promtheus oom。

  2. 用戶查詢體驗差: 頻發的 oom 告警,經常導致數據斷點,再加上 Promtheus+Thanos 查詢性能性能有限,經常出現面板查詢慢 / 超時,open api 查詢失敗,誤告警等。

  3. 雲上監控數據質量差: 由於我們使用了許多不同廠商的雲主機,即使同一廠商也有多個賬號和不同地域,有自建可用區和雲上可用區打通的,也有不打通的,有專線的,也有非專線的,網絡拓撲復雜,經常出現因網絡不通導致無監控數據。同時每個賬號下都是一套獨立的 Promtheus 採集,因此存在多個雲監控數據源,用戶很難選擇需要的雲數據源。

2.0 架構設計

設計思路

  1. 採集存儲分離: 由於 Promtheus 是採集存儲一體的,導致我們在 target 監控實例調度分發時,很難快速彈性擴縮,同時也不太容易調度到不同的採集節點。所以要實現採集存儲分離的架構,target 實例可以動態調度,採集器可以彈性擴縮。

  2. 存算分離: 由於 Promtheus 也是存算一體的,但隨着業務發展,指標數據量級和計算需求往往不是線性關係的,比如在存儲資源增加不到一倍的情況,計算資源 (查詢需求) 需要兩倍以上,因此,如果按照存算一體的方式採購服務器資源,勢必造成一些資源浪費。所以要實現存算分離的架構,在寫入,存儲和查詢都可以分別進行彈性擴縮。

  3. 時序數據庫選型: 我們瞭解到 VictoriaMetrics(以下簡稱 VM) 已經被越來越多的公司做爲時序數據庫,同時經過我們的調研,在寫入 & 查詢性能,分佈式架構,VM 運維效率都滿足我們的需求。(關於 VM 一些選型優勢,本文不在討論,下文會有介紹對 VM 的一些查詢優化)

  4. 單元化容災: 由於我們是大多數場景 (95%) 基於 pull 模式的,每個 target 監控實例需要按照一定的調度規則,分配到不同的採集器。但是之前沒有一個標準,有的場景按照 cluster 維度調度,有的場景按照 idc 調度,有的按照 instance name 維度,導致指標數據無法做到從採集 ->傳輸 ->存儲→查詢 整個鏈路單元內閉環。因此,我們制定了一個新標準,全部按照 zone 維度調度,可實現同 zone 下,全鏈路單元內容災。

基於以上核心的設計思路,設計了監控 2.0 架構。首先先看下整體的功能架構:

功能架構概覽

通過上面的功能架構圖可以看出,Metrics 指標數據,不僅應用在大盤、告警等基礎場景,一些業務場景、發佈平臺都會依賴指標數據,做一些流程決策。因此 2.0 架構的落地,存在以下幾個方面的挑戰:監控系統自身穩定性,數據可用性,查詢性能,故障爆炸半徑等。

整體架構

如上是整體的技術架構,下面重點從數據來源,數據採集,數據存儲,和數據查詢方面介紹:

數據來源

從大的分類來看,監控覆蓋場景主要分爲 paas 層 (應用監控,在 / 離線組件和一些中間件監控) 和 iaas 層(自建機房服務器 / 雲主機,容器監控和網絡監控)。由於之前是基於 pull 的方式發現 target 監控實例,主要存在兩方面問題:

  1. 發現 target 監控實例存在延遲:因爲 pull 的方式有一定的同步週期,比如 30s, 當一個新的監控實例出現,需要 30s 才能發現,因此看監控指標要比預期晚 30s。

  2. 運維成本較大:當 pull 業務方提供的接口有問題時,業務方一般無法第一時間感知問題,需要我們去主動協同處理。

針對上面問題,我們將 pull 方式改成 push 方式,讓業務方主動 push 需要被監控的 target 實例。這樣可以實時的 push, 解決因 pull 方式存在的 30s 延遲,同時,爲了讓各個業務方更方面的管理 target 實例,在指標平臺提供按集成任務申請監控接入,並且實時顯示 target 實例採集狀態,採集數量和採集耗時,進一步提升監控接入狀態的可見性。

數據採集

調度層

調度層主要負責採集 job 配置的生成和 target 監控實例的分發。爲了實現單元內鏈路全閉環,調度層分爲一級調度 (所有采集配置) 和二級調度(本機房內採集配置)

一級調度 (Master)

  1. 從數據庫中,拿到全量的採集 job 配置和 target 監控實例。根據採集調度配置 (zone 維度調度),在內存中構建各個二級調度所需的採集配置。

  2. 爲了保證 Master 數據高可用,當訪問依賴數據庫異常,內存快照數據不更新。同時在其他異常的場景下,我們也做了一些內存快照數據保護策略:如,當一次性刪除 > 5k targets 的 job, 一次更新 diff 量減少 > 5k targets 時,平臺會攔截保護,做 doubble check, 防止用戶誤操作。

  3. 目前通過一些手段,異步,內存 cache, 多協程等方式,Master 全量調度時間從 50s 降低到 10s 內。

二級調度 (Contractor)

  1. 根據採集集羣名稱 + 版本號定時去 Master 拿本機房的採集配置。設計多套二級調度,爲了防止故障爆炸半徑。

  2. 拿到本機房採集配置後,根據 Collector 心跳,拿到當前 health 採集節點 (Collector),根據實例和容量維度進行調度,將採集配置分給對應的採集節點。

  3. 當 Master 採集配置或採集節點有更新時,會觸發新一輪的調度。爲了保證 target 不隨機調度,在實現調度算法時,已經分配給某個採集節點的配置,下次調度時,還是會優先拿到該配置。

  4. 當 Contractor 應用發版,重啓等場景時,怎麼保證指標數據無斷點或抖動呢?
    在 Contractor 重啓時, 內存會維護一個全局 state 變量,等待 Collector 全部上報 targets 完成後,纔開始進行 targets 調度,當調度完成後,state 設置爲 ready,Contractor 接口才會對 Collector 提供訪問。

採集器

  1. 採集器 Collector 是基於 vmagent 封裝了一層。主要有兩個功能,一個是定時上報心跳給 Contractor, 二是拿到相關採集配置,call reload api,觸發 vmagent 開始採集。

  2. 當我們灰度了一些量後,發現 vmagent 佔用的內存較高,通過 heap pprof 發現, 在每次 pull 抓取上報的指標消耗內存較多,後面開啓流式採集 promscrape.streamParse=true 後,內存降低 20% 左右。

  3. vmagent 自身會有隨機 (採集間隔時間) 平滑 load 機制。比如我們採集間隔配置了 30s, 當 vmagent 拿到配置時,一個 target 最慢要 30s 纔會有指標數據。所以當 Collector 擴 / 縮容時,target 會漂移到其他 Collector 採集時,會出現指標斷點。因此我們設計了一個機制,Collector 監聽到退出信號後,不立即退出,只是停止上報心跳,同時繼續採集一個週期後,纔會退出,這樣可以保證指標無斷點。

數據存儲

我們通過 vmstorage 進行指標的存儲。由於 vmstorage 涉及到存儲細節比較多,這裏主要介紹索引結構的存儲。首先認識下 vmstorage 當中幾個核心類型。

4 byte account id | 4 byte projectid | metricname(__name__) | 1 | tag1 k | 1 | tag1 v | 1 | .... | tagn k | 1 | tagn v | 1 | 2 |
4 byte accountid | 4 byte projectid | 8 byte metricname(__name__) id | 4 byte job id | 4 byte instance id | 8 byte metricid

TSID 與 MetricId 都是一簇 ts 的唯一鍵,區別在與 MetricId 只是 8byte 的時間戳,而 TSID 的序列化信息在包含 MetricId 之外還包含其他非常多的標識信息,當 TSID 通過字典序排序之後,同租戶相同名字的指標將會在分佈上連續在一起,綜合指標查詢的特點,查詢的結果總是集中在同一個租戶下的同名指標,並且都會有相似的 label 條件。所以 TSID 實際上是 vmstorage 當中 data 目錄下的 key, 在索引部分的查詢核心是根據查詢條件得到最後的 TSID。

在上述三個類型下,就能形成如下幾個抽象索引結構:

MetricName -> TSID:0 | MetricName | TSID
MetricId -> MetricName:3 | 4 byte accountid | 4 byte projectid | MetricId | MetricName
MetricId -> TSID:2 | 4 byte accountid | 4 byte projectid | MetricId | TSID

同時核心的倒排索引結構抽象如下:

1 | 4 byte accountid | 4 byte projectid | metricname(__name__) | 1 | MetricId
1 | 4 byte accountid | 4 byte projectid | tag k | 1 |  tag v | 1 | MetricId

結合倒排索引和索引 3 就可以得到這麼一條流程:

根據查詢攜帶的租戶信息和指標名稱與 label 信息,就可以組成所需要的倒排索引前綴,在字典序排序的索引下,就可以通過二分查找查得所需要的 MetricId 集合。將多個條件從倒排索引中查詢到的 MetricId 求交集,就能得到符合 label 查詢條件的 MetricId 集合,就能通過索引 3 組裝成響應的索引前綴查詢到最後符合條件的 TSID。在 vmstorage 中執行查詢時,索引部分的核心目標就是根據條件求得最後所需要的 TSID 集合。

結合以上的抽象索引,vm 通過基於前綴的壓縮進行存儲來達到減少磁盤佔用的目的。因此相比於 Prometheus,磁盤存儲成本大概有 40% 的降幅。

在正常的 vmstorage 使用中,vmstroage 提供了十分優秀的性能,以某一個集羣爲例子,單臺 48c 256g 的 vm stroage 日常足夠支撐每秒 40w 點的寫入, 2w qps 的查詢。在這一過程中,通過調整應用的 gogc 來平衡 cpu 佔比來優化 vm 的資源使用。默認情況下,vmstorage 的 gogc 的值給到了 30,可能會存在比較頻繁的 gc,在查詢寫入飽和的情況下,如果與指標點的 merge 發生在一起,那麼查詢將會在短時間內存在比較大的握手延遲而導致失敗。在內存夠用的情況下,調大 vmstorage 的 gogc ,可以以一定的內存爲代價換來更穩定 vmstorage 運行。

數據查詢

promql 自動替換增強

在我們通過 grafana 訪問 victoriametrics 進行日常指標查詢的過程中,經常會遇到某些 panel 返回數據過慢或者是直接返回了查詢覆蓋的數據量太大而直接失敗。這部分的 panel 常常在儘可能優化了查詢條件後,仍舊無法通過正常手段查詢。

在這裏以一個 promql 語句作爲例子:

histogram_quantile(0.99, sum(rate(grpc_server_requests_duration_ms_bucket{app="$app",env=~"$env"}[2m])) by (le, method))

這條語句用來查詢某個 env 下的某個 app 下的所有 grpc 接口調用 p99 耗時。在這條語句中,假設一個存在 app A,其下包含 2000 個實例,20 個接口, 50 個調用方,5 個 le 桶,以 30s 爲間隔進行一次上報,單個時間上的點符合條件的點就有 1000 w,這條語句還包括一個 2m 的時間窗口統計,那麼本次查詢總體上符合點的數量就爲 4000w。這樣的查詢語句,即使能夠避開查詢的容量限制,也會影響整體的查詢數據源的穩定性。在常規的 grafana 使用習慣上,我們將會對這條語句的整體進行一次預聚合,但是在這個語句的場景下,b 站處於活躍狀態的 app 數量爲大幾千,而這條語句中只有兩位數的應用存在查詢性能問題,如果直接對這條語句進行預聚合的話,就會存在比較大的資源浪費。同時,在這條語句中,如果將第一個參數分別修改爲 0.5 和 0.9,那麼可以直接查詢得到當相應的 p50 和 p90 指標,對着這條的優化並不能幫助到 p50 和 p90 的查詢,單次聚合的產出也不理想。因此,在這思考一個方式,能不能通過僅聚合存在性能問題的部分來解決包含這個語句的 panel 的查詢問題。

首先,從 vmselect 執行這個語句的流程入手,這條語句在 vmselect 執行的過程中,將會被解析成如下的一顆執行樹:

首先來描述一下該語句的執行流程,在解析得到這棵執行樹之後,將會通過深度優先的方式依次來執行各個節點。具體流程如下

  1. 指標數據查詢:在葉子節點處,根據指標名稱和 label 的篩選條件從 vmstorage 當中進行分佈式查詢得到需要的原始指標數據

  2. rate 函數執行:從第 1 步中查詢到的指標數據根據 label 相同的指標分爲一個指標簇,對相同的指標簇內的 2m 時間窗口中的最後一個點和第一個點相減,併除以他們之間的距離,得到 rate 函數的結果

  3. sum 聚合函數執行:最後根據 sum 的 group by 分區鍵將第 2 步中的數據根據 le + method 的分區鍵進行分區,之後對分到同一個區內的指標進行 sum 操作

  4. histogram_quantile 函數執行:最後遍歷第 3 步中得到的每個分區的數據進行 p99 的計算,得到最後需要的耗時分佈的結果

我們在這裏繼續用前文提到的 app 舉例子,其下包含 2000 個實例,20 個接口, 50 個調用方,5 個 le 桶,以 30s 爲間隔進行一次上報,在第一步中查詢需要得到的指標點數量就達到了 4000w。該 4000w 的數據在第二步會在時間 rate 函數的時間窗口計算當中被減少到 1000w。到第三步的時候,這 1000w 的數據會根據 le + method 的分區鍵被分成 100 個分區,最後輸出的結果相應的被縮減到 100 個。第 4 步的結果和輸入的數量相同。因此我們可以看到,在整顆樹的執行過程中,第 1 步往往會成爲真正執行過程中的性能瓶頸,第 2 步雖然在輸出上大大減少了數量,但是仍會存在巨大的指標輸出,並沒有根本解決性能問題。而如果能針對第 3 步的輸出結果去進行預聚合並將預聚合的結果用到查詢中,該查詢將會直接變成一個查詢 20 個指標點的簡單查詢。同時,再考慮一點,無論是 p50 p90 p99 的計算,到第 3 步的指標輸出爲止,底層的計算邏輯是完全一致的,那麼如果我們將第 3 步的結果進行預聚合並持久化下來,完全可以複用到任何參數的 histogram_quantile 計算分位耗時計算當中,一次預聚合的性價比達到了相當大的價值。

那麼,我們假設已經通過實時或者定時的方式將 app A 的這條 promql 進行預聚合並將上述這個表達式的結果轉化爲了指標 test_metric_app_A。

sum(rate(grpc_server_requests_duration_ms_bucket{app="A"}[2m])) by (le, method)

那麼我們實際執行查詢 app A 的 p99 分位耗時的時候,希望真正執行的查詢語句如下

histogram_quantile(0.99, test_metric_app_A)

但是,在 grafana 的 panel 配置當中,一個 panel 對應原始查詢語句和多個這樣的優化查詢的話,實際查詢時體驗將會變得非常奇怪,甚至對整體體驗會達到負優化的效果,那麼回到一開始原始語句的查詢執行樹:

如果我們把黃色的部分的子樹,直接替換成 test_metric_app_A 的查詢樹就可以無縫完成查詢的優化了。那麼我們需要在 vmselect 上面在查詢之前就構建如下的一個映射關係:

在正式執行查詢的時候,我們在執行樹解析完成的時候,從執行樹的根節點就可以開始依次檢查收否存在完全滿足映射條件的 key 的執行樹的子樹,只要符合,就可以將原始查詢語句的子樹替換成映射的 value 樹。

這樣看似是一個非常合適的方案,但是實際的查詢過程中的替換還是存在問題。首先我們的原始語句當中,用到了 env 作爲查詢條件,但是我們預聚合的結果採用的分區鍵是 le + method,在我們預聚合的結果指標當中並沒有 env,直接那這裏的預聚合指標進行替換,會直接丟失掉查詢 env 的能力,因此此處的替換是不符合實際查詢的要求的。那麼我們的預聚合語句應該修改爲:

sum(rate(grpc_server_requests_duration_ms_bucket{app="A"}[2m])) by (le, method, code)

這樣的預聚合,當我們想重新將查詢語句的執行樹進行替換的時候,顯然就不能單純的將替換關係直接套在查詢的執行樹上,反之,在聚合函數外層應該加上一層新的聚合函數節點。

重新思考當前的場景,當我們存在如上的預聚合之後,我們需要的是在預聚合的基礎上自動增加一層聚合函數,就能夠達到我們需要的目的。

我們新的映射關係變成下面的樣子:

當我們得到這個映射關係之後,我們重新開始從根節點開始進行遍歷的時候,會發現在 sum 這一層的分區鍵上存在不匹配的關係,但是,查詢的分區鍵集合 (le, method) 是映射的分區鍵 (le, method, env) 的一部分,直接使用也對語句的執行沒有任何影響。在這個基礎上,可以直接在替換子樹的時候把新的子樹掛在這一層的聚合函數下面。替換結果如下:

達成如上的執行樹替換之後,就可以在語義無損的前提下完成 promql 的自動替換。同時,在這個轉換的支持下,對於預聚合的指標我們可以在不影響預聚合輸出數量的前提下儘可能的增加一些聚合的維度,這樣預聚合的結果可以作爲子查詢用在更多的場景下。而這裏的查詢轉換,對於用戶層面在使用 grafana 進行查詢的時候,是完全無感知的。在查詢中對於使用了預聚合的 app,將會自動對解析後的執行樹進行替換,而對於本身就不需要進行優化的查詢條件的時候,將會直接根據原始查詢語句去進行查詢,在儘可能節省預聚合所消耗的資源的前提下,對存在性能瓶頸的查詢語句進行了優化。再者,即使用戶修改了查詢語句,只要語句中包含預聚合了的子查詢,都能夠起到優化的效果,達到了預聚合資源消耗的最大回報。

ps:(avg 聚合函數會存在一定的語義損耗,但是在大部分場景下的誤差可以忽略不計)

基於 promql 的 flink 指標預聚合

對於前文中提到的預聚合,B 站原本主要的預聚合都是通過定時的批量查詢來進行預聚合。這樣的查詢聚合對存儲側 vmstroage 和查詢側 vmselect 都有比較大的壓力,從而可能會影響到正常的指標查詢需求。在預聚合的角度來看,執行原生的 promql 聚合存在一定的內存浪費,以上文提到的一條 promql 爲例子:

sum(rate(grpc_server_requests_duration_ms_bucket{app="A"}[2m])) by (le, method, code)

在這個預聚合語句當中,vmselect 將會從 vmstorage 當中全量撈出符合 grpc_server_requests_duration_ms_bucket{app="A"} 的指標並在內存當中保留全部的標籤,直到全部的分佈式查詢完成後,進行計算,在最後的 sum 部分纔會根據 group by 的 le,method,code 進行 label 收斂再得到最後的結果。這個過程中,vmselect 的內存常常在執行 sum 之前,就遇到了瓶頸,尤其是這裏的 rate 函數,設置了 2m 的時間窗口計算,這部分進一步擴大了 vmselect 的壓力。同時,這樣的查詢往往會成爲慢查詢,這部分數據的聚合也會出現明顯的延遲。同理,定時的大批量查詢也會增加 stroage 的壓力。

爲了避免查詢請求對 vm 集羣的壓力,同時儘可能保證預聚合的實效性,我們嘗試採用 flink 來進行基於 promql 的指標預聚合。

前文提到了 promql 在 vmselect 當中的執行方式。類似 sum(rate(grpc_server_requests_duration_ms_bucket{app="A"}[2m])) by (le, method, code) 這樣的表達式,將會在 vmselect 當中解析成如下的執行樹。

我們在 flink 預聚合的配置側就可以類似的對這個 promql 進行相應的解析,解析成如上的執行樹。在 flink 那一側就不需要關心原始 promql 語句的構成,只需要得到這顆執行樹的 json 即可,同時在配置側需要關心的是在解析期間得到的其中的時間窗口信息作爲元數據保存在一起,flink 的 job 將會各自配置的時間窗口配置從配置中選取符合條件的預聚合配置執行。

對於這顆執行樹,我們站在 flink 的角度去進行設計執行流程。

數據過濾階段

這個階段類比我們進行 vm 查詢指標,和實際 vm 查詢執行一樣,用到的都是執行的葉子節點,在配置時我們就根據每棵樹的葉子節點的指標名稱 label 構建 key,初步篩選實時 kafka 流中的指標數據,並在初步篩選完畢之後,進一步根據剩餘的指標 label 過濾條件進行過濾,在第一階段只需要 check 執行樹的葉子結點就可以從實時指標流中得到所有符合 promql 查詢的指標原始數據

數據分區階段

sum(rate(grpc_server_requests_duration_ms_bucket{app="A"}[2m])) by (le, method, code) 的時候,我們常常在執行這個 2m 的時間窗口聚合的時候需要在內存中持有 2m 內的全量原始數據而存在壓力,通常情況下,一個指標點的 label 集合就將達到 1-2k,對於預聚合是非常大的內存壓力。優化執行聚合過程中,內存中時間窗口中持有的指標點的內存是最核心的優化方向。

在我們通過 flink 進行分區時間窗口聚合的時候,我們正好需要依賴的就是執行樹中的 sum 節點, sum 的 group by 鍵正是 flink 當中需要的分區鍵,我們完全可以複用這裏的邏輯,將指標中對應的 label 提取作爲分區鍵,將數據點作爲 value 進行緩存在 flink 當中的時間窗口當中進行聚合。但是,此處就存在了與 vmselect 執行語句的一樣的內存瓶頸。我們回顧這條語句,grpc_server_requests_duration_ms_bucket 這個指標,其實除了分區鍵當中涉及的 le method code 之外的 label,在執行過程中實際都不是我們需要考慮的 label,我們完全可以在此處對剩餘的 label 全部丟棄掉,只保留一個 label 集合統一的 uuid 用來在 rate 的時候能夠 hash 到一個分桶裏進行聚合即可。同時對於分區鍵的組合,我們甚至可以直接丟棄掉 label key 的部分,因爲我們的分區鍵全部都是按照順序排列的,我們的分區鍵只需要保留這兩個 label 的具體 value, 而對 label 的 key 直接進行丟棄,在執行的時候按照 promql 聲明的順序順序取即可,具體爲 promql id 和 label value,而我們的具體指標數據也只需要保留點的 label 集合 uuid 和時間數值即可。此處的優化可以直接解決聚合當中的內存壓力。

以這條語句所涉及的一個指標爲例子,一個指標所生成的分區鍵爲 "promqlid(四字節 需要在時間窗口中定位具體的執行樹) + le value + method value + code value",需要緩存在時間窗口的指標點的內容爲 "label set uuid (四字節) +  time(八字節) + value(八字節)"。

最後一個點在內存中固定的內存佔用爲 20 字節,而相同分區鍵下的點之間共享的分區鍵大小也只有 4 字節 + 必須要的 value 串,達到了最小的內存消耗。

時間窗口執行階段

當我們完成數據的篩選和分區之後進入時間窗口的算子階段,我們只需要在這個階段對這顆執行樹進行一次深度優先的調用即可。

這個階段中,由於 flink 的時間窗口限制,在具體的函數實現上會和 vm 存在一些細微的區別。

最明顯的例子爲 increase 函數,在 vm 對於該參數的實現中,是通過當前時間窗口的最後一個點和前一個時間窗口的最後一個點進行相減得到的結果,在 flink 中,由於已經定死了緩存 2 分鐘的實時數據,這個實現將會造成額外的內存消耗,同時,在別的函數的實現上也會需要考慮額外的存儲所帶來的實現成本,因此我們選擇了 prometheus 的 increace 實現來達到我們的目的。在 prometheus 的 increase 實現中,通過同一窗口內最後一個點和最後一個點在時間座標軸的一次函數的 k 值,來根據時間窗口的長度來預估這個期間的增量。

類似如此的場景,由於 vm 相比 prometheus 在很多涉及時間窗口的函數中引入了前一個時間窗口的數據來參與計算讓數據更加精準,這樣的優化在 flink 將會導致額外的資源,所有類似的函數 flink 將會參考 prometheus 的設計來實現。

同時,由於我們在上一個階段中對絕大部分的 label 進行了丟棄,在這裏類似 topk 這樣需要前後保留原始 label 信息的函數也存在不支持的場景。

數據上報階段

在前一個階段得到的預聚合指標數據就是我們所需要的結果,可以直接 flink 的 sink 當中通過 remotewrite 協議寫到 vminsert 上。

經過以上的設計,我們基於 promql 的 flink 預聚合可以快速根據實際存在查詢瓶頸的 promql 進行快速配置生效,在實際場景下 100c 400g 的 flink job 配置即可滿足每 2 分鐘 3 億個指標點的時間窗口緩存和計算需求,相比定時預聚合,大大減少了預聚合的資源需求,這裏的實現也滿足我們在透明查詢當中對資源利用最大化的期望。

查詢優化收益

查詢的自動優化加上 flink 的專項預聚合,可以針對實際情況下的查詢情況,對特定的 promql 的集中進行治理優化,在非常小的資源消耗下,日均使 20s 以上的慢查詢減少了百分之 90,查詢數據源資源減少百分之 50,以非常小的代價帶來非常高的收益。

數據可視化

目前我們主要使用 grafana 構建監控大盤,由於歷史原因,grafana 使用的一直是比較老的版本 (v6.7.x), 但是新版 grafana 有非常多的功能迭代和性能優化,因此我們決定升級 grafana 版本到 v9.2.x。但版本升級存在如下挑戰:

  1. v6.7.x 升級到 v9.2.x 存在多個 break change(除了官方描述的一些 break change,同時也包括一些自定義數據源 / pannel 插件存在 break change)。

  2. 老版本 grafana 部署成本高:之前是物理機部署,且部署方式相對黑盒,同時我們增加 nginx+grafana auth 服務做 auth proxy 認證,因此部署運維成本進一步提高。

面對版本升級存在的挑戰和問題,我們做了以下事項:

  1. 在升級前,我們做了大量的測試和驗證,通過一些腳本 fix 因爲 break change 造成的一些數據不兼容,通過 opensearch 數據源插件替換新版本再支持的 es 插件。

  2. 在部署方式上,首先用 git 倉庫管理整個 grafana 部署編譯腳本,讓每次版本變更不再黑盒,同時將 nginx+grafana auth+grafana 構建成 all-in-one 鏡像, 實現容器化部署,進一步降低部署成本。

  3. 新版本的 grafana, 如果 prometheus 數據源使用版本 > v2.37.x,變量的獲取方式默認從 series api 改成 label values api, 查詢性能會提升 10 倍左右 (2s->200ms)。

升級完成後,面板加載性能和整體用戶查詢體驗大幅提升。

整體收益

雲監控方案

當前雲上監控存在以下痛點:

  1. 因有各個雲廠商,或者同一個雲廠商,因地域不同造成網絡環境不通,導致採集失敗,出現無監控數據的情況。

  2. 每個賬號下都是一套獨立的 Promtheus 採集,因此存在多個雲監控數據源,用戶很難選擇需要的雲數據源。

雲監控方案整體和 idc 採集方案類似,爲了方便用戶統一數據源查詢和統一告警計算,我們將 Prometheus 採集的雲上數據,通過 remote write 回源到 idc 存儲集羣。架構如下:

比較 idc 採集方案,雲上監控有以下幾點不同:

  1. Contractor 支持從公網 pull 本 zone 所需的採集配置。

  2. 爲什麼使用 Prometheus 而不是 vmagent 採集?

  1. 爲什麼要引入 vm-auth 組件?

因爲雲上數據回源到 idc, 走的是公網,直接回源 remote write 到 vm-insert,會存在安全風險,所以我們加了 vm-auth,對回源流量做租戶認證和流量調度。vm-auth 配置如下:

收益

未來規劃

  1. 支持更長時間 Metrics 指標數據存儲: 當前數據默認存儲時間是 15d, 希望給業務方提供更長時間的數據, 用作分析,覆盤,資源預估等場景。

  2. 支持更細粒度的指標埋點:目前應用監控默認是 30s 一個點, 少部分場景支持了 5s,希望後面提供更細粒度的埋點,覆蓋更多的場景,爲業務可觀測、排障助力。

  3. 自監控能力增強:目前有一套自監控鏈路,跟運維平臺聯動,提供了基礎的監控自身系統的監控和告警,希望後面可以覆蓋全所有的自監控場景 & 運維 SOP。

  4. 指標平臺迭代:目前指標平臺主要提供了監控接入,採集配置管理和監控對象查詢等基礎能力,未來規劃增加寫入 / 查詢封禁、白名單等能力,同時希望後面可以藉助大模型的能力,通過指標元信息增強,實現 text2promql(自然語言翻譯成 PromQL,以及自動生成 PromQL 註釋)

作者丨鮑森樂、剁椒

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/medbc78ok932qrEqMIAGVw