在本文中,我們將探討如何設計一個可擴展的指標監控和告警系統。一個好的監控和告警系統,對基礎設施的可觀察性,高可用性,可靠性方面發揮著關鍵作用。
下圖顯示了市面上一些流行的指標監控和告警服務。

接下來,我們會設計一個類似的服務,可以供大公司內部使用。
設計要求
從一個小明去面試的故事開始。

面試官:如果讓你設計一個指標監控和告警系統,你會怎么做?
小明:好的,這個系統是為公司內部使用的,還是設計像 Datadog 這種 SaaS 服務?
面試官:很好的問題,目前這個系統只是公司內部使用。
小明:我們想收集哪些指標信息?
面試官:包括操作系統的指標信息,中間件的指標,以及運行的應用服務的 qps 這些指標。
小明:我們用這個系統監控的基礎設施的規模是多大的?
面試官:1億日活躍用戶,1000個服務器池,每個池 100 臺機器。
小明:指標數據要保存多長時間呢?
面試官:我們想保留一年。
小明:好吧,為了較長時間的存儲,可以降低指標數據的分辨率嗎?
面試官:很好的問題,對于最新的數據,會保存 7 天,7天之后可以降低到1分鐘的分辨率,而到 30 天之后,可以按照 1 小時的分辨率做進一步的匯總。
小明:支持的告警渠道有哪些?
面試官:郵件,電 釘釘,企業微信,Http Endpoint。
小明:我們需要收集日志嗎?還有是否需要支持分布式系統的鏈路追蹤?
面試官:目前專注于指標,其他的暫時不考慮。
小明:好的,大概都了解了。
總結一下,被監控的基礎設施是大規模的,以及需要支持各種維度的指標。另外,整體的系統也有較高的要求,要考慮到可擴展性,低延遲,可靠性和靈活性。
基礎知識
一個指標監控和告警系統通常包含五個組件,如下圖所示

1. 數據收集:從不同的數據源收集指標數據。
2. 數據傳輸:把指標數據發送到指標監控系統。
3. 數據存儲:存儲指標數據。
4. 告警:分析接收到的數據,檢測到異常時可以發出告警通知。
5. 可視化:可視化頁面,以圖形,圖表的形式呈現數據。
數據模式
指標數據通常會保存為一個時間序列,其中包含一組值及其相關的時間戳。
序列本身可以通過名稱進行唯一標識,也可以通過一組標簽進行標識。
讓我們看兩個例子。
示例1:生產服務器 i631 在 20:00 的 CPU 負載是多少?

上圖標記的數據點可以用下面的格式表示

在上面的示例中,時間序列由指標名稱,標簽(host:i631,env:prod),時間戳以及對應的值構成。
示例2:過去 10 分鐘內上海地區所有 Web 服務器的平均 CPU 負載是多少?
從概念上來講,我們會查詢出和下面類似的內容
CPU.load?host=webserver01,region=shanghai?1613707265?50CPU.load?host=webserver01,region=shanghai?1613707270?62CPU.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,如下

下圖展示了我們現在的數據拉取流程。

1. 指標收集器從服務發現組件中獲取元數據,包括拉取間隔,IP 地址,超時,重試參數等。
2. 指標收集器通過設定的 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_nrfrom?(select?id,time_read,"epoch"::timestamp?+?"900?seconds"::interval?*?(extract(epoch?from?time_read)::int4?/?900)?as?interval_group,tempfrom?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 秒分辨率。
原始數據

下采樣之后

告警服務
讓我們看看告警服務的設計圖,以及工作流程。

1. 加載 YAML 格式的告警配置文件到緩存。
-?name:?instance_down
??rules:
??#?服務不可用時間超過?5?分鐘觸發告警.
??-?alert:?instance_down
????expr:?up?==?0
????for:?5m
????labels:
??????severity:?page2. 警報管理器從緩存中讀取配置。
3. 根據告警規則,按照設定的時間和條件查詢指標,如果超過閾值,則觸發告警。
4. Alert Store 保存著所有告警的狀態(掛起,觸發,已解決)。
5. 符合條件的告警會添加到 Kafka 中。
6. 消費隊列,根據告警規則,發送警報信息到不同的通知渠道。
可視化
可視化建立在數據層之上,指標數據可以在指標儀表板上顯示,告警信息可以在告警儀表板上顯示。下圖顯示了一些指標,服務器的請求數量、內存/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/
END
做了一個 .NET 的學習網站,內容涵蓋了分布式系統,數據結構與算法,設計模式,操作系統,計算機網絡等,以及工作推薦和面試經驗分享,歡迎來撩。
回復 dotnet 獲取網站地址。
回復 面試題 獲取 .NET 面試題。
回復 程序員副業?獲取適合程序員的副業指南。