在 TiDB 里面,我們使用 Prometheus 作為我們 Monitor 工具,然后使用 Grafana 展示,這套解決方案應(yīng)該是現(xiàn)在非常流行的,功能也很強(qiáng)大,很多問題,我們不需要登錄到問題機(jī)器,直接看 metric 就能夠定位。一切都很美好,直到我們發(fā)現(xiàn)了一個(gè)嚴(yán)重的問題 - 有些用戶,只有內(nèi)網(wǎng)環(huán)境,我們看不到監(jiān)控。。。
因?yàn)槲覀儧]法看到監(jiān)控,所以如果用戶那邊出現(xiàn)了問題,就會(huì)非常的麻煩,通常我們會(huì)使用 TeamViewer,但 TeamViewer 有幾個(gè)問題,一個(gè)就是網(wǎng)速有時(shí)候會(huì)很慢,操作幾次之后,大家都會(huì)崩潰的。另外就是客戶不能用這臺(tái)機(jī)器干事情了,有時(shí)候他們不愿意。
另一種方案,就是使用微信,然后直接指導(dǎo)用戶,『 麻煩幫我把這個(gè) metric 給截圖一下,麻煩再給那個(gè) metric 給截圖下』不用說,讓用戶這么干幾次,如果我是用戶,我也會(huì)發(fā)飆了。
所以我們需要有一套機(jī)制,能非常好的幫我們自動(dòng)收集所有的 metrics。
PDF Exporter
最開始,我們使用的是直接將 Grafana 上面的面板按照 PDF 格式直接導(dǎo)出,譬如使用 Reporter,不過這個(gè)庫(kù)需要安裝 pdflatex,但這在用戶那邊是不可能的,所以我們稍微做了改進(jìn),用了 gopdf 來生成 PDF,大概樣子類似這樣:

使用這種方式雖然能快速的生成 metrics 的 PDF,但有一個(gè)問題就是展示的 metrics 是靜態(tài)的,我們不能用選擇某一個(gè)曲線進(jìn)行高亮展示,或者是重新選擇一段區(qū)間進(jìn)行更細(xì)化的展示。
為了解決這個(gè)問題,我們其實(shí)需要考慮的是將 Prometheus 的數(shù)據(jù)給拿出來,重新導(dǎo)入到我們本地的 Prometheus 里面,然后用 Grafana 展示。Prometheus 提供了 snapshot 的功能,能支持將 Prometheus 的數(shù)據(jù)給 dump 出來,但這個(gè)功能會(huì) dump 所有的 Prometheus 數(shù)據(jù),而我們的數(shù)據(jù)通常都至少保留 15 天,所以這個(gè) dump 出來的數(shù)據(jù)實(shí)在是太大了,根本不可能這么做。
Remote Storage
對(duì)于我們來說,其實(shí)是不需要 dump 出來 Prometheus 所有的數(shù)據(jù)的。因?yàn)槲覀冎恍枰桥挪殄e(cuò)誤,通常只會(huì)關(guān)心一段時(shí)間的 metrics 變化,所以只需要將那一段時(shí)間的 metrics 數(shù)據(jù)給弄出來就可以了,而且因?yàn)闀r(shí)間不會(huì)很長(zhǎng),數(shù)據(jù)量不會(huì)特別大。但不幸的是,Prometheus 并不提供這樣的機(jī)制。
雖然 Prometheus 不提供,但不代表我們不能做。Prometheus 提供了一套 Remote Storage API,能讓 Prometheus 對(duì) remote storage 進(jìn)行讀寫,參考官網(wǎng):

所以我們可以基于 Remote Storage 來做。簡(jiǎn)單的架構(gòu)圖如下
+------------------+ write +---------------+ dump +-----------+
| User Prometheus | -------> | Write Storage | ------> | Dump Data |
+------------------+ +---------------+ +-----------+
|
|
|
|
+------------------+ read +---------------+ load |
| Local Prometheus | -------> | Read Storage | <---------+
+------------------+ +---------------+
流程就比較簡(jiǎn)單了:
- 用戶的 Prometheus 將 metrics 數(shù)據(jù)寫入到 Write Storage
- Write Storage 支持按照某一段時(shí)間區(qū)間將相應(yīng)的 metrics 給 dump 出來
- 拿到 dump 的數(shù)據(jù)之后,我們可以導(dǎo)入到本地的 Read Storage
- 啟動(dòng)本地的 Prometheus,讓它從 Read Storage 讀取數(shù)據(jù)
實(shí)現(xiàn)
可以看到,通過 Remote Storage,我們能方便的實(shí)現(xiàn) Prometheus 一段時(shí)間數(shù)據(jù) dump + load 的工作,而對(duì)于實(shí)現(xiàn)來說,無非就是幾個(gè)地方需要考量:
- 如何與 Prometheus 交互,這個(gè)其實(shí)就是實(shí)現(xiàn) Remote Storage Protocol,這方面,Prometheus 有太多的例子,譬如官方的,這里就不說明了。
- 如何在自己的 Storage 里面存儲(chǔ)數(shù)據(jù),并進(jìn)行高效的檢索。這里我們重點(diǎn)說明下這個(gè)。
因?yàn)槲覀冏罡哳l的需求是按照某一段時(shí)間從 Write Storage 里面 dump 出來數(shù)據(jù),而 Prometheus 從 Read Storage 里面讀取數(shù)據(jù)的時(shí)候也是按照時(shí)間區(qū)間來的,所以對(duì)于我們的 Storage 來說,我們僅僅需要一個(gè)高性能的 KV 數(shù)據(jù)庫(kù)就可以了。這里,為了簡(jiǎn)單實(shí)現(xiàn),我選擇了 badger。其實(shí) boltdb,LMDB,LevelD 都可以。
Prometheus 給 Remote Storage 寫入數(shù)據(jù)的時(shí)候,會(huì)發(fā)送如下的數(shù)據(jù)結(jié)構(gòu):
message Sample {
double value = 1;
int64 timestamp = 2;
}
message TimeSeries {
repeated Label labels = 1;
repeated Sample samples = 2;
}
message Label {
string name = 1;
string value = 2;
}
message Labels {
repeated Label labels = 1 [(gogoproto.nullable) = false];
}
每一個(gè) TimeSeries,會(huì)有多個(gè) Sample,每個(gè) Sample 會(huì)帶上一個(gè)毫秒精度的 timestamp 以及 value,所有的 sample 會(huì)共享一個(gè) label 集合,這個(gè) label 集合里面,我們可以通過 __name__ 字段拿到這個(gè) metric 實(shí)際的名字。對(duì)于我們來說,所有的查詢都會(huì)帶上 metric name,所以我們 KV 里面,key 的格式如下:
|timestamp|counter|name|
這里額外加了一個(gè) counter,可以認(rèn)為是一個(gè)全局的唯一 ID,主要是為了防止 timestamp 和 name 不唯一的情況。Timestamp 和 counter 都按照大端序排序,這樣就能保證我們所有的 metrics 能按照時(shí)間先后順序在 KV 里面存儲(chǔ)了。獲取某一段時(shí)間的 metric,只需要靠著底層 engine 自帶的 seek 功能就可以了,譬如對(duì)于后面的查詢來說,流程如下:
- 根據(jù) timestamp 區(qū)間,seek 到 start key,然后依次遍歷,直到遇到時(shí)間戳超過區(qū)間的數(shù)據(jù)
- 每次獲取一個(gè) key,判斷 name 是否一致
- 如果是需要查詢的 metric,讀取數(shù)據(jù),判斷 label 是否匹配查詢條件
- 如果所有條件匹配,拿到 metric 的值,返回給 Prometheus
例子
我寫了一個(gè)簡(jiǎn)單的程序,prom-porter 來驗(yàn)證我的想法,使用很簡(jiǎn)單,編譯好 write 和 read storage,在用戶的 Prometheus 那邊加上 Write Storage 的地址:
remote_write:
- url: "http://localhost:1234/write"
啟動(dòng)之后,Prometheus 就會(huì)將自己的 metrics 給寫入到 Write Storage,然后我們將 一段時(shí)間數(shù)據(jù) dump 出來
curl http://localhost:1234/dump?start=timestamp_ms&end=timestamp_ms
將得到的數(shù)據(jù)用 Read Storage 載入啟動(dòng),同時(shí)在我們自己的 Prometheus 配置上 Read Storage 地址:
remote_read:
- url: "http://localhost:1235/read"
然后我們就可以在 Grafana 上面指定好我們自己的 Prometheus,進(jìn)行查詢了,下圖就是一個(gè)簡(jiǎn)單的 PD 面板,可以看到,數(shù)據(jù)都能正常的顯示出來,不過因?yàn)橹挥幸欢螘r(shí)間的數(shù)據(jù),所以超過這段時(shí)間的范圍了,就不能查詢了。

小結(jié)
上面只是我的一個(gè)簡(jiǎn)單嘗試,如果有更好的辦法,歡迎聯(lián)系我,我的郵箱 tl@pingcap.com