一個複雜的應用,每每由不少個模塊組成,並且每每會存在各類各樣奇奇怪怪的使用場景,誰也不能保證本身維護的服務永遠不會出問題,等用戶投訴才發現問題再去處理問題就爲時已晚,損失已沒法挽回。html
因此,經過數據指標來衡量一個服務的穩定性和處理效率,是否正常運做,監控指標曲線的狀態,指標出現異常時及時主動告警,這一套工具就十分重要。前端
常見的一些指標,包括但不限於:node
舉個例子,假如一個服務:mysql
<!--這究竟是人性的扭曲仍是道德的淪喪,-->
一旦服務存在某些缺陷致使這些問題,經過服務日誌,很難直觀快速地察覺到這些指標的變化波動。golang
經過監控和告警手段能夠有效地覆蓋了「發現」和「定位」問題,從而更有效率地排查和解決問題。web
Prometheus 是一個開源的服務監控系統和時間序列數據庫。sql
工做流能夠簡化爲:數據庫
具體的架構設計以下:
後端
Prometheus 用的是本身設計的時序數據庫(TSDB),那麼爲何不用咱們更加熟悉,更加經常使用的 mysql, 或者其餘關係型數據庫呢?api
假設須要監控 WebServerA 每一個API的請求量爲例,須要監控的維度包括:服務名(job)、實例IP(instance)、API名(handler)、方法(method)、返回碼(code)、請求量(value)。
若是以SQL爲例,演示常見的查詢操做:
# 查詢 method=put 且 code=200 的請求量 SELECT * from http_requests_total WHERE code=」200」 AND method=」put」 AND created_at BETWEEN 1495435700 AND 1495435710; # 查詢 handler=prometheus 且 method=post 的請求量 SELECT * from http_requests_total WHERE handler=」prometheus」 AND method=」post」 AND created_at BETWEEN 1495435700 AND 1495435710; # 查詢 instance=10.59.8.110 且 handler 以 query 開頭 的請求量 SELECT * from http_requests_total WHERE handler=」query」 AND instance=」10.59.8.110」 AND created_at BETWEEN 1495435700 AND 1495435710;
經過以上示例能夠看出,在經常使用查詢和統計方面,平常監控多用於根據監控的維度進行查詢與時間進行組合查詢。若是監控100個服務,平均每一個服務部署10個實例,每一個服務有20個API,4個方法,30秒收集一次數據,保留60天。那麼總數據條數爲:100(服務)* 10(實例)* 20(API)* 4(方法)* 86400(1天秒數)* 60(天) / 30(秒)= 138.24 億條數據
,寫入、存儲、查詢如此量級的數據是不可能在Mysql類的關係數據庫上完成的。 所以 Prometheus 使用 TSDB 做爲 存儲引擎。
時序數據庫主要用於指處理帶時間標籤(按照時間的順序變化,即時間序列化)的數據,帶時間標籤的數據也稱爲時序數據。
對於 prometheus 來講,每一個時序點結構以下:
每一個指標,有多個時序圖;多個時序數據點鏈接起來,構成一個時序圖
假如用傳統的關係型數據庫來表示時序數據,就是如下結構:
create_time | __metric_name__ |
path | value |
---|---|---|---|
2020-10-01 00:00:00 | http_request_total | /home | 100 |
2020-10-01 00:00:00 | http_request_total | /error | 0 |
2020-10-01 00:00:15 | http_request_total | /home | 120 |
2020-10-01 00:01:00 | http_request_total | /home | 160 |
2020-10-01 00:01:00 | http_request_total | /error | 1 |
指標 request_total{path="/home"} 在 2020-10-01 00:01:00 時的 qps = (160 - 100)/60 = 1 , 同理,
指標 request_total{path="/error"} 在 2020-10-01 00:01:00 時的 qps = 1/60
相比於 MySQL,時序數據庫核心在於時序,其查詢時間相關的數據消耗的資源相對較低,效率相對較高,而剛好指標監控數據帶有明顯的時序特性,因此採用時序數據庫做爲存儲層
const reqCounter = new Counter({ name: `credit_insight_spl_id_all_pv`, help: 'request count', labelNames: ['deviceBrand','systemType', 'appVersion', 'channel'] }) reqCounter.inc({ deviceBrand: 'Apple', systemType: 'iOS', appVersion: '26014', channel: 'mepage' },1)
/metrics
的controller@Get('metrics') getMetrics(@Res() res) { res.set('Content-Type', register.contentType) res.send(register.metrics()) }
/metrics
接口,得到
promQL 是 prometheus 的查詢語言,語法十分簡單
查詢指標最新的值:
{__name__="http_request_total", handler="/home"} # 語法糖: http_request_total{handler="/home"} # 等價於 mysql: select * from http_request_total where handler="/home" AND create_time=《now()》
查詢過去一分鐘內的數據
# promQL http_request_total[1m] # 等價於 SELECT * from http_requests_total WHERE create_time BETWEEN 《now() - 1min》 AND 《now()》;
PS: promQL 不支持指定時間點進行查詢,只能經過 offset 來查詢歷史某個點的數據
查詢一個小時前的數據。
# promQL http_request_total offset 1h # 等價於 SELECT * from http_requests_total WHERE create_time=《now() - 1 hour》;
根據以上的查詢語法,咱們能夠簡單組合出一些指標數據:
例如,查詢最近一天內的 /home 頁請求數
http_request_total{handler="/home"} - http_request_total{handler="/home"} offset 1d
那麼實際上面這個寫法很明顯比較不簡潔,咱們可以使用內置 increase 函數來替換:
# 和上述寫法等價 increase(http_request_total{handler="/home"}[1d])
除了 increase 外,還有不少其餘好用的函數,例如,
rate 函數計算 QPS
// 過去的 2 分鐘內平均每秒請求數 rate(http_request_total{code="400"}[2m]) // 等價於 increase(http_request_total{code="400"}[2m]) / 120
除了上述基礎查詢外,咱們可能還須要聚合查詢
假如咱們有如下數據指標:
credit_insight_spl_id_all_pv{url="/home",channel="none"} credit_insight_spl_id_all_pv{url="/home",channel="mepage"} credit_insight_spl_id_all_pv{url="/error",channel="none"} credit_insight_spl_id_all_pv{url="/error",channel="mepage"}
將全部指標數據以某個維度進行聚合查詢時,例如:查詢 url="/home" 最近一天的訪問量,channel 是 none仍是mepage 的 /home 訪問量都包括在內。
咱們理所固然地會寫出:
increase(credit_insight_spl_id_all_pv{url="/home"}[1d])
但實際上咱們會得出這樣的兩條指標結果:
credit_insight_spl_id_all_pv{url="/home",channel="none"} 233 credit_insight_spl_id_all_pv{url="/home",channel="mepage"} 666
並不是咱們預期中的:
credit_insight_spl_id_all_pv{url="/home"} 899
而要是咱們想要獲得這樣的聚合查詢結果,就須要用到 sum by
# 聚合 url="/home" 的數據 sum(increase(credit_insight_spl_id_all_pv{url="/home"}[1d])) by (url) # 得出結果: credit_insight_spl_id_all_pv{url="/home"} 899 # 全部 channel 中 /home 頁訪問量累加值 # 聚合全部的 url 則能夠這樣寫: sum(increase(credit_insight_spl_id_all_pv{}[1d])) by (url) # 得出結果: credit_insight_spl_id_all_pv{url="/home"} 899 credit_insight_spl_id_all_pv{url="/error"} 7 # 等價於 mysql SELECT url, COUNT(*) AS total FROM credit_insight_spl_id_all_pv WHERE create_time between <now() - 1d> and <now()> GROUP BY url;
以上的全部例子的查詢數值,其實都是最近時間點的數值,
而咱們更關注的是一個時間段的數值變化。
要實現這個原理也很簡單,只須要在歷史的每一個時間點都執行一次指標查詢,
# 假現在天7號 # 6號到7號的一天訪問量 sum(increase(credit_insight_spl_id_all_pv{}[1d] )) by (url) # 5號到6號的一天訪問量 offset 1d sum(increase(credit_insight_spl_id_all_pv{}[1d] offset 1d)) by (url) # 4號到5號的一天訪問量 sum(increase(credit_insight_spl_id_all_pv{}[1d] offset 2d)) by (url)
而 Prometheus 已經內置了時間段查詢功能,並對此優化處理。
可經過 /api/v1/query_range
接口進行查詢,獲的 grpah:
數據存儲:
指標數據有 「Writes are vertical,reads are horizontal」 的(垂直寫,水平讀)模式:
「Writes are vertical,reads are horizontal」 的意思是 tsdb 一般按固定的時間間隔收集指標並寫入,會 「垂直」 地寫入最近全部時間序列的數據,而讀取操做每每面向必定時間範圍的一個或多個時間序列,「橫向」 地跨越時間進行查詢
而 Prometheus 的默認查詢 sample 上限是 5000w
因此,若是指標的時序圖數量過大,容許查詢的時間區間相對就會較小了。
一個圖表查詢時序數量的影響因素有 3 個,分別是:
以 credit_insight_spl_id_all_pv
指標爲例,該指標總共大約有 n = 163698 種時序,
假如 step = 15s,若是搜索該指標過去 time = 60m 的所有時序圖,那麼,須要搜索的例子要163698 * 60 * (60/15) = 39287520
,將近 4kw,是能夠搜出來的。
但若是搜的是過去 90m 的數據,163698 * 90 * 4 = 58931280
,超過了 5000w,你就發現數據請求異常:Error executing query: query processing would load too many samples into memory in query execution
因此,目測可得一個圖的查詢時序點數量公式是:total = n * time / step, time 和 step 的時間單位必須一致,total 必須不超過 5000w。
反推一下得出,time < 5000w / n * step 。要擴大搜索時間範圍,增大 step ,或者下降 n 便可作到。
credit_insight_spl_id_all_pv{systemType="Android", systemVersion="10"}
,n = 18955
固然,通常狀況下,咱們的 n 值只有幾百,而 step 基本是大於 60s 的,因此通常狀況下都能查詢 2 個多月以上的數據圖。
grafana 是一個開源的,高度可配置的數據圖表分析,監控,告警的平臺,也是一款前端可視化的產品。
grafana 內置提供多種圖表模板,具體是如下類型:
Prometheus 做爲數據源的狀況下,通常用的 graph 類型畫時序圖比較多。
對於一些基礎的數據大盤監控,這些圖表類型已經足夠知足咱們的需求。
但對於複雜的需求,這些類型沒法知足咱們的須要時,咱們安裝 pannel 插件,來更新可用的圖表類型,也能夠根據官方文檔 build a panel plugin 開發本身的前端圖表 panel。
在時序圖表配置場景下,咱們須要核心關注配置的有:
從上圖能夠看到,
爲了實現一些經常使用的篩選過濾場景,grafana 提供了變量功能
$xxx
形式去引用。
除了 Prometheus 自己能夠配置告警表達式以外:
grafana 也能夠配置告警:
Prometheus 一般用於後端應用的指標數據實時上報,主要用於異常告警,問題排查,因此數據存在時效性,咱們不會關注幾個月前的一個已經被排查並 fixed 的指標異常波動告警。
可是,要是咱們將 Prometheus 用於業務指標監控,那麼咱們可能會關注更久遠的數據。
例如咱們可能想要看過去一個季度的環比同比增加,用 Prometheus 做爲數據源就不合適,由於 Prometheus 是時序數據庫,更多關注實時數據,數據量大,當前數據保存的時效設定只有 3 個月。
那麼這個時候可能咱們要維護一個長期的統計數據,可能就須要存儲在 mysql 或者其餘存儲方式。
grafana 不是 Prometheus 的專屬產品,還支持多種數據源,包括但不限於:
常見數據庫
日誌、文檔數據庫
時序數據庫
鏈路追蹤
若是沒有本身須要的數據源配置,還能夠安裝 REST API Datasource Plugin, 經過 http 接口查詢做爲數據源
瞭解 grafana 的高度可配置性設計後,有值得思考的幾點:
等等這些,其實都是值得咱們去思考的。
此外,Prometheus 和 grafana 都有些進階的玩法,你們有興趣也能夠去探索下。