Prometheus Remote-Write 2.0 规范 [实验性]

  • 版本: 2.0-rc.3
  • 状态: 实验性
  • 日期:2024 年 5 月

Remote-Write 规范,总的来说,旨在记录 Prometheus 和兼容 Remote-Write 的发送方如何将数据发送给 Prometheus 或兼容 Remote-Write 的接收方的标准。

本文档旨在定义 Prometheus Remote-Write API 的第二个版本,对协议和语义进行了微小改动。这个第二版增加了一个新的 Protobuf 消息,带来了新功能,以支持更多的用例和更广泛的采用,同时还能提高性能和节省成本。第二版还废弃了 1.0 Remote-Write 规范中的旧 Protobuf 消息,并为可靠性目的增加了强制性的 `X-Prometheus-Remote-Write-*-Written` HTTP 响应头。最后,本规范概述了如何使用现有的基本内容协商请求头来实现向后兼容的发送方和接收方(即使在单个端点下)。如果需要,未来的次要版本可能会引入更高级、自动化的内容协商机制。有关 2.0 规范背后的理由,请参阅正式提案

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。

注意这是 Remote-Write 2.0 规范的一个候选发布版本。这意味着该规范目前处于实验状态——预计不会有重大变更,但我们保留在必要时根据早期采用者的反馈破坏兼容性的权利。潜在的反馈、问题和建议应作为评论添加到开放提案的 PR中。

简介

背景

Remote-Write 协议旨在可靠地将样本从发送方实时传播到接收方,且不发生丢失。

Remote-Write 协议被设计为无状态的;严格来说,消息之间没有通信。因此,该协议不被认为是“流式”的。为了实现流式效果,应通过同一连接(例如使用 HTTP/1.1 或 HTTP/2)发送多条消息。我们曾考虑过 gRPC 等“高级”技术,但当时它们并未被广泛采用,并且在 AWS EC2 ELB 等负载均衡器后面向互联网暴露 gRPC 服务具有挑战性。

Remote-Write 协议提供了批处理的机会,例如,在单个请求中发送不同时间序列的多个样本。通常不期望在同一请求中为同一时间序列发送多个样本,尽管 Protobuf 消息中对此提供了支持。

测试套件可以在 https://github.com/prometheus/compliance/tree/main/remote_write_sender 找到。针对 remote write 2.0 兼容性的合规性测试仍在进行中

术语表

在本文档中,遵循以下定义

  • Remote-Write 是此 Prometheus 协议的名称。
  • 协议是一种通信规范,使客户端和服务器能够传输指标。
  • Protobuf 消息(或 Proto 消息)指的是此协议的数据结构的内容类型定义。由于本规范专门使用Google Protocol Buffers ("protobuf"),其模式在一个 "proto" 文件中定义,并由单个 Protobuf "消息" 表示。
  • 线路格式是指数据在网络中传输时的格式。在 Remote-Write 的情况下,这始终是压缩后的二进制 protobuf 格式。
  • 发送方是发送 Remote-Write 数据的实体。
  • 接收方是接收(写入)Remote-Write 数据的实体。已写入的含义由接收方决定,例如,通常指将接收到的数据存储在数据库中,但也可能只是验证、拆分或增强它。
  • 已写入指的是接收方已接收并接受的数据。至于是否已将此数据摄入持久化存储、写入 WAL 等,则由接收方自行决定。唯一的区别是接收方已接受此数据,而不是通过错误响应明确拒绝它。
  • 样本是一个(时间戳,值)对。
  • 直方图是一个(时间戳,直方图值)对。
  • 标签是一个(键,值)对。
  • 时间序列是由一组唯一标签标识的样本列表。

定义

协议

Remote-Write 协议必须由 RPC 组成,其请求体使用 Google Protocol Buffers 进行序列化,然后进行压缩。

protobuf 序列化必须使用以下任一 Protobuf 消息

  • Remote-Write 1.0 规范中引入的 `prometheus.WriteRequest`。从 2.0 版本开始,此消息已被弃用。它应该仅用于兼容性目的。发送方和接收方可以不支持 `prometheus.WriteRequest`。
  • 在本规范中引入并在下面定义的 `io.prometheus.write.v2.Request`。发送方和接收方应尽可能使用此消息。发送方和接收方必须支持 `io.prometheus.write.v2.Request`。

Protobuf 消息必须使用二进制线路格式。然后,必须使用Google 的 Snappy进行压缩。必须使用 Snappy 的块格式——不得使用帧格式

发送方必须在 HTTP POST 请求的主体中发送序列化和压缩后的 Protobuf 消息,并通过 HTTP 将其发送到接收方提供的 URL 路径。接收方可以指定任何 HTTP URL 路径来接收指标。

发送方必须随 HTTP 请求发送以下保留头

  • Content-Encoding
  • Content-Type
  • X-Prometheus-Remote-Write-Version
  • User-Agent

发送方可以允许用户添加自定义 HTTP 头;它们不得允许用户以发送保留头的方式配置它们。

Content-Encoding

Content-Encoding: <compression>

Content-Encoding 请求头必须遵循 RFC 9110。发送方必须使用 `snappy` 值。接收方必须支持 `snappy` 压缩。新的、可选的压缩算法可能会在 2.x 或更高版本中出现。

Content-Type

Content-Type: application/x-protobuf
Content-Type: application/x-protobuf;proto=<fully qualified name>

Content-Type 请求头必须遵循 RFC 9110。发送方必须使用 `application/x-protobuf` 作为唯一的媒体类型。发送方可以在头的值中添加 `;proto=` 参数,以指示所使用的 Protobuf 消息的完全限定名,即上面提到的两种之一。因此,发送方必须发送以下三种支持的头值之一

对于在 PRW 1.0 中引入的、由 `prometheus.WriteRequest` 标识的已弃用消息

  • Content-Type: application/x-protobuf
  • Content-Type: application/x-protobuf;proto=prometheus.WriteRequest

对于在 PRW 2.0 中引入的、由 `io.prometheus.write.v2.Request` 标识的消息

  • Content-Type: application/x-protobuf;proto=io.prometheus.write.v2.Request

与 1.x 接收方通信时,发送方为了向后兼容,应该使用 `Content-Type: application/x-protobuf`。否则,发送方应该使用 `Content-Type: application/x-protobuf;proto=io.prometheus.write.v2.Request`。更多的 Protobuf 消息可能会在 2.x 或更高版本中出现。

接收方必须使用 content type 头来确定要使用的 Protobuf 消息模式。意外的错误模式选择可能导致不确定的行为(例如数据损坏)。

注意由于 `io.prometheus.write.v2.Request` 中有保留字段,接收方意外地使用错误的模式与 `prometheus.WriteRequest` 将导致空消息。这通常是为了方便避免意外错误,但不应依赖它——未来的 Protobuf 消息可能不具备此功能。

X-Prometheus-Remote-Write-Version

X-Prometheus-Remote-Write-Version: <Remote-Write spec major and minor version>

与 1.x 接收方通信时,发送方必须使用 `X-Prometheus-Remote-Write-Version: 0.1.0` 以实现向后兼容。否则,发送方应该使用其兼容的最新 Remote-Write 版本,例如 `X-Prometheus-Remote-Write-Version: 2.0.0`。

User-Agent

User-Agent: <name & version of the Sender>

发送方必须包含一个 user agent 头,该头应遵循 RFC 9110 User-Agent 头格式

响应

成功写入所有数据的接收方必须返回一个成功的 2xx HTTP 状态码。在这种成功的情况下,接收方的响应体应该为空,状态码应该是204 HTTP No Content;发送方必须忽略响应体。响应体保留供将来使用。

如果接收方已知的任何已发送数据片段(例如样本、直方图、样本值)未能成功写入(无论是部分写入还是完全写入拒绝),接收方不得返回 2xx HTTP 状态码。在这种情况下,接收方必须在响应体中提供人类可读的错误消息。接收方的错误应包含有关被拒绝的样本数量及其原因的信息。发送方不得尝试解释错误消息,而应按原样记录它。

以下小节指定了围绕头和不同写入错误情况的发送方和接收方语义。

必需的 `Written` 响应头

成功进行内容协商后,接收方会处理(写入)收到的数据批次。一旦完成(无论成功或失败),对于每个重要的数据片段(目前是样本、直方图和范例),接收方都必须发送一个专用的 HTTP `X-Prometheus-Remote-Write-*-Written` 响应头,其中包含成功写入元素的精确数量。

每个头的值必须是单个 64 位整数。头名称必须如下所示

X-Prometheus-Remote-Write-Samples-Written <count of all successfully written Samples>
X-Prometheus-Remote-Write-Histograms-Written <count of all successfully written Histogram samples>
X-Prometheus-Remote-Write-Exemplars-Written <count of all successfully written Exemplars>

在收到 2xx 或 4xx 状态码时,发送方可以假设任何缺失的 `X-Prometheus-Remote-Write-*-Written` 响应头意味着该类别(例如样本)中的任何元素都未被接收方写入(计数为 `0`)。由于存在遇到没有此功能的 1.0 接收方的风险,发送方在使用已弃用的 `prometheus.WriteRequest` Protobuf 消息时不得做此假设。

发送方可以使用这些头来确认数据的哪些部分已成功被接收方写入。常见用例

  • 更好地处理部分写入失败情况:发送方可以使用这些头进行更准确的客户端插桩和错误处理。
  • 检测损坏的 1.0 接收方实现:当使用 `io.prometheus.write.v2.Request` 请求发送数据并收到 2xx HTTP 状态码,但未从接收方收到任何 `X-Prometheus-Remote-Write-*-Written` 响应头时,发送方应假设状态码为415 HTTP 不支持的媒体类型。这是 1.0 接收方不检查 `Content-Type` 请求头的一个常见问题;用 `prometheus.WriteRequest` 模式意外解码 `io.prometheus.write.v2.Request` 负载会导致空结果且无解码错误。
  • 检测其他损坏的实现或问题:发送方可以使用这些头来检测损坏的发送方和接收方实现或其他问题。

发送方不得从远程写入响应头中推断接收方实现的 Remote Write 规范版本。

未来可能会有更多(可选的)头,例如当添加了更多值得确认的实体或字段时。

部分写入

发送方应使用 Remote-Write 在单个请求中发送多个时间序列的样本。因此,接收方可以在一个写入请求中写入有效的样本,即使该请求也包含一些无效或未写入的样本,这代表了部分写入的情况。在这种情况下,接收方必须返回非 2xx 状态码,并遵循无效样本部分写入重试部分。

不支持的请求内容

如果接收方不支持发送方提供的给定内容类型或编码,则必须返回 415 HTTP 不支持的媒体类型 状态码。

为实现向后兼容性,发送方应预期从 1.x 接收方收到因上述原因返回的 400 HTTP 错误请求

无效样本

接收方可能不支持某些指标类型或样本(例如,一个接收方可能会拒绝没有指定元数据类型或没有创建时间戳的样本,而另一个接收方可能会接受这样的样本)。样本是否无效由接收方决定。除非发生可重试的部分写入,否则对于包含任何无效样本的写入请求,接收方必须返回 400 HTTP 错误请求 状态码。

发送方不得对 4xx HTTP 状态码(除了 429)进行重试,接收方必须使用这些状态码来指示写入操作将永远无法成功,不应重试。发送方可以对 415 HTTP 状态码使用不同的内容类型或编码进行重试,以查看接收方是否支持。

重试与退避

接收端(Receiver)可以(MAY)返回 429 HTTP Too Many Requests 状态码来表示服务器过载。接收端可以(MAY)返回 Retry-After 标头来指示下一次写入尝试的时间。接收端可以(MAY)返回 5xx HTTP 状态码来表示内部服务器错误。

发送端(Sender)可以(MAY)在收到 429 HTTP 状态码时进行重试。发送端必须(MUST)在收到 5xx HTTP 状态码时重试写入请求。发送端必须(MUST)使用退避算法以防止服务器不堪重负。发送端可以(MAY)处理 Retry-After 响应标头 来估算下一次重试的时间。

处理 429 和 5xx 的区别在于,当接收端无法跟上请求量,或接收端选择对发送端进行速率限制以保护其可用性时,可能会出现发送端“落后”的情况。因此,发送端可以选择在收到 429 时不重试,这使得当出现发送端侧错误(例如流量过大)时仍能取得进展,而当出现接收端侧错误(5xx)时数据不会丢失。

部分写入的重试

当接收端期望发送端重试整个请求时,可以在部分写入或部分无效样本的情况下返回 5xx HTTP 或 429 HTTP 状态码。在这种情况下,接收端必须(MUST)支持幂等性,因为发送端可以(MAY)使用相同的请求进行重试。

向后和向前兼容性

该协议遵循语义化版本 2.0:任何与 2.x 兼容的接收端必须(MUST)能够读取任何与 2.x 兼容的发送端,反之亦然。破坏性或向后不兼容的更改将导致规范版本升级到 3.x。

Protobuf 消息(在线上格式中)本身在某些方面是向前/向后兼容的。

  • 从 Protobuf 消息中删除字段需要提升主版本号。
  • 添加(可选)字段可以在次要版本升级中完成。

换句话说,这意味着未来 2.x 的次要版本可以(MAY)向 io.prometheus.write.v2.Request 添加新的可选字段、新的压缩方式、Protobuf 消息和协商机制,只要它们是向后兼容的(例如,对接收端和发送端都是可选的)。

2.x 与 1.x 的兼容性

2.x 协议通过引入一个新的、强制性的 io.prometheus.write.v2.Request Protobuf 消息并弃用 prometheus.WriteRequest,从而打破了与 1.x 的兼容性。

2.x 发送端可以(MAY)通过允许用户配置发送端应使用的内容类型来支持 1.x 接收端。如果接收端返回 415 HTTP 状态码,2.x 发送端也可以(MAY)自动回退到不同的内容类型。

Protobuf 消息

io.prometheus.write.v2.Request

io.prometheus.write.v2.Request 引用了新的 Protobuf 消息,旨在替换并弃用 Remote-Write 1.0 的 prometheus.WriteRequest 消息。

完整的 schema 和事实来源位于 Prometheus 仓库的 prompb/io/prometheus/write/v2/types.proto 文件中。gogo 依赖和选项可以(CAN)被忽略(最终将被移除)。它们不属于规范的一部分,因为它们不影响序列化格式。

新的 io.prometheus.write.v2.Request 的简化版本如下所示。

// Request represents a request to write the given timeseries to a remote destination.
message Request {
  // Since Request supersedes 1.0 spec's prometheus.WriteRequest, we reserve the top-down message
  // for the deterministic interop between those two.
  // Generally it's not needed, because Receivers must use the Content-Type header, but we want to
  // be sympathetic to adopters with mistaken implementations and have deterministic error (empty
  // message if you use the wrong proto schema).
  reserved 1 to 3;

  // symbols contains a de-duplicated array of string elements used for various
  // items in a Request message, like labels and metadata items. For the sender's convenience
  // around empty values for optional fields like unit_ref, symbols array MUST start with
  // empty string.
  //
  // To decode each of the symbolized strings, referenced, by "ref(s)" suffix, you
  // need to lookup the actual string by index from symbols array. The order of
  // strings is up to the sender. The receiver should not assume any particular encoding.
  repeated string symbols = 4;
  // timeseries represents an array of distinct series with 0 or more samples.
  repeated TimeSeries timeseries = 5;
}

// TimeSeries represents a single series.
message TimeSeries {
  // labels_refs is a list of label name-value pair references, encoded
  // as indices to the Request.symbols array. This list's length is always
  // a multiple of two, and the underlying labels should be sorted lexicographically.
  //
  // Note that there might be multiple TimeSeries objects in the same
  // Requests with the same labels e.g. for different exemplars, metadata
  // or created timestamp.
  repeated uint32 labels_refs = 1;

  // Timeseries messages can either specify samples or (native) histogram samples
  // (histogram field), but not both. For a typical sender (real-time metric
  // streaming), in healthy cases, there will be only one sample or histogram.
  //
  // Samples and histograms are sorted by timestamp (older first).
  repeated Sample samples = 2;
  repeated Histogram histograms = 3;

  // exemplars represents an optional set of exemplars attached to this series' samples.
  repeated Exemplar exemplars = 4;

  // metadata represents the metadata associated with the given series' samples.
  Metadata metadata = 5;

  // created_timestamp represents an optional created timestamp associated with
  // this series' samples in ms format, typically for counter or histogram type
  // metrics. Created timestamp represents the time when the counter started
  // counting (sometimes referred to as start timestamp), which can increase
  // the accuracy of query results.
  //
  // Note that some receivers might require this and in return fail to
  // write such samples within the Request.
  //
  // For Go, see github.com/prometheus/prometheus/model/timestamp/timestamp.go
  // for conversion from/to time.Time to Prometheus timestamp.
  //
  // Note that the "optional" keyword is omitted due to
  // https://cloud.google.com/apis/design/design_patterns.md#optional_primitive_fields
  // Zero value means value not set. If you need to use exactly zero value for
  // the timestamp, use 1 millisecond before or after.
  int64 created_timestamp = 6;
}

// Exemplar represents additional information attached to some series' samples.
message Exemplar {
  // labels_refs is an optional list of label name-value pair references, encoded
  // as indices to the Request.symbols array. This list's len is always
  // a multiple of 2, and the underlying labels should be sorted lexicographically.
  // If the exemplar references a trace it should use the `trace_id` label name, as a best practice.
  repeated uint32 labels_refs = 1;
  // value represents an exact example value. This can be useful when the exemplar
  // is attached to a histogram, which only gives an estimated value through buckets.
  double value = 2;
  // timestamp represents the timestamp of the exemplar in ms.
  // For Go, see github.com/prometheus/prometheus/model/timestamp/timestamp.go
  // for conversion from/to time.Time to Prometheus timestamp.
  int64 timestamp = 3;
}

// Sample represents series sample.
message Sample {
  // value of the sample.
  double value = 1;
  // timestamp represents timestamp of the sample in ms.
  int64 timestamp = 2;
}

// Metadata represents the metadata associated with the given series' samples.
message Metadata {
  enum MetricType {
    METRIC_TYPE_UNSPECIFIED    = 0;
    METRIC_TYPE_COUNTER        = 1;
    METRIC_TYPE_GAUGE          = 2;
    METRIC_TYPE_HISTOGRAM      = 3;
    METRIC_TYPE_GAUGEHISTOGRAM = 4;
    METRIC_TYPE_SUMMARY        = 5;
    METRIC_TYPE_INFO           = 6;
    METRIC_TYPE_STATESET       = 7;
  }
  MetricType type = 1;
  // help_ref is a reference to the Request.symbols array representing help
  // text for the metric. Help is optional, reference should point to an empty string in
  // such a case.
  uint32 help_ref = 3;
  // unit_ref is a reference to the Request.symbols array representing a unit
  // for the metric. Unit is optional, reference should point to an empty string in
  // such a case.
  uint32 unit_ref = 4;
}

// A native histogram, also known as a sparse histogram.
// See https://github.com/prometheus/prometheus/blob/remote-write-2.0/prompb/io/prometheus/write/v2/types.proto#L142
// for a full message that follows the native histogram spec for both sparse
// and exponential, as well as, custom bucketing.
message Histogram { ... }

所有时间戳必须(MUST)是 int64 类型,表示自 Unix 纪元以来的毫秒数。样本值必须(MUST)是 float64 类型。

对于每个 TimeSeries 消息:

  • 必须(MUST)提供 labels_refs
  • sampleshistograms 中必须(MUST)至少提供一个元素。一个 TimeSeries 不能(MUST NOT)同时包含 sampleshistograms。对于(极少数)会混合浮点数和直方图样本的序列,必须(MUST)使用一个单独的 TimeSeries 消息。
  • 应当(SHOULD)提供 metadata 的子字段。接收端可以(MAY)拒绝未指定 Metadata.type 的序列。
  • 如果序列存在 Exemplar,应当(SHOULD)提供。
  • 对于遵循计数器语义的指标(例如计数器和直方图),应当(SHOULD)提供 created_timestamp。接收端可以(MAY)拒绝未设置 created_timestamp 的序列。

以下小节详细定义了一些 schema 元素。

符号 (Symbols)

io.prometheus.write.v2.Request Protobuf 消息旨在对所有字符串进行驻留,以在标准压缩的基础上获得经过验证的额外压缩和内存效率提升。

必须(MUST)提供 symbols 表,并且它必须(MUST)包含在序列、Exemplar 标签和元数据字符串中使用的去重字符串。symbols 表的第一个元素必须(MUST)是一个空字符串,用于表示空值或未指定的值,例如当 Metadata.unit_refMetadata.help_ref 未提供时。引用必须(MUST)指向 symbols 字符串数组中存在的索引。

序列标签 (Series Labels)

完整的标签集必须(MUST)与每个 SampleHistogram 样本一起发送。此外,与样本关联的标签集:

  • 应当(SHOULD)包含一个 __name__ 标签。
  • 不能(MUST NOT)包含重复的标签名称。
  • 标签名称必须(MUST)按字典序排序。
  • 不能(MUST NOT)包含任何空的标签名称或值。

指标名称、标签名称和标签值必须(MUST)是任何 UTF-8 字符序列。

指标名称应当(SHOULD)遵循正则表达式 [a-zA-Z_:]([a-zA-Z0-9_:])*

标签名称应当(SHOULD)遵循正则表达式 [a-zA-Z_]([a-zA-Z0-9_])*

不符合上述规范的名称可能会给 PromQL 用户带来使用困难(更多详情请参阅 UTF-8 提案)。

以“__”开头的标签名称保留(RESERVED)供系统使用,不应(SHOULD NOT)使用,请参阅 Prometheus 数据模型

接收端也可以(MAY)对标签的数量和长度施加限制,但这取决于接收端的具体实现,超出了本文档的范围。

样本和直方图样本

对于任何给定的 TimeSeries,发送端必须(MUST)按时间戳顺序发送 samples(或 histograms)。发送端可以(MAY)并行发送不同序列的多个请求。

当一个时间序列不再有新数据追加时,发送端应当(SHOULD)发送陈旧标记(stale marker)。如果能够检测到时间序列的中断,发送端必须(MUST)发送陈旧标记,例如:

  • 对于被拉取(抓取)的序列,除非使用了显式时间戳。
  • 对于由记录规则评估产生的序列。

通常,对于已中断的序列不发送陈旧标记,可能导致接收端出现严重的查询时间对齐问题

陈旧标记必须(MUST)通过特殊的 NaN 值 0x7ff0000000000002 来表示。此值不能(MUST NOT)用于其他目的。

通常,发送端可以通过以下技术检测时间序列何时不再追加数据:

  1. 通过服务发现,检测到暴露该序列的目标已消失。
  2. 在连续的抓取中,注意到目标不再暴露该时间序列。
  3. 抓取最初暴露时间序列的目标失败。
  4. 跟踪记录和告警规则的配置和评估。
  5. 对于非抓取来源的指标,跟踪指标的中断(例如,在 k6 中,当基准测试完成时,它可以为每个基准测试的序列发出一个陈旧标记)。

元数据 (Metadata)

元数据应当(SHOULD)遵循 Prometheus 官方关于类型(Type)帮助(Help)的指南。

元数据可以(MAY)遵循 OpenMetrics 官方关于单位(Unit)的指南。

Exemplars(范例)

每个 Exemplar,如果附加到 TimeSeries

  • 必须(MUST)包含一个值。
  • 可以(MAY)包含标签,例如引用跟踪或请求 ID。如果 Exemplar 引用了一个跟踪,作为最佳实践,它应当(SHOULD)使用 trace_id 标签名。
  • 必须(MUST)包含一个时间戳。虽然在 Prometheus/Open Metrics 暴露格式中 Exemplar 的时间戳是可选的,但这里的假设是在抓取时会为 Exemplar 分配一个时间戳,就像为抓取样本分配时间戳一样。接收端需要 Exemplar 的时间戳来可靠地处理(例如去重)传入的 Exemplar。

超出范围

1.0 版本相同。

未来计划

本节包含一些推测性计划,尚未被视为协议规范的一部分,但为了完整性在此提及。请注意,2.0 规范完成了 1.0 版本中 3 个未来计划中的 2 个

  • 事务性 2.0 规范仍然没有定义事务性,这主要是因为它使得可扩展的发送端实现变得困难。Prometheus 发送端的目标是“事务性”的——即永远不要向查询暴露一个部分抓取的目标。我们打算在 Remote-Write 中也这样做——例如,未来我们希望将 Remote-Write 与抓取“对齐”,也许这样一次抓取的所有样本、元数据和 Exemplar 都能在单个 Remote-Write 请求中发送。

    然而,Remote-Write 2.0 规范为经典直方图桶解决了一个重要的事务性问题。这是通过 io.prometheus.write.v2.Request 线上格式支持自定义分桶的原生直方图实现的。发送端可能会将所有经典直方图转换为原生直方图,但这超出了本规范的强制要求。然而,由于这个原因,接收端可以(MAY)忽略某些指标类型(例如经典直方图)。

  • 替代的线上格式。OpenTelemetry 社区已经通过其 OTLP 协议证明了 Apache Arrow(以及其他潜在的列式格式)在线上数据传输中的有效性。我们希望进行实验,以确认类似格式与 Prometheus 数据模型的兼容性,并包括任何资源使用变化的基准测试。我们可能会长期同时维护 protobuf 和列式格式以保证兼容性,并使用我们的内容协商为此目的添加不同的 Protobuf 消息。

  • 全局符号。预定义的字符串字典用于驻留。该协议可以预定义一个静态的 ref->symbol 字典,其中包含被认为是常见的字符串,例如“namespace”、“le”、“job”、“seconds”、“bytes”等。发送端可以引用这些字符串,而无需将它们包含在请求的符号表中。这个字典可以随着本协议的次要版本发布而逐步增长。

常见问题

为什么不使用 gRPC? 因为 1.0 协议不使用 gRPC,破坏这一点会增加采用的阻力。请参阅 1.0 版本的原因

为什么不使用流式 protobuf 消息? 如果您使用持久化的 HTTP/1.1 连接,它们已经非常接近流式传输了。当然,标头必须重新发送,但这比建立新的 TCP 连接的成本要低。

为什么我们按顺序发送样本? 顺序约束来自于我们在 Prometheus 中用于时间序列数据的编码方式,其实现针对仅追加的工作负载进行了优化。然而,许多其他数据库和生态系统中的供应商也共享此要求。事实上,启用了 OOO(乱序)功能的 Prometheus允许乱序写入,但会带来性能损失,因此仅用于罕见事件。总而言之,接收端可能支持乱序写入,尽管规范不允许这样做。在未来的版本中,例如 2.x 规范版本,如果需要,我们可以扩展内容类型来协商乱序写入。

在有顺序约束的情况下,我们如何并行化请求? 样本必须是按顺序的,但这是*针对给定序列*而言的。然而,即使接收端不支持乱序写入,只要 Remote-Write 请求是针对不同序列的,它们就可以并行发送。Prometheus 根据标签将样本分片到不同的队列中,然后在每个队列中顺序写入。这保证了同一序列的样本按顺序传递,但不同序列的样本是并行发送的——并且在不同序列之间可能是“乱序”的。

Remote-Write 2.0 和 OpenTelemetry 的 OTLP 协议有什么区别? OpenTelemetry OTLP 是一种用于在遥测数据源、中间节点和遥测后端之间传输遥测数据(如指标、日志、跟踪和剖析)的协议。推荐的传输方式是使用 protobuf 的 gRPC,但也描述了使用 protobuf 或 JSON 的 HTTP。它从头开始设计,旨在支持各种不同的可观测性信号、数据类型和额外信息。对于指标来说,这意味着额外的非标识性标签、标志、时间聚合类型、资源或作用域指标、schema URL 等。OTLP 还要求使用语义约定

Remote-Write 的设计追求简单、高效和有机增长。第一个版本于 2023 年正式发布,当时CNCF 生态系统中已经有数十个经过实战检验的采用者使用该协议多年。Remote-Write 2.0 在前一个协议的基础上进行了迭代,增加了一些新元素(元数据、Exemplar、创建时间戳和原生直方图)以及字符串驻留。Remote-Write 2.0 始终是无状态的,只关注指标,并且有自己的主张;因此,它被限定在 Prometheus 社区认为足以拥有强大指标解决方案的元素范围内。其意图是确保 Remote-Write 是一个稳定的协议,比可观测性生态系统中的替代方案更便宜、更简单地采用和使用。

本页内容