Prometheus Remote-Write 2.0 规范 [实验性]
- 版本: 2.0-rc.4
- 状态: 实验性 (Experimental)
- 日期: 2024 年 5 月
Remote-Write 规范旨在记录 Prometheus 以及兼容 Prometheus Remote-Write 的发送方如何将数据发送给 Prometheus 或兼容的接收方。
本文档旨在定义 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 等“花哨”的技术,但当时它们并未被广泛采用,且将 gRPC 服务暴露在 AWS EC2 ELB 等负载均衡器后面的公网上具有一定挑战。
Remote-Write 协议包含批处理的机会,例如在单个请求中为不同的序列发送多个样本。虽然 Protobuf 消息支持在同一请求中为同一个序列发送多个样本,但通常不建议这样做。
合规性测试可在以下地址找到:
- 发送方 (Sender): https://github.com/prometheus/compliance/tree/main/remotewrite/sender
- 接收方 (Receiver): https://github.com/prometheus/compliance/tree/main/remotewrite/receiver
术语表
本文档遵循以下定义:
Remote-Write是该 Prometheus 协议的名称。协议 (Protocol)是指使客户端和服务器能够传输指标的通信规范。Protobuf 消息 (Protobuf Message)(或 Proto 消息) 指的是该协议数据结构的 内容类型 (Content-Type) 定义。由于该规范完全使用 Google Protocol Buffers ("protobuf") ,因此模式定义在 "proto" 文件 中,并由单个 Protobuf "message" 表示。线传输格式 (Wire Format)是数据在网络上传输时的格式。在 Remote-Write 中,这始终是压缩后的二进制 protobuf 格式。发送方 (Sender)指的是发送 Remote-Write 数据的一方。接收方 (Receiver)指的是接收(写入)Remote-Write 数据的一方。已写入 (Written)的确切含义由接收方决定,例如,通常意味着将接收到的数据存储在数据库中,但也可能仅仅是进行验证、拆分或增强。已写入 (Written)指的是接收方已接收并接受的数据。无论是否已将其存入持久化存储、写入到预写日志 (WAL) 等,都由接收方自行决定。唯一的区别是,接收方已接受此数据,而不是明确地用错误响应将其拒绝。样本 (Sample)是一个三元组 (开始时间戳, 时间戳, 值)。直方图 (Histogram)是一个三元组 (开始时间戳, 时间戳, 直方图值 )。标签 (Label)是一个键值对 (key, value)。序列 (Series)是由唯一的一组标签标识的样本(或直方图)列表。
定义
协议
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 消息必须使用二进制线传输格式 (Wire Format)。然后,必须使用 Google Snappy 进行压缩。必须使用 Snappy 的 块格式 (block format) —— 不得使用 帧格式 (framed format) 。
发送方必须在 HTTP POST 请求的主体中发送序列化并压缩后的 Protobuf 消息,并通过 HTTP 将其发送到接收方的指定 URL 路径。接收方可以指定任何 HTTP URL 路径来接收指标。
发送方必须在 HTTP 请求中发送以下预留标头:
Content-EncodingContent-TypeX-Prometheus-Remote-Write-VersionUser-Agent
发送方可以允许用户添加自定义 HTTP 标头;但不得允许用户以发送预留标头的方式进行配置。
Content-Encoding
Content-Encoding: <compression>
内容编码请求标头必须遵循 RFC 9110 。发送方必须使用 snappy 值。接收方必须支持 snappy 压缩。新的、可选的压缩算法可能会在 2.x 或更高版本中出现。
Content-Type
Content-Type: application/x-protobuf
Content-Type: application/x-protobuf;proto=<fully qualified name>
内容类型请求标头必须遵循 RFC 9110 。发送方必须仅使用 application/x-protobuf 作为媒体类型。发送方可以在标头值中添加 ;proto= 参数,以指示所使用的 Protobuf 消息的完全限定名称(从上述两个中选择)。因此,发送方必须发送以下三个支持的标头值中的任意一个:
对于在 PRW 1.0 中引入并由 prometheus.WriteRequest 标识的已弃用消息:
Content-Type: application/x-protobufContent-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 消息。
接收方必须使用内容类型标头来确定要使用的 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>
发送方必须包含一个用户代理标头,该标头应遵循 RFC 9110 User-Agent 标头格式 。
响应
成功写入所有数据的接收方必须返回一个 成功的 2xx HTTP 状态码 。在此种成功情况下,接收方的响应体应为空,状态码应为 204 HTTP No Content ;发送方必须忽略响应体。响应体保留供将来使用。
如果发送的数据(如样本、直方图、样本点 (Exemplars))中有任何部分未能成功写入(无论是 部分写入 (partial write) 还是完全拒绝写入),接收方不得返回 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 消息时,发送方不得做出此假设。
发送方可以使用这些标头来确认哪些数据部分已被接收方成功写入。常见用例:
- 更好地处理 部分写入 (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有效载荷会导致空结果且没有解码错误。 - 检测其他损坏的实现或问题:发送方可以使用这些标头来检测损坏的发送方和接收方实现或其他问题。
发送方不得根据远程写入响应标头来推断接收方实现了哪个 Remote Write 规范版本。
将来可能会有更多(可选的)标头出现,例如当添加更多值得确认的实体或字段时。
部分写入 (Partial Write)
发送方应使用 Remote-Write 在单个请求中发送多个序列的样本。因此,接收方可以在写入请求中写入有效样本,该请求同时也包含一些无效或未写入的样本,这代表了部分写入的情况。在此类情况下,接收方必须按照 无效样本 (Invalid Samples) 和 部分写入重试 (Retry on Partial Writes) 部分的要求返回非 2xx 状态码。
不受支持的请求内容
如果接收方不支持发送方提供的特定内容类型或编码,则必须返回 415 HTTP 不支持的媒体类型 状态码。
出于向后兼容性考虑,发送方应预计 1.x 版本的接收方会因上述原因返回 400 HTTP 错误请求 。
无效样本 (Invalid Samples)
接收方可能不支持某些指标类型或样本(例如,接收方可能拒绝没有指定元数据类型或没有开始时间戳的样本,而另一个接收方可能接受)。何为无效样本由接收方决定。除非发生 部分可重试写入 (partial retriable write),否则接收方必须对包含任何无效样本的写入请求返回 400 HTTP 错误请求 状态码。
发送方不得对 4xx HTTP 状态码(除了 429 )进行重试,接收方必须使用这些状态码来表示写入操作永远无法成功,不应重试。发送方可以使用不同的内容类型或编码重试 415 HTTP 状态码,以查看接收方是否支持。
重试与回退 (Retries & Backoff)
接收方可以返回 429 HTTP Too Many Requests 状态码以指示服务器负载过高的情况。接收方可以返回 Retry-After 响应头来指示下一次写入尝试的时间。接收方可以返回 5xx HTTP 状态码来表示服务器内部错误。
发送方可以在收到 429 HTTP 状态码时进行重试。发送方必须在收到 5xx HTTP 状态码时重试写入请求。发送方必须使用退避(backoff)算法以防止使服务器过载。发送方可以使用 Retry-After 响应头 来估计下一次重试的时间。
处理 429 和 5xx 的区别在于,前者可能是由于发送方“处理滞后”,即接收方无法跟上请求量,或者是接收方为了保护其可用性而选择对发送方进行速率限制。因此,发送方可以选择不在 429 错误时重试,这样当发生发送方侧错误(例如流量过大)时可以继续推进;而当发生接收方侧错误(5xx)时,通过重试可以确保数据不会丢失。
部分写入的重试
当接收方期望发送方重试整个请求时,接收方可能会针对部分写入或部分无效样本情况返回 5xx 或 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 协议打破了与 1.x 的兼容性,引入了强制性的 io.prometheus.write.v2.Request Protobuf 消息,并弃用了 prometheus.WriteRequest。
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。
- 必须在
samples或histograms中至少提供一个元素。TimeSeries不得同时包含samples和histograms。对于极少数需要混合 float 和直方图样本的序列,必须使用单独的TimeSeries消息。
- 应该提供
metadata子字段。接收方可以拒绝未指定Metadata.type的序列。 - 如果存在 Exemplar,则应该为序列提供它们。
以下小节详细定义了一些模式元素。
符号
io.prometheus.write.v2.Request Protobuf 消息旨在 驻留所有字符串 ,以便在标准压缩基础上获得经过验证的额外压缩和内存效率提升。
必须提供 symbols 表,且必须包含序列、Exemplar 标签和元数据字符串中使用的已去重字符串。symbols 表的第一个元素必须为空字符串,用于表示空值或未指定值,例如未提供 Metadata.unit_ref 或 Metadata.help_ref 时。引用必须指向 symbols 字符串数组中的现有索引。
序列标签
完整的标签集必须随每个 Sample 或 Histogram 样本一起发送。此外,与样本关联的标签集
- 应该包含一个
__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 来发出信号。此值不得用于其他用途。
通常,发送方可以使用以下技术检测时间序列何时不再被追加:
- 使用服务发现检测到公开该序列的目标已下线。
- 注意到目标在连续抓取之间不再公开该时间序列。
- 无法抓取最初公开时间序列的目标。
- 跟踪记录规则和警报规则的配置和评估。
- 对于非抓取来源的指标,跟踪指标的停止(例如,在 k6 中,当每个基准测试的序列完成时,它可以发出陈旧标记)。
元数据
元数据应该遵循 Prometheus 关于 类型 (Type) 和 帮助字符串 (Help) 的官方指南。
元数据可以遵循 OpenMetrics 关于 单位 (Unit) 的官方指南。
Exemplars(范例)
每个附加到 TimeSeries 的 Exemplar:
- 必须包含一个值。
- 可以包含标签(例如引用 trace 或 request ID)。作为最佳实践,如果 Exemplar 引用 trace,应该使用
trace_id标签名称。 - 必须包含时间戳。虽然 Prometheus/Open Metrics 展示格式中 Exemplar 时间戳是可选的,但假设时间戳是在抓取时分配的,方式与分配给抓取样本的时间戳相同。接收方需要 Exemplar 时间戳来可靠地处理(例如去重)传入的 Exemplar。
范围之外
与 1.0 相同。
未来计划
本节包含投机性计划,它们尚未被视为协议规范的一部分,但在此提及以求完整。请注意,2.0 规范完成了 1.0 中 3 个未来计划中的 2 个。
-
事务性 (Transactionality) 2.0 规范中仍未定义事务性,主要是因为它使可扩展的发送方实现变得困难。Prometheus 发送方旨在实现“事务性”——即永远不会向查询暴露部分抓取的目标。我们打算在 Remote-Write 中实现同样的目标——例如,未来我们希望将 Remote-Write 与抓取“对齐”,例如,单次抓取的所有样本、元数据和 Exemplar 都在单个 Remote-Write 请求中发送。
然而,Remote-Write 2.0 规范解决了一个重要的 经典直方图桶 的事务性问题。这要归功于
io.prometheus.write.v2.Request线上传输格式支持的原生直方图自定义桶功能。发送方可以通过这种方式将所有经典直方图转换为原生直方图,但这不在本规范的强制要求范围内。然而,基于这个原因,接收方可能会忽略某些指标类型(例如经典直方图)。 -
替代线上传输格式。OpenTelemetry 社区已经通过 OTLP 协议证明了 Apache Arrow(以及潜在的其他列式格式)用于数据在线传输的有效性。我们希望进行实验以确认类似格式与 Prometheus 数据模型的兼容性,并包含任何资源使用变化的基准测试。出于兼容性原因,我们可能会长期同时维护 protobuf 和列式格式,并使用内容协商来为此目的添加不同的 Protobuf 消息。
-
全局符号。用于驻留的预定义字符串字典。该协议可以预定义一个包含被视为通用的字符串(例如 “namespace”、“le”、“job”、“seconds”、“bytes” 等)的 ref->symbol 静态字典。发送方无需将它们包含在请求的符号表中即可引用它们。该字典可以随着此协议的次版本发布而逐步增长。
相关
常见问题
为什么不使用 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 是一种用于在遥测源、中间节点和遥测后端之间传输遥测数据(如指标、日志、trace 和 profiles)的协议。推荐的传输方式涉及使用 protobuf 的 gRPC,但也描述了使用 protobuf 或 JSON 的 HTTP。它的设计初衷就是为了支持各种不同的可观测性信号、数据类型和额外信息。对于 指标 而言,这意味着额外的非标识标签、标志、时间聚合类型、资源或作用域指标、模式 URL 等。OTLP 还要求使用 语义约定 。
Remote-Write 的设计初衷是简单、高效和有机增长。第一个版本于 2023 年正式发布,当时 CNCF 生态系统中已有数十个经过实战验证的采用者 多年来一直使用该协议。Remote-Write 2.0 在之前的协议基础上进行了迭代,增加了少量新元素(元数据、Exemplar、开始时间戳和原生直方图)以及字符串驻留。Remote-Write 2.0 始终是无状态的,仅关注指标,且具有一定的观点;因此,它仅限于 Prometheus 社区认为足以实现稳健指标解决方案的元素。其目的是确保 Remote-Write 成为一个稳定的协议,比可观测性生态系统中的替代方案更便宜、更简单且易于采用。