前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Prometheus新特性:分块的、流式的远程读API版本

Prometheus新特性:分块的、流式的远程读API版本

作者头像
CNCF
发布2019-12-04 11:01:07
1.5K0
发布2019-12-04 11:01:07
举报
文章被收录于专栏:CNCFCNCF

作者:Bartlomiej Plotka(@bwplotka)

新的Prometheus 2.13.0版本已经发布,并且一如既往地包含了许多修复和改进。你可以到这里看发生了什么变化。然而,有一个特性是一些项目和用户一直在等待的:分块的、流式的远程读API版本。

https://github.com/prometheus/prometheus/blob/release-2.13/CHANGELOG.md https://docs.google.com/document/d/1JqrU3NjM9HoGLSTPYOvR217f5HBKBiJTqikEB9UiJL0/edit#heading=h.3en2gbeew2sa

在本文中,我将深入介绍我们在远程协议中更改了什么、更改的原因以及如何有效地使用它。

远程API

从版本1.x,Prometheus有能力直接与它的存储使用远程API交互。

这个API允许第三方系统通过两种方法与度量数据交互:

  • 写 - 接收Prometheus推送的样本
  • 读 - 从Prometheus拉取样本

这两种方法都使用HTTP和使用protobufs编码的消息。使用snappy对这两个方法的请求和响应进行了压缩。

远程写

这是将Prometheus数据复制到第三方系统中最流行的方法。在这种模式下,Prometheus通过周期性地向给定的端点发送一批样本来传输样本。

远程写最近在3月份得到了极大的改进,使用了基于WAL的远程写,提高了可靠性和资源消耗。值得注意的是,这里提到的几乎所有第三方集成都支持远程写。

https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage

远程读

读的方法不太常见。它是在2017年3月添加的(服务器端),从那时起就没有显著的开发。

Prometheus 2.13.0包含了Read API中已知资源瓶颈的修复。本文将重点介绍这些改进。

远程读取的关键思想,是允许直接查询Prometheus storage (TSDB),而无需PromQL评估。它类似于PromQL引擎用于从存储中检索数据的Querier接口。

这本质上允许对Prometheus收集的TSDB时间序列进行读访问。远程读取的主要用例有:

  • 无缝Prometheus升级之间的不同数据格式的Prometheus,所以有Prometheus从另一个Prometheus阅读。
  • Prometheus能够从第三方长期存储系统读取,例如InfluxDB。
  • 第三方系统从Prometheuse查询数据,例如Thanos。

远程读API公开了一个简单的HTTP端点,它期望以下protobuf有效载荷:

代码语言:javascript
复制
message ReadRequest {
  repeated Query queries = 1;
}

message Query {
  int64 start_timestamp_ms = 1;
  int64 end_timestamp_ms = 2;
  repeated prometheus.LabelMatcher matchers = 3;
  prometheus.ReadHints hints = 4;
}

有了这个有效载荷,客户端可以请求特定的系列匹配给定的matchers和时间范围,包括end和start。

响应同样简单:

代码语言:javascript
复制
message ReadResponse {
  // In same order as the request's queries.
  repeated QueryResult results = 1;
}

message Sample {
  double value    = 1;
  int64 timestamp = 2;
}

message TimeSeries {
  repeated Label labels   = 1;
  repeated Sample samples = 2;
}

message QueryResult {
  repeated prometheus.TimeSeries timeseries = 1;
}

Remote read返回匹配的时间序列,其中包含值和时间戳的原始样本。

问题陈述

对于这样一个简单的远程读取,有两个关键问题。它很容易使用和理解,但是在我们定义的protobuf格式的单个HTTP请求中没有流(streaming)功能。其次,响应包括原始样本(float64值和int64时间戳),而不是称为“chunk”的经过编码、压缩的一批样本,这些样本用于在TSDB中存储度量。

远程,没有流的,读取服务器算法为:

  1. 解析请求。
  2. 从TSDB中选择指标。
  3. 对所有解码系列:
    • 对所有样本:
      • 加入响应原基
  4. 编列回应。
  5. Snappy压缩。
  6. 发送回HTTP响应。

远程读取的整个响应必须以原始的、未压缩的格式进行缓冲,以便在将其发送到客户机之前将其编列到一个可能非常大的protobuf消息中。然后,整个响应必须再次在客户机中得到完全的缓冲,以便能够从接收到的protobuf解组它。只有在此之后,客户才能使用原始样本。

这是什么意思?这意味着,比方说,仅匹配10,000个系列的8个小时的请求,就会占用客户端和服务器各自分配的2.5GB内存!

下面是Prometheus和Thanos边车(远程读客户端)在远程读请求期间的内存使用度量:

值得注意的是,查询10,000个系列并不是一个好主意,即使对于Prometheus原生HTTP query_range端点也是如此,因为你的浏览器根本不愿意获取、存储和呈现数百兆字节的数据。此外,出于指示板和呈现的目的,拥有这么多数据是不现实的,因为人类不可能读取它。这就是为什么我们通常创建不超过20个系列的查询。

这很好,但是一种非常常见的技术是以这样的方式组合查询,即查询返回聚合的20系列,然而在查询引擎的底层,可能需要接触数千个系列来评估响应(例如当使用aggregators时)。这就是为什么像Thanos这样的系统,除了其他数据,也使用来自远程读取的TSDB数据,通常情况下,请求很重。

解决方案

为了解释这个问题的解决方案,理解Prometheus如何在查询数据时进行迭代是很有帮助的。核心概念可以在被称为SeriesSet的查询器的Select方法返回类型中显示。界面如下图所示:

代码语言:javascript
复制
// SeriesSet contains a set of series.
type SeriesSet interface {
    Next() bool
    At() Series
    Err() error
}

// Series represents a single time series.
type Series interface {
    // Labels returns the complete set of labels identifying the series.
    Labels() labels.Labels
    // Iterator returns a new iterator of the data of the series.
    Iterator() SeriesIterator
}

// SeriesIterator iterates over the data of a time series.
type SeriesIterator interface {
    // At returns the current timestamp/value pair.
    At() (t int64, v float64)
    // Next advances the iterator by one.
    Next() bool
    Err() error
}

这些接口集允许流程内部的流。我们不再需要一个预先计算的包含样本的序列列表。使用这个接口,每个SeriesSet.Next()实现都可以根据需要获取series。以类似的方式,在每个系列中。我们还可以分别通过SeriesIterator.Next动态地获取每个样本。

有了这个契约,Prometheus可以最小化分配的内存,因为PromQL引擎可以在样本上进行迭代,从而优化查询的性能。TSDB以同样的方式实现了SeriesSet,它以一种从文件系统中逐个存储的块中获取序列的最佳方式,从而最小化了分配。

这对于远程read API非常重要,因为我们可以使用迭代器重用相同的流模式,方法是为单个系列以几块的形式向客户机发送响应片段。由于protobuf没有原生定界逻辑,所以我们扩展了proto定义,允许发送一组小的协议缓冲区消息,而不是单个的大消息。我们将此模式称为STREAMED_XOR_CHUNKS远程读取,而旧模式称为SAMPLES。扩展协议意味着Prometheus不再需要缓冲整个响应。相反,它可以依次处理每个系列,并为每个系列集发送单个帧。它可以按顺序处理每个系列,并为每个SeriesSet.Next或SeriesIterator.Next批处理发送单个帧,从而有可能为下一个系列重用相同的内存页面!

现在,STREAMED_XOR_CHUNKS远程读取的响应是一组Protobuf消息(帧),如下所示:

代码语言:javascript
复制
// ChunkedReadResponse is a response when response_type equals STREAMED_XOR_CHUNKS.
// We strictly stream full series after series, optionally split by time. This means that a single frame can contain
// partition of the single series, but once a new series is started to be streamed it means that no more chunks will
// be sent for previous one.
message ChunkedReadResponse {
  repeated prometheus.ChunkedSeries chunked_series = 1;
}

// ChunkedSeries represents single, encoded time series.
message ChunkedSeries {
  // Labels should be sorted.
  repeated Label labels = 1 [(gogoproto.nullable) = false];
  // Chunks will be in start time order and may overlap.
  repeated Chunk chunks = 2 [(gogoproto.nullable) = false];
}

正如你所看到的,每帧不再包括原始样本。这是我们做的第二个改进:我们发送成批的消息样本块(有关块的更多信息,请参见本视频),这些消息块与我们存储在TSDB中的完全相同。

我们最终得到了以下服务器算法:

  1. 解析请求。
  2. 从TSDB中选择指标。
  3. 对所有系列:
    • 对所有样本:
      • 编码成块
        • 如果帧是>=1MB;break
    • 编列ChunkedReadResponse消息。
    • Snappy压缩
    • 发送消息

你可以在这里找到完整的设计。

https://docs.google.com/document/d/1JqrU3NjM9HoGLSTPYOvR217f5HBKBiJTqikEB9UiJL0/edit#

基准测试

与旧的解决方案相比,新方法的性能如何?

让我们比较一下Prometheus 2.12.0和2.13.0之间的远程读取特性。对于本文开头给出的初始结果,我使用Prometheus作为服务器,而Thanos边车作为remote read的客户端。我通过使用grpcurl对Thanos边车运行gRPC调用来测试远程读请求。测试在我的笔记本电脑(联想X1 16GB,i7 8th)上进行,Kubernetes在docker中(使用kind)。

数据是人工生成的,表示高度动态的10,000系列(最坏的情况)。

完整的测试平台可以在thanosbench repo中找到。

https://github.com/thanos-io/thanosbench/blob/master/benchmarks/remote-read/README.md

内存

没有流

有流

减少内存是我们的解决方案的主要目标。Prometheus不是分配GBs内存,而是在整个请求期间缓冲大约50MB,而Thanos只使用少量内存。多亏了Thanos gRPC StoreAPI,边车现在是一个非常简单的代理。

此外,我尝试了不同的时间范围和系列的数量,但正如我所期望的那样,我始终看到Prometheus的最大分配是50MB,而Thanos什么都看不到。这证明,无论你请求多少样例,我们的远程读操作每次都使用恒定的内存。每个请求分配的内存受数据基数的影响也大大减小,因此获取的序列数量与以前一样。

这使得在并发限制的帮助下,更容易地针对用户流量进行容量规划。

CPU

没有流

有流

在我的测试中,CPU使用情况也得到了改善,CPU使用时间减少了2倍。

延迟

由于流和较少的编码,我们还实现了减少远程读请求延迟。

8h范围与10,000系列的远程读请求延迟:

2h范围与10,000系列的远程读请求延迟:

除了大约2.5倍的低延迟外,与非流版本相比,响应立即进行流处理,其中客户端延迟为27秒(real减去user时间),仅在Prometheus和Thanos端进行处理和封送。

兼容性

远程读取以向后和向前兼容的方式扩展。这要感谢protobuf和accepted_response_types字段,这两个字段对于较老的服务器是忽略的。同时,如果假定旧的SAMPLES远程读取的旧客户机不提供accepted_response_types,服务器也可以正常工作。

远程读协议以向后和向前兼容的方式扩展:

  • v2.13.0之前的Prometheus将安全地忽略新客户端提供的accepted_response_types字段,并采用SAMPLES模式。
  • 对于没有提供accepted_response_types参数的老客户端,v2.13.0之后的Prometheus将默认使用SAMPLES模式。

使用

为了使用Prometheus v2.13.0中新的流远程读取,第三方系统必须向请求添加accepted_response_types = [STREAMED_XOR_CHUNKS]。

然后Prometheus将发送ChunkedReadResponse,而不是旧的信息。对于CRC32 Castagnoli校验和,每个ChunkedReadResponse消息都遵循varint大小和固定大小的bigendian uint32。

对于Go,建议使用ChunkedReader直接从流中读取。

注意,storage.remote.read-sample-limit标志不再对STREAMED_XOR_CHUNKS有效。storage.remote.read-concurrent-limit与之前一样有效。

还有新的选项storage.remote.read-max-bytes-in-frame 控制每个消息的最大大小。建议将其保留为1MB,因为谷歌建议保留protobuf消息不大于1MB。

https://developers.google.com/protocol-buffers/docs/techniques#large-data

如前所述,Thanos在这方面得到了很大的改进。v0.7.0中添加了流远程读,因此,这个或任何后续版本,只要在Thanos 边车中使用Prometheus 2.13或更新版本,就会自动使用流远程读。

下一步

发行版2.13.0引入了扩展远程读和Prometheus服务器端实现,但是在撰写本文时,为了充分利用扩展远程读协议,我们还需要做一些事情:

  • 支持Prometheus远程阅读客户端:开发中
  • 避免在远程读取过程中对块进行重新编码:开发中 https://github.com/prometheus/prometheus/issues/5926 https://github.com/prometheus/prometheus/pull/5882

总结

综上所述,分块的远程读流的主要好处是:

  • 客户端和服务器都能够使用几乎恒定的内存大小和每个请求。这是因为Prometheus在远程读取时只处理和发送单个小帧,而不是整个响应。这极大地帮助进行容量规划,特别是对于内存这样的不可压缩资源。
  • Prometheus服务器不再需要在远程读取时将数据块解码为原始样本。如果系统重用本地TSDB XOR压缩(就像Thanos一样),那么客户端也可以进行编码。

和往常一样,如果你有任何问题或反馈,请在GitHub上提交或在邮件列表上提问

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CNCF 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
Prometheus 监控服务
Prometheus 监控服务(TencentCloud Managed Service for Prometheus,TMP)是基于开源 Prometheus 构建的高可用、全托管的服务,与腾讯云容器服务(TKE)高度集成,兼容开源生态丰富多样的应用组件,结合腾讯云可观测平台-告警管理和 Prometheus Alertmanager 能力,为您提供免搭建的高效运维能力,减少开发及运维成本。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档