专栏首页CNCFPrometheus新特性:分块的、流式的远程读API版本

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

作者: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有效载荷:

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。

响应同样简单:

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方法返回类型中显示。界面如下图所示:

// 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消息(帧),如下所示:

// 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上提交或在邮件列表上提问

本文分享自微信公众号 - CNCF(lf_cncf),作者:Prometheus

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Thanos项目

    说明:Thanos是一组组件,组成一个高度可用的度量系统,具有无限的存储容量,无缝地添加到现有的Prometheus部署之上。Thanos利用Prometheu...

    CNCF
  • Cortex:多租户、可横向扩展的Prometheus即服务

    Prometheus是用于监控和可观察性的标准开源解决方案之一。 Prometheus于2012年起源于SoundCloud,迅速获得广泛采用,后来成为首批CN...

    CNCF
  • Prometheus用户分享:Presslabs

    继续我们对Prometheus用户的一系列访谈,来自Presslabs的Mile Rosu谈到了他们的监控之旅。

    CNCF
  • Prometheus 如何做到“活学活用”,大牛总结的避坑指南

    监控系统的历史悠久,是一个很成熟的方向,而 Prometheus 作为新生代的开源监控系统,慢慢成为了云原生体系的事实标准,也证明了其设计很受欢迎。

    民工哥
  • Prometheus HA详解

    以下所有操作都是在k8s集群中完成,如果你是VM或者物理机在配置方面不会有太大区别;

    笨兔儿
  • 使用Prometheus实现大规模的应用程序监视【Containers】

    我们有充分的理由证明Prometheus是一个日益流行的开源工具。开源工具可以为应用程序和服务器提供监视和警报。 Prometheus的强大优势在于监视服务器端...

    王欣壳
  • 《Prometheus监控实战》第2章 Prometheus简介

    yeedomliu
  • GitHub 热榜:适合初学者学习的 Prometheus 监控系统

    在前面的《GitHub 热榜:轻量级无 Agent 的自动化运维平台!》文章中,简单的描述了下传统运维以及到现在的运维所接触的监控平台,spug 是面向中小型企...

    杰哥的IT之旅
  • prometheus基础

    Prometheus 是由前 Google 工程师从 2012 年开始在 Soundcloud 以开源软件的形式进行研发的系统监控和告警工具包,自此以后,许多公...

    行 者
  • 一文了解Prometheus

    Prometheus 是一套开源的监控系统。设计思路来自于Google的borgmon 监控系统(由工作在 SoundCloud的Google 前员工在2012...

    用户1278550

扫码关注云+社区

领取腾讯云代金券