在本文中,我們將探討如何設計一個可擴充套件的指標監控和告警系統。 一個好的監控和告警系統,對基礎設施的可觀察性,高可用性,可靠性方面發揮著關鍵作用。
下圖顯示了市面上一些流行的指標監控和告警服務。
接下來,我們會設計一個類似的服務,可以供大公司內部使用。
設計要求
從一個小明去面試的故事開始。
面試官:如果讓你設計一個指標監控和告警系統,你會怎麼做?
小明:好的,這個系統是為公司內部使用的,還是設計像 Datadog 這種 SaaS 服務?
面試官:很好的問題,目前這個系統只是公司內部使用。
小明:我們想收集哪些指標資訊?
面試官:包括作業系統的指標資訊,中介軟體的指標,以及執行的應用服務的 qps 這些指標。
小明:我們用這個系統監控的基礎設施的規模是多大的?
面試官:1億日活躍使用者,1000個伺服器池,每個池 100 臺機器。
小明:指標資料要儲存多長時間呢?
面試官:我們想保留一年。
小明:好吧,為了較長時間的儲存,可以降低指標資料的解析度嗎?
面試官:很好的問題,對於最新的資料,會儲存 7 天,7天之後可以降低到1分鐘的解析度,而到 30 天之後,可以按照 1 小時的解析度做進一步的彙總。
小明:支援的告警渠道有哪些?
面試官:郵件,電 釘釘,企業微信,Http Endpoint。
小明:我們需要收集日誌嗎?還有是否需要支援分散式系統的鏈路追蹤?
面試官:目前專注於指標,其他的暫時不考慮。
小明:好的,大概都瞭解了。
總結一下,被監控的基礎設施是大規模的,以及需要支援各種維度的指標。另外,整體的系統也有較高的要求,要考慮到可擴充套件性,低延遲,可靠性和靈活性。
基礎知識
一個指標監控和告警系統通常包含五個元件,如下圖所示
- 資料收集:從不同的資料來源收集指標資料。
- 資料傳輸:把指標資料傳送到指標監控系統。
- 資料儲存:儲存指標資料。
- 告警:分析接收到的資料,檢測到異常時可以發出告警通知。
- 視覺化:視覺化頁面,以圖形,圖表的形式呈現資料。
資料模式
指標資料通常會儲存為一個時間序列,其中包含一組值及其相關的時間戳。
序列本身可以通過名稱進行唯一標識,也可以通過一組標籤進行標識。
讓我們看兩個例子。
示例1:生產伺服器 i631 在 20:00 的 CPU 負載是多少?
上圖示記的資料點可以用下面的格式表示
在上面的示例中,時間序列由指標名稱,標籤(host:i631,env:prod),時間戳以及對應的值構成。
示例2:過去 10 分鐘內上海地區所有 Web 伺服器的平均 CPU 負載是多少?
從概念上來講,我們會查詢出和下面類似的內容
CPU.load host=webserver01,region=shanghai 1613707265 50
CPU.load host=webserver01,region=shanghai 1613707270 62
CPU.load host=webserver02,region=shanghai 1613707275 43
我們可以通過上面每行末尾的值計算平均 CPU 負載,上面的資料格式也稱為行協議。是市面上很多監控軟體比較常用的輸入格式,Prometheus 和 OpenTSDB 就是兩個例子。
每個時間序列都包含以下內容:
- 指標名稱,字串型別的 metric name 。
- 一個鍵值對的陣列,表示指標的標籤,List<key,value>
- 一個包含時間戳和對應值的的陣列,List <value, timestamp>
資料儲存
資料儲存是設計的核心部分,不建議構建自己的儲存系統,也不建議使用常規的儲存系統(比如 MySQL)來完成這項工作。
理論下,常規資料庫可以支援時間序列資料, 但是需要資料庫專家級別的調優後,才能滿足資料量比較大的場景需求。
具體點說,關係型資料庫沒有對時間序列資料進行優化,有以下幾點原因
- 在滾動時間視窗中計算平均值,需要編寫複雜且難以閱讀的 SQL。
- 為了支援標籤(tag/label)資料,我們需要給每個標籤加一個索引。
- 相比之下,關係型資料庫在持續的高併發寫入操作時表現不佳。
那 NoSQL 怎麼樣呢?理論上,市面上的少數 NoSQL 資料庫可以有效地處理時間序列資料。比如 Cassandra 和 Bigtable 都可以。但是,想要滿足高效儲存和查詢資料的需求,以及構建可擴充套件的系統,需要深入瞭解每個 NoSQL 的內部工作原理。
相比之下,專門對時間序列資料優化的時序資料庫,更適合這種場景。
OpenTSDB 是一個分散式時序資料庫,但由於它基於 Hadoop 和 HBase,執行 Hadoop/HBase 叢集也會帶來複雜性。Twitter 使用了 MetricsDB 時序資料庫儲存指標資料,而亞馬遜提供了 Timestream 時序資料庫服務。
根據 DB-engines 的報告,兩個最流行的時序資料庫是 InfluxDB 和 Prometheus ,它們可以儲存大量時序資料,並支援快速地對這些資料進行實時分析。
如下圖所示,8 核 CPU 和 32 GB RAM 的 InfluxDB 每秒可以處理超過 250,000 次寫入。
高層次設計
- Metrics Source 指標來源,應用服務,資料庫,訊息佇列等。
- Metrics Collector 指標收集器。
- Time series DB 時序資料庫,儲存指標資料。
- Query Service 查詢服務,向外提供指標查詢介面。
- Alerting System 告警系統,檢測到異常時,傳送告警通知。
- Visualization System 視覺化,以圖表的形式展示指標。
深入設計
現在,讓我們聚焦於資料收集流程。主要有推和拉兩種方式。
拉模式
上圖顯示了使用了拉模式的資料收集,單獨設定了資料收集器,定期從執行的應用中拉取指標資料。
這裡有一個問題,資料收集器如何知道每個資料來源的地址? 一個比較好的方案是引入服務註冊發現元件,比如 etcd,ZooKeeper,如下
下圖展示了我們現在的資料拉取流程。
- 指標收集器從服務發現元件中獲取後設資料,包括拉取間隔,IP 地址,超時,重試引數等。
- 指標收集器通過設定的 HTTP 端點獲取指標資料。
在資料量比較大的場景下,單個指標收集器是獨木難支的,我們必須使用一組指標收集器。但是多個收集器和多個資料來源之間應該如何協調,才能正常工作不發生衝突呢?
一致性雜湊很適合這種場景,我們可以把資料來源對映到雜湊環上,如下
這樣可以保證每個指標收集器都有對應的資料來源,相互工作且不會發生衝突。
推模式
如下圖所示,在推模式中,各種指標資料來源(Web 應用,資料庫,訊息佇列)直接傳送到指標收集器。
在推模式中,需要在每個被監控的伺服器上安裝收集器代理,它可以收集伺服器的指標資料,然後定期的傳送給指標收集器。
推和拉兩種模式哪種更好?沒有固定的答案,這兩個方案都是可行的,甚至在一些複雜場景中,需要同時支援推和拉。
擴充套件資料傳輸
現在,讓我們主要關注指標收集器和時序資料庫。不管使用推還是拉模式,在需要接收大量資料的場景下,指標收集器通常是一個服務叢集。
但是,當時序資料庫不可用時,就會存在資料丟失的風險,所以,我們引入了 Kafka 訊息佇列元件, 如下圖
指標收集器把指標資料傳送到 Kafka 訊息佇列,然後消費者或者流處理服務進行資料處理,比如 Apache Storm、Flink 和 Spark, 最後再推送到時序資料庫。
指標計算
指標在多個地方都可以聚合計算,看看它們都有什麼不一樣。
- 客戶端代理:客戶端安裝的收集代理只支援簡單的聚合邏輯。
- 傳輸管道:在資料寫入時序資料庫之前,我們可以用 Flink 流處理服務進行聚合計算,然後只寫入彙總後的資料,這樣寫入量會大大減少。但是由於我們沒有儲存原始資料,所以丟失了資料精度。
- 查詢端:我們可以在查詢端對原始資料進行實時聚合查詢,但是這樣方式查詢效率不太高。
時序資料庫查詢語言
大多數流行的指標監控系統,比如 Prometheus 和 InfluxDB 都不使用 SQL,而是有自己的查詢語言。一個主要原因是很難通過 SQL 來查詢時序資料, 並且難以閱讀,比如下面的SQL 你能看出來在查詢什麼資料嗎?
select id,
temp,
avg(temp) over (partition by group_nr order by time_read) as rolling_avg
from (
select id,
temp,
time_read,
interval_group,
id - row_number() over (partition by interval_group order by time_read) as group_nr
from (
select id,
time_read,
"epoch"::timestamp + "900 seconds"::interval * (extract(epoch from time_read)::int4 / 900) as interval_group,
temp
from readings
) t1
) t2
order by time_read;
相比之下, InfluxDB 使用的針對於時序資料的 Flux 查詢語言會更簡單更好理解,如下
from(db:"telegraf")
|> range(start:-1h)
|> filter(fn: (r) => r._measurement == "foo")
|> exponentialMovingAverage(size:-10s)
資料編碼和壓縮
資料編碼和壓縮可以很大程度上減小資料的大小,特別是在時序資料庫中,下面是一個簡單的例子。
因為一般資料收集的時間間隔是固定的,所以我們可以把一個基礎值和增量一起儲存,比如 1610087371, 10, 10, 9, 11 這樣,可以佔用更少的空間。
下采樣
下采樣是把高解析度的資料轉換為低解析度的過程,這樣可以減少磁碟使用。由於我們的資料保留期是1年,我們可以對舊資料進行下采樣,這是一個例子:
- 7天資料,不進行取樣。
- 30天資料,下采樣到1分鐘的解析度
- 1年資料,下采樣到1小時的解析度。
我們看另外一個具體的例子,它把 10 秒解析度的資料聚合為 30 秒解析度。
原始資料
下采樣之後
告警服務
讓我們看看告警服務的設計圖,以及工作流程。
-
載入 YAML 格式的告警配置檔案到快取。
- name: instance_down rules: # 服務不可用時間超過 5 分鐘觸發告警. - alert: instance_down expr: up == 0 for: 5m labels: severity: page
-
警報管理器從快取中讀取配置。
-
根據告警規則,按照設定的時間和條件查詢指標,如果超過閾值,則觸發告警。
-
Alert Store 儲存著所有告警的狀態(掛起,觸發,已解決)。
-
符合條件的告警會新增到 Kafka 中。
-
消費佇列,根據告警規則,傳送警報資訊到不同的通知渠道。
視覺化
視覺化建立在資料層之上,指標資料可以在指標儀表板上顯示,告警資訊可以在告警儀表板上顯示。下圖顯示了一些指標,伺服器的請求數量、記憶體/CPU 利用率、頁面載入時間、流量和登入資訊。
Grafana 可以是一個非常好的視覺化系統,我們可以直接拿來使用。
總結
在本文中,我們介紹了指標監控和告警系統的設計。在高層次上,我們討論了資料收集、時序資料庫、告警和視覺化,下圖是我們最終的設計:
Reference
[0] System Design Interview Volume 2:
https://www.amazon.com/System-Design-Interview-Insiders-Guide/dp/1736049119
[1] Datadog: https://www.datadoghq.com/
[2] Splunk: https://www.splunk.com/
[3] Elastic stack: https://www.elastic.co/elastic-stack
[4] Dapper, a Large-Scale Distributed Systems Tracing Infrastructure:
https://research.google/pubs/pub36356/
[5] Distributed Systems Tracing with Zipkin:
https://blog.twitter.com/engineering/en_us/a/2012/distributed-systems-tracing-with-zipkin.html
[6] Prometheus: https://prometheus.io/docs/introduction/overview/
[7] OpenTSDB - A Distributed, Scalable Monitoring System: http://opentsdb.net/
[8] Data model: : https://prometheus.io/docs/concepts/data_model/
[9] Schema design for time-series data | Cloud Bigtable Documentation
https://cloud.google.com/bigtable/docs/schema-design-time-series
[10] MetricsDB: TimeSeries Database for storing metrics at Twitter:
https://blog.twitter.com/engineering/en_us/topics/infrastructure/2019/metricsdb.html
[11] Amazon Timestream: https://aws.amazon.com/timestream/
[12] DB-Engines Ranking of time-series DBMS: https://db-engines.com/en/ranking/time+series+dbms
[13] InfluxDB: https://www.influxdata.com/
[14] etcd: https://etcd.io
[15] Service Discovery with Zookeeper
https://cloud.spring.io/spring-cloud-zookeeper/1.2.x/multi/multi_spring-cloud-zookeeper-discovery.html
[16] Amazon CloudWatch: https://aws.amazon.com/cloudwatch/
[17] Graphite: https://graphiteapp.org/
[18] Push vs Pull: http://bit.ly/3aJEPxE
[19] Pull doesn’t scale - or does it?:
https://prometheus.io/blog/2016/07/23/pull-does-not-scale-or-does-it/
[20] Monitoring Architecture:
https://developer.lightbend.com/guides/monitoring-at-scale/monitoring-architecture/architecture.html
[21] Push vs Pull in Monitoring Systems:
https://giedrius.blog/2019/05/11/push-vs-pull-in-monitoring-systems/
[22] Pushgateway: https://github.com/prometheus/pushgateway
[23] Building Applications with Serverless Architectures
https://aws.amazon.com/lambda/serverless-architectures-learn-more/
[24] Gorilla: A Fast, Scalable, In-Memory Time Series Database:
http://www.vldb.org/pvldb/vol8/p1816-teller.pdf
[25] Why We’re Building Flux, a New Data Scripting and Query Language:
https://www.influxdata.com/blog/why-were-building-flux-a-new-data-scripting-and-query-language/
[26] InfluxDB storage engine: https://docs.influxdata.com/influxdb/v2.0/reference/internals/storage-engine/
[27] YAML: https://en.wikipedia.org/wiki/YAML
[28] Grafana Demo: https://play.grafana.org/
最後
做了一個 .NET 的學習網站,內容涵蓋了分散式系統,資料結構與演算法,設計模式,作業系統,計算機網路等,以及工作推薦和麵試經驗分享,歡迎來撩。
關注公眾號
回覆 dotnet 獲取網站地址。
回覆 面試題 獲取 .NET 面試題。
回覆 程式設計師副業 獲取適合程式設計師的副業指南。