在 TiDB 里面,我们使用 Prometheus 作为我们 Monitor 工具,然后使用 Grafana 展示,这套解决方案应该是现在非常流行的,功能也很强大,很多问题,我们不需要登录到问题机器,直接看 metric 就能够定位。一切都很美好,直到我们发现了一个严重的问题 - 有些用户,只有内网环境,我们看不到监控。。。
因为我们没法看到监控,所以如果用户那边出现了问题,就会非常的麻烦,通常我们会使用 TeamViewer,但 TeamViewer 有几个问题,一个就是网速有时候会很慢,操作几次之后,大家都会崩溃的。另外就是客户不能用这台机器干事情了,有时候他们不愿意。
另一种方案,就是使用微信,然后直接指导用户,『 麻烦帮我把这个 metric 给截图一下,麻烦再给那个 metric 给截图下』不用说,让用户这么干几次,如果我是用户,我也会发飙了。
所以我们需要有一套机制,能非常好的帮我们自动收集所有的 metrics。
PDF Exporter
最开始,我们使用的是直接将 Grafana 上面的面板按照 PDF 格式直接导出,譬如使用 Reporter,不过这个库需要安装 pdflatex,但这在用户那边是不可能的,所以我们稍微做了改进,用了 gopdf 来生成 PDF,大概样子类似这样:
使用这种方式虽然能快速的生成 metrics 的 PDF,但有一个问题就是展示的 metrics 是静态的,我们不能用选择某一个曲线进行高亮展示,或者是重新选择一段区间进行更细化的展示。
为了解决这个问题,我们其实需要考虑的是将 Prometheus 的数据给拿出来,重新导入到我们本地的 Prometheus 里面,然后用 Grafana 展示。Prometheus 提供了 snapshot 的功能,能支持将 Prometheus 的数据给 dump 出来,但这个功能会 dump 所有的 Prometheus 数据,而我们的数据通常都至少保留 15 天,所以这个 dump 出来的数据实在是太大了,根本不可能这么做。
Remote Storage
对于我们来说,其实是不需要 dump 出来 Prometheus 所有的数据的。因为我们只需要是排查错误,通常只会关心一段时间的 metrics 变化,所以只需要将那一段时间的 metrics 数据给弄出来就可以了,而且因为时间不会很长,数据量不会特别大。但不幸的是,Prometheus 并不提供这样的机制。
虽然 Prometheus 不提供,但不代表我们不能做。Prometheus 提供了一套 Remote Storage API,能让 Prometheus 对 remote storage 进行读写,参考官网:
所以我们可以基于 Remote Storage 来做。简单的架构图如下
+------------------+ write +---------------+ dump +-----------+
| User Prometheus | -------> | Write Storage | ------> | Dump Data |
+------------------+ +---------------+ +-----------+
|
|
|
|
+------------------+ read +---------------+ load |
| Local Prometheus | -------> | Read Storage | <---------+
+------------------+ +---------------+
流程就比较简单了:
- 用户的 Prometheus 将 metrics 数据写入到 Write Storage
- Write Storage 支持按照某一段时间区间将相应的 metrics 给 dump 出来
- 拿到 dump 的数据之后,我们可以导入到本地的 Read Storage
- 启动本地的 Prometheus,让它从 Read Storage 读取数据
实现
可以看到,通过 Remote Storage,我们能方便的实现 Prometheus 一段时间数据 dump + load 的工作,而对于实现来说,无非就是几个地方需要考量:
- 如何与 Prometheus 交互,这个其实就是实现 Remote Storage Protocol,这方面,Prometheus 有太多的例子,譬如官方的,这里就不说明了。
- 如何在自己的 Storage 里面存储数据,并进行高效的检索。这里我们重点说明下这个。
因为我们最高频的需求是按照某一段时间从 Write Storage 里面 dump 出来数据,而 Prometheus 从 Read Storage 里面读取数据的时候也是按照时间区间来的,所以对于我们的 Storage 来说,我们仅仅需要一个高性能的 KV 数据库就可以了。这里,为了简单实现,我选择了 badger。其实 boltdb,LMDB,LevelD 都可以。
Prometheus 给 Remote Storage 写入数据的时候,会发送如下的数据结构:
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];
}
每一个 TimeSeries,会有多个 Sample,每个 Sample 会带上一个毫秒精度的 timestamp 以及 value,所有的 sample 会共享一个 label 集合,这个 label 集合里面,我们可以通过 __name__
字段拿到这个 metric 实际的名字。对于我们来说,所有的查询都会带上 metric name,所以我们 KV 里面,key 的格式如下:
|timestamp|counter|name|
这里额外加了一个 counter,可以认为是一个全局的唯一 ID,主要是为了防止 timestamp 和 name 不唯一的情况。Timestamp 和 counter 都按照大端序排序,这样就能保证我们所有的 metrics 能按照时间先后顺序在 KV 里面存储了。获取某一段时间的 metric,只需要靠着底层 engine 自带的 seek 功能就可以了,譬如对于后面的查询来说,流程如下:
- 根据 timestamp 区间,seek 到 start key,然后依次遍历,直到遇到时间戳超过区间的数据
- 每次获取一个 key,判断 name 是否一致
- 如果是需要查询的 metric,读取数据,判断 label 是否匹配查询条件
- 如果所有条件匹配,拿到 metric 的值,返回给 Prometheus
例子
我写了一个简单的程序,prom-porter 来验证我的想法,使用很简单,编译好 write 和 read storage,在用户的 Prometheus 那边加上 Write Storage 的地址:
remote_write:
- url: "http://localhost:1234/write"
启动之后,Prometheus 就会将自己的 metrics 给写入到 Write Storage,然后我们将 一段时间数据 dump 出来
curl http://localhost:1234/dump?start=timestamp_ms&end=timestamp_ms
将得到的数据用 Read Storage 载入启动,同时在我们自己的 Prometheus 配置上 Read Storage 地址:
remote_read:
- url: "http://localhost:1235/read"
然后我们就可以在 Grafana 上面指定好我们自己的 Prometheus,进行查询了,下图就是一个简单的 PD 面板,可以看到,数据都能正常的显示出来,不过因为只有一段时间的数据,所以超过这段时间的范围了,就不能查询了。
小结
上面只是我的一个简单尝试,如果有更好的办法,欢迎联系我,我的邮箱 [email protected]