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

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

远程写入 (Remote-Write) 规范总体旨在记录 Prometheus 以及兼容 Prometheus 远程写入的发送者如何将数据发送到 Prometheus 或兼容 Prometheus 远程写入的接收者的标准。

本文档旨在定义 Prometheus Remote-Write API 的第二个版本,对协议和语义进行了微调。此版本增加了一条新的 Protobuf 消息,不仅在性能和成本节约方面有所提升,还启用了更多应用场景并促进了更广泛的采用。第二个版本还弃用了 1.0 远程写入规范 中的原有 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  中。

简介

背景

远程写入协议旨在实现从发送者到接收者实时、可靠且无丢失地传播样本。

远程写入协议设计为无状态;消息之间不存在严格的交互通信。因此,该协议不被视为“流式”。为了达到流式传输的效果,应使用例如 HTTP/1.1 或 HTTP/2 在同一连接上发送多条消息。虽然曾考虑过如 gRPC 等“花哨”的技术,但在当时它们并未被广泛采用,且在 AWS EC2 ELB 等负载均衡器后端向互联网暴露 gRPC 服务存在挑战。

远程写入协议包含了批处理的机会,例如在单个请求中为不同的序列发送多个样本。虽然 Protobuf 消息对此提供了支持,但并不期望在同一请求中频繁发送同一序列的多个样本。

合规性测试可参考以下地址:

术语表

本文档遵循以下定义:

  • Remote-Write 是此 Prometheus 协议的名称。
  • 协议 (Protocol) 是使客户端和服务器能够传输指标的通信规范。
  • Protobuf 消息 (Protobuf Message) (或 Proto 消息) 指该协议数据结构的 内容类型 (content type)  定义。由于该规范完全使用 Google Protocol Buffers ("protobuf") ,其模式定义在 "proto" 文件  中,并由单个 Protobuf "message"  表示。
  • 线路格式 (Wire Format) 是数据在网络上传输时的格式。在远程写入的情况下,这始终是压缩的二进制 protobuf 格式。
  • 发送者 (Sender) 指发送远程写入数据的组件。
  • 接收者 (Receiver) 指接收(写入)远程写入数据的组件。已写入 (Written) 的含义取决于接收者,例如通常意味着将接收到的数据存储在数据库中,但也可能仅指验证、拆分或增强数据。
  • 已写入 (Written) 指接收者已收到并接受的数据。是否将其摄入到持久存储、写入预写日志 (WAL) 等由接收者决定。唯一的区别是接收者已经接受了此数据,而不是明确通过错误响应拒绝了它。
  • 样本 (Sample) 是一个三元组 (开始时间戳, 时间戳, 值)。
  • 直方图 (Histogram) 是一个三元组 (开始时间戳, 时间戳, 直方图值 )。
  • 标签 (Label) 是一个键值对 (key, value)。
  • 序列 (Series) 是样本(或直方图)的列表,由一组唯一的标签标识。

定义

协议

远程写入协议必须由 RPC 组成,其请求体使用 Google Protocol Buffers 序列化并进行压缩。

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

  • 远程写入 1.0 规范中引入的 prometheus.WriteRequest。截至 2.0 版本,此消息已被弃用。它仅应出于兼容性原因使用。发送者和接收者可能不支持 prometheus.WriteRequest
  • 本规范引入并定义在下方io.prometheus.write.v2.Request。发送者和接收者应该在可能的情况下使用此消息。发送者和接收者必须支持 io.prometheus.write.v2.Request

Protobuf 消息必须使用二进制线路格式,并使用 Google 的 Snappy  进行压缩。必须使用 Snappy 的 块格式 (block format)  ——不得使用 帧格式 (framed format) 

发送者必须将序列化并压缩后的 Protobuf 消息放入 HTTP POST 请求的 body 中,并通过 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。在 2.x 或更高版本中可能会引入更多的 Protobuf 消息。

接收者必须使用 Content-Type 请求头来识别要使用的 Protobuf 消息模式。意外选择错误的模式可能会导致不确定性行为(例如数据损坏)。

注意得益于 io.prometheus.write.v2.Request 中的保留字段,如果接收者错误地将 prometheus.WriteRequest 模式与错误的 schema 配合使用,将导致空消息。这主要是为了方便起见以避免出现惊人的错误,但请勿依赖此特性——未来的 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。否则,发送者应该使用其兼容的最新远程写入版本,例如 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 响应头

在内容协商成功后,接收者处理(写入)收到的数据批次。对于每个重要的数据片段(目前为样本、直方图和 Exemplars),一旦处理完成(无论成功与否),接收者必须发送一个专用的 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 消息时,不得做出此假设。

发送者可以使用这些请求头来确认接收者成功写入了哪些部分的数据。常见的用例包括:

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

发送者不得通过远程写入响应头推测接收者实现了哪个远程写入规范版本。

未来可能会引入更多(可选的)请求头,例如在添加了更多实体或字段且值得确认时。

部分写入 (Partial Write)

发送者应该使用远程写入在单个请求中发送多个序列的样本。因此,接收者可能在包含一些无效或未写入样本的写入请求中写入了有效的样本,这代表了一种部分写入的情况。在这种情况下,接收者必须根据无效样本部分重试写入部分返回非 2xx 状态码。

不支持的请求内容

如果接收者不支持发送者提供的内容类型或编码,则必须返回 415 HTTP 不支持的媒体类型 (Unsupported Media Type)  状态码。

为了向后兼容,发送者应该预期从 1.x 接收者因上述原因返回 400 HTTP 错误请求 (Bad Request) 

无效样本

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

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

重试与退避

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

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

429 与 5xx 处理方式的区别源于以下潜在情况:当接收方无法跟上请求量时,发送方可能会“落后”;或者接收方选择对发送方进行速率限制以保护其可用性。因此,发送方可以选择不在 429 时重试,这允许在发生发送方错误(例如流量过大)时继续进行,而当发生接收方错误(5xx)时,数据不会丢失。

部分写入的重试

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

向后和向前兼容性

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

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

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

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

2.x 与 1.x 兼容性

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

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

Protobuf 消息

io.prometheus.write.v2.Request

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

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

下面提供了新的 io.prometheus.write.v2.Request 的简化版本。

message Request {
  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 {
  reserved 6;
    
  // 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 start 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;
}

// Exemplar is an additional information attached to some series' samples.
// It is typically used to attach an example trace or request ID associated with
// the metric changes.
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.
  //
  // For Go, see github.com/prometheus/prometheus/model/timestamp/timestamp.go
  // for conversion from/to time.Time to Prometheus timestamp.
  int64 timestamp = 2;
  // start_timestamp represents an optional start timestamp for the sample,
  // in ms format. This information is typically used for counter, histogram (cumulative)
  // or delta type metrics.
  //
  // For cumulative metrics, the start timestamp represents the time when the
  // counter started counting (sometimes referred to as created timestamp), which
  // can increase the accuracy of certain processing and query semantics (e.g. rates).
  //
  // Note:
  // * That some receivers might require start timestamps for certain metric
  // types; rejecting such samples within the Request as a result.
  // * start timestamp is the same as "created timestamp" name Prometheus used in the past.
  //
  // 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 efficiency and consistency.
  // Zero value means value not set. If you need to use exactly zero value for
  // the timestamp, use 1 millisecond before or after.
  int64 start_timestamp = 3;
}

// 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 message, supporting
// * sparse exponential bucketing, custom bucketing.
// * float or integer histograms.
//
// See the full spec: https://prometheus.ac.cn/docs/specs/native_histograms/
message Histogram { ... }

所有时间戳必须为 int64 类型,计为自 Unix 纪元以来的毫秒数。样本的值必须为 float64 类型。

对于每个 TimeSeries 消息

  • 必须提供 labels_refs
  • 必须提供 sampleshistograms 中的至少一个元素。TimeSeries 不得同时包含 sampleshistograms。对于(极少数情况下)混合浮点样本和直方图样本的序列,必须使用单独的 TimeSeries 消息。
  • 建议提供 metadata 子字段。接收方可以拒绝未指定 Metadata.type 的序列。
  • 如果序列存在 Exemplar,建议提供。

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

符号

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

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

序列标签

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

  • 建议包含一个 __name__ 标签。
  • 不得包含重复的标签名称。
  • 标签名称必须按字典顺序排序。
  • 不得包含任何空的标签名称或值。

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

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

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

不遵循上述规则的名称可能对 PromQL 用户更难使用(有关详细信息,请参阅 UTF-8 提案 )。

以 "__" 开头的标签名称是为系统使用而保留的,建议不要使用,请参阅 Prometheus 数据模型

接收方也可能对标签的数量和长度施加限制,但这取决于具体接收方,且超出了本文档的范围。

样本和直方图样本

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

对于遵循计数器语义的类型(例如计数器和计数器直方图),建议提供样本或直方图的 start_timestamp。接收方可以拒绝未设置 start_timestamp 的序列。考虑到可选性,接收方必须将 0 值视为未设置值。要表示毫秒级 Unix 时间戳为 0 的罕见情况,必须使用 "1" 或 "-1" 值。

当时间序列不再追加数据时,发送方建议发送陈旧标记(stale markers)。如果能够检测到时间序列的停止,发送方必须发送陈旧标记,例如:

  • 对于已拉取(抓取)的序列,除非使用了明确的时间戳。
  • 对于通过记录规则评估产生的序列。

通常,对于已停止的序列不发送陈旧标记可能导致接收方出现 非平凡的查询时间对齐问题

陈旧标记必须通过特殊的 NaN 值 0x7ff0000000000002 来标记。该值不得用于其他用途。

通常,发送方可以使用以下技术检测时间序列何时不再追加:

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

元数据

元数据建议遵循 Prometheus 关于 类型 (Type)帮助字符串 (Help) 的官方指南。

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

Exemplars(范例)

每个 Exemplar(如果附加到 TimeSeries

  • 必须包含一个值。
  • 可以包含标签,例如引用跟踪 ID 或请求 ID。作为最佳实践,如果 Exemplar 引用了一个跟踪,建议使用 trace_id 标签名称。
  • 必须包含时间戳。虽然 Exemplar 时间戳在 Prometheus/Open Metrics 展示格式中是可选的,但假设时间戳是在抓取时以与分配给抓取样本的时间戳相同的方式分配的。接收方需要 Exemplar 时间戳来可靠地处理(例如去重)传入的 Exemplar。

范围之外

1.0 版本相同。

未来计划

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

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

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

  • 替代线路格式。OpenTelemetry 社区已经展示了 Apache Arrow(以及其他可能的列式格式)对于其 OTLP 协议的在线数据传输的有效性。我们希望进行实验,以确认类似格式与 Prometheus 数据模型的兼容性,并包含任何资源使用变化的基准测试。出于兼容性原因,我们可能长期维护 protobuf 和列式两种格式,并使用内容协商为此目的添加不同的 Protobuf 消息。

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

常见问题

为什么不使用 gRPC? 因为 1.0 协议不使用 gRPC,破坏它会增加采用的摩擦。请参阅 1.0 原因

为什么不对 protobuf 消息进行流式处理? 如果使用持久的 HTTP/1.1 连接,它们非常接近流式处理。当然,必须重新发送头部,但这比重新建立 TCP 连接的代价要小。

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

如何在满足有序约束的同时并行化请求? 对于给定的序列,样本必须是有序的。然而,即使接收方不支持乱序写入,Remote-Write 请求也可以在它们属于不同序列时并行发送。Prometheus 将样本按其标签分片到单独的队列中,然后在每个队列中顺序执行写入。这保证了同一序列的样本按顺序发送,但不同序列的样本是并行发送的——并且在不同序列之间可能是“乱序”的。

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

Remote-Write 的设计初衷是简单、高效和有机增长。第一个版本于 2023 年正式发布,当时 CNCF 生态系统中已经有 数十个久经考验的采用者 多年来一直在使用该协议。Remote-Write 2.0 在之前的协议基础上进行了迭代,增加了几个新元素(元数据、Exemplar、起始时间戳和原生直方图)以及字符串驻留。Remote-Write 2.0 始终是无状态的,仅专注于指标,并且带有主观倾向;因此,它被缩小到 Prometheus 社区认为足以拥有健壮指标解决方案的要素。其目的是确保 Remote-Write 成为一个稳定的协议,比可观测性生态系统中的替代方案更廉价、更容易采用和使用。

本页内容