打破瓶颈 | Prometheus Remote Storage 实践
Prometheus 的设计者非常看重监控系统自身的稳定性,所以 Prometheus 仅仅依赖了本地文件系统,而这就决定了 Prometheus 自身并不适合存储长期数据。
“长期”具体是多久,需要根据具体的数据量和服务器资源来看。如果数据不过期,最先达到瓶颈的资源通常是内存,因为 Prometheus 会将需要的 time series 都先读到内存,所以一个时间范围长,涉及 time series 非常多的 query 很容易触发 OOM。
Prometheus 起初打算寻找一个合适的外部存储,但发现现有的时序数据库都不能很好地满足 Prometheus 的要求。
详见 Prometheus issue 史上的 #10:
https://github.com/prometheus/prometheus/issues/10
所以 Prometheus 提供了 remote read 和 remote write 的接口,让用户自己去实现对接。
Prometheus doc 中对 Prometheus 与外部系统集成方式
Adapter 是一个中间组件,Prometheus 与 Adapter 之间通过由 Prometheus 定义的标准格式发送和接收数据。Adapter 与外部存储系统之间的通信可以自定义。目前 Prometheus 和 Adapter 之间通过 grpc 通信。Prometheus 将 samples 发送到 Adapter。为了提高效率,samples 会在队列中先缓存,再打包发送给 Adapter。而一个读请求中包含了 start_timestamp,end_timestamp 和 label_matchers,response 则包含所有 match 到的 time series 。也就是说,Prometheus 仅通过 Adapter 来获取时间序列,进一步的处理都在 Prometheus 中完成。
Prometheus v2.0.0 中 RemoteWriteConfig 结构
Prometheus v2.0.0 中 RemoteWriteConfig 的结构定义了数据发送给 Remote Storage 的方式。尽管在官方文档中 remote read 和 remote write 的配置还没有稳定,我们还是可以从代码中来一探究竟。HTTPClientConfig 可以用来配置 HTTP 相关的 auth 信息,proxy 方式,以及 tls。WriteRelabelConfigs 用在发送过程中对 timeseries 进行 relabel。QueueConfig 定义了发送队列的 batch size,queue 数量,发送失败时的重试次数与等待时间等参数。默认的 QueueConfig 如下:
默认的 QueueConfig
可以看到 Prometheus 默认定义了 1000 个 queue,batch size 为 100,预期可以达到 1M samples/s 的发送速率。Prometheus 输出了一些 queue 相关的指标,例如 failed_samples_total, dropped_samples_total,如果这两个指标的 rate 大于 0,就需要说明 Remote Storage 出现了问题导致发送失败,或者队列满了导致 samples 被丢弃掉。
再来看看 RemoteReadConfig 结构:
RemoteReadConfig 结构
ReadRecent 如果为 false,Prometheus 会在处理查询时比较本地存储中最早的数据的 timestamp 与 query 的 start timestamp,如果发现需要的数据都在本地存储中,则会跳过对 Remote Storage 的查询。
这是一个比较重要的优化,详情可见 #3129:
https://github.com/prometheus/prometheus/pull/3129
Prometheus 与 Influxdb 之间的数据格式转化很方便,所以 Prometheus 与 Influxdb 的对接也是比价简单的。Influxdb 官方提供了用来对接 Prometheus 的 read 和 write api,所以 Adapter 可以去掉。遗憾的是 Influxdb 集群不再开源。所以本文中也就没有过多去探究 Influxdb。
Read and Write api:
https://github.com/influxdata/influxdb/pull/8784
Opentsdb 是一个基于 hbase 的分布式时序数据库。它的一大优势便是长期保存大量数据,并且能够水平扩展。本文中使用的 Opentsdb 版本是 v2.3.0。
Opentsdb 中的 sample 格式为:
sys.cpu.user 是指标名,host=webserver01 是其中一个 tag,类似于 Prometheus 中的 label,1356998400 是时间戳(unix timestamp,精度为秒或毫秒),50 是值(支持 8 byte 整数或单精度浮点数,在 2.4 及之后的版本中会支持双精度浮点数)。
将 Prometheus 的数据写入 Opentsdb 需要注意以下几点:
Metric name 和 tag value 需要 escape。Opentsdb 对 metric name 和 tag value 的约束比 Prometheus 更严格。Prometheus 的 remote_storage_adapter 中定义了一些 escape 规则。
Prometheus 中 timestamp 精度默认为毫秒,在 Opentsdb 中则需要开启相应的配置项才能支持毫秒。但如果 scrapeinterval 是 10s 级别的话,秒级精度也足够了。
Opentsdb 有 tag 数的限制,默认为 20。可以通过 tsd.storage.max_tags 来配置。
Prometheus 的 remote_storage_adapter 不支持从 Opentsdb 中读取数据。为了查询 Opentsdb 中存储的数据,可以直接使用 grafana。
下图是在 grafana 中分别从 Opentsdb 和 Prometheus 查询同样的指标得到的结果:
我们也可以自己实现一个 Adapter,以支持 Prometheus 从 Opentsdb 直接读取数据。根据前面对 Prometheus read 协议的描述,只需要实现 “=”, “!=”, “=~”, “!~” 这四种 matcher。”=” 和 “!=” 可以转化为 Opentsdb 中的 “literal_or” filter。而 “=~” 和 “!~” 没有办法直接转化成 filter,只能先转化成 match all, 从 Opentsdb 中查出数据然后再过滤(这样可能会导致 OOM,但是一般来说还有其它 filter,加上 downsample,可以让返回的数据量不至于过大)。
下面是两个 Prometheus:
一个仅从 local storage 读取数据(同时向 Opentsdb 写数据);
另一个仅从 Opentsdb 读数据,执行相同的查询得到的结果对比。
可以看到从 remoteread 的查询速度相对较慢,但结果是基本一致的:
prometheus 从 local storage 读取数据
Prometheus 从 Remote Storage 读取数据
用 Opentsdb 来作为 Prometheus 的长期存储可以说是一个比较可靠的方案。另外有许多其它的时序数据库也提供了对 Prometheus 的集成,详见:
https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage
关于 Opentsdb 的 schema,可参考:
http://opentsdb.net/docs/build/html/user_guide/backends/hbase.html
https://yq.aliyun.com/articles/54785
本文作者:Caicloud 软件工程师 蔡通
-END-
推荐阅读:
才云 Austin KubeCon 三日志:发布 Compass v2.5.0