Prometheus Remote-Write 2.0 规范 [实验性]
- 版本: 2.0-rc.4
- 状态: 实验性
- 日期: 2024 年 5 月
Remote-Write 规范,总的来说,旨在记录 Prometheus 和兼容 Prometheus Remote-Write 的发送者将数据发送到 Prometheus 或兼容 Prometheus Remote-Write 的接收者的标准。
本文档旨在定义一个带有协议和语义轻微改动的 Prometheus Remote-Write API 的第二个版本。第二个版本增加了一个新的 Protobuf Message,该 Message 具有新功能,可以在性能和成本节省的基础上,实现更多用例和更广泛的应用。第二个版本还废弃了来自 1.0 Remote-Write 规范 的前一个 Protobuf Message,并为了可靠性目的增加了强制性的 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 Message 中对此提供了支持。
合规性测试可以在以下位置找到:
- 发送方:https://github.com/prometheus/compliance/tree/main/remotewrite/sender
- 接收方:https://github.com/prometheus/compliance/tree/main/remotewrite/receiver
术语表
本文档遵循以下定义
Remote-Write是此 Prometheus 协议的名称。协议是使客户端和服务器能够传输指标的通信规范。Protobuf Message(或 Proto Message)指的是此协议的数据结构的 内容类型 定义。由于规范仅使用 Google Protocol Buffers(“protobuf”) ,因此其 schema 定义在一个 “proto” 文件 中,并由单个 Protobuf “message” 表示。Wire Format是数据在网络上传输的格式。对于 Remote-Write,这始终是压缩的二进制 protobuf 格式。Sender是发送 Remote-Write 数据的东西。Receiver是接收(写入)Remote-Write 数据的东西。“Written”的含义由接收方决定,例如,通常意味着将接收到的数据存储在数据库中,但也可能只是对其进行验证、拆分或增强。Written指的是Receiver已收到并接受的数据。无论它是否已将这些数据摄取到持久存储、写入 WAL 等,都由Receiver决定。唯一的区别是Receiver已接受此数据,而不是通过错误响应明确拒绝。Sample是一个(开始时间戳、时间戳、值)的三元组。Histogram是一个(开始时间戳、时间戳、直方图值 )的三元组。Label是一个(键、值)对。Series是一个样本(或直方图)列表,由一组唯一的标签标识。
定义
协议
Remote-Write 协议必须由 RPC 组成,其请求体使用 Google Protocol Buffers 进行序列化,然后进行压缩。
protobuf 序列化必须使用以下任一 Protobuf Message:
- 在 Remote-Write 1.0 规范 中引入的
prometheus.WriteRequest。截至 2.0 版本,此 Message 已被弃用。为保持兼容性,它仅应被使用。发送方和接收方可能不支持prometheus.WriteRequest。 - 在此规范中引入并在 下面 定义的
io.prometheus.write.v2.Request。发送方和接收方应尽可能使用此 Message。发送方和接收方必须支持io.prometheus.write.v2.Request。
Protobuf Message 必须使用二进制 Wire Format。然后,必须使用 Google 的 Snappy 进行压缩。必须使用 Snappy 的 块格式 ——不得使用 帧格式 。
发送方必须将序列化并压缩的 Protobuf Message 发送到 HTTP POST 请求的请求体中,并通过 HTTP 发送到接收方的提供的 URL 路径。接收方可以指定任何 HTTP URL 路径来接收指标。
发送方必须发送以下保留的 HTTP 请求头:
Content-EncodingContent-TypeX-Prometheus-Remote-Write-VersionUser-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 Message 的完全限定名称,从上述两个中选择。因此,发送方必须发送以下三种支持的头值之一:
对于 PRW 1.0 中引入的、由 prometheus.WriteRequest 标识的已弃用 Message
Content-Type: application/x-protobufContent-Type: application/x-protobuf;proto=prometheus.WriteRequest
对于 PRW 2.0 中引入的、由 io.prometheus.write.v2.Request 标识的 Message
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 Message。
接收方必须使用内容类型头来识别要使用的 Protobuf Message schema。意外的错误 schema 选择可能导致非确定性行为(例如,数据损坏)。
注意得益于io.prometheus.write.v2.Request中的保留字段,接收方意外使用错误的 schema(如prometheus.WriteRequest)将导致 Message 为空。这通常是为了方便以避免意外错误,但不要依赖它——未来的 Protobuf Message 可能没有此功能。
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 No Content ;发送方必须忽略响应体。响应体保留供将来使用。
如果发送数据的任何部分(例如 Sample、Histogram、Exemplar)未能成功写入(包括 部分写入 或完全拒绝写入),接收方不得返回 2xx HTTP 状态码。在这种情况下,接收方必须在响应体中提供人类可读的错误消息。接收方的错误应包含有关被拒绝样本数量以及拒绝原因的信息。发送方不得尝试解释错误消息,应按原样记录。
以下子章节规定了发送方和接收方在处理头和不同写入错误情况下的语义。
必需的 Written 响应头
在成功的内容协商后,接收方会处理(写入)接收到的数据批次。一旦完成(成功或失败),对于每个重要的数据部分(目前是 Samples、Histograms 和 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 响应头表示该类别(例如 Sample)的元素未被接收方写入(计数为 0)。发送方不得假设同样的情况,当使用已弃用的 prometheus.WriteRequest Protobuf Message 时,因为有击中不支持此功能的 1.0 接收方的风险。
发送方可以使用这些头来确认接收方成功写入了哪些数据部分。常见用例:
- 更好地处理 部分写入 失败情况:发送方可以使用这些头进行更准确的客户端仪表化和错误处理。
- 检测损坏的 1.0 接收方实现:发送方应假设在发送使用
io.prometheus.write.v2.Request请求的数据并收到 2xx HTTP 状态码,但未收到接收方的任何X-Prometheus-Remote-Write-*-Written响应头时,会收到 415 HTTP Unsupported Media Type 状态码。这是 1.0 接收方的常见问题,它们不检查Content-Type请求头;使用prometheus.WriteRequestschema 意外解码io.prometheus.write.v2.Requestpayload 会导致结果为空且无解码错误。 - 检测其他损坏的实现或问题:发送方可以使用这些头来检测损坏的发送方和接收方实现或其他问题。
发送方不得从远程写入响应头推断接收方实现了哪个 Remote Write 规范版本。
未来可能会引入更多(可选)头,例如,当有更多实体或字段被添加并且值得确认时。
部分写入
发送方应使用 Remote-Write 在单个请求中发送多个系列的样本。因此,接收方可以在包含一些无效或未写入样本的写入请求中写入有效样本,这代表部分写入情况。在这种情况下,接收方必须返回非 2xx 状态码,遵循 无效样本 和 部分写入重试 部分。
不支持的请求内容
如果接收方不支持发送方提供的给定内容类型或编码,接收方必须返回 415 HTTP Unsupported Media Type 状态码。
为了向后兼容,发送方应期望从 1.x 接收方收到 400 HTTP Bad Request 来表示上述原因。
无效样本
接收方可能不支持某些指标类型或样本(例如,一个接收方可能拒绝没有指定元数据类型或没有开始时间戳的样本,而另一个接收方可能接受此类样本)。样本的有效性由接收方决定。接收方必须为包含任何无效样本的写入请求返回 400 HTTP Bad Request 状态码,除非发生 部分可重试写入。
发送方不得重试 4xx HTTP 状态码(除了 429 ),接收方必须使用此状态码指示写入操作永远无法成功,不应重试。发送方可以尝试使用不同的内容类型或编码重试 415 HTTP 状态码,以查看接收方是否支持。
重试和退避
接收器可以返回一个 429 HTTP 请求过多 状态码来指示服务器过载的情况。接收器可以返回 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 引用了旨在替换和弃用 Remote-Write 1.0 的 prometheus.WriteRequest 消息的新 Protobuf 消息。
完整的模式和真相来源位于 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 epoch 以来计数。样本的值必须是 float64。
对于每个 TimeSeries 消息
- 必须提供
labels_refs。
- 必须在
samples或histograms中提供至少一个元素。TimeSeries不能同时包含samples和histograms。对于(很少)混合了浮点数和直方图样本的系列,必须使用单独的TimeSeries消息。
metadata子字段应提供。接收方可以拒绝具有未指定Metadata.type的系列。- 如果存在系列的样本,则应提供
Exemplars。
以下子节详细定义了一些模式元素。
符号
io.prometheus.write.v2.Request Protobuf 消息旨在 对所有字符串进行内部化 ,以在标准压缩之上获得额外的压缩和内存效率增益。
必须提供 symbols 表,其中必须包含系列、样本标签和元数据字符串中使用的去重字符串。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 值必须被接收方视为未设置值。为了表示不太可能的 0 Unix 时间戳(毫秒),必须使用“1”或“-1”的值。
发送者在时间序列将不再追加时,应发送 stale 标记。发送者必须发送 stale 标记,如果时间序列的终止是可检测的,例如
- 对于被拉取(抓取)的系列,除非使用了显式时间戳。
- 对于记录规则评估产生的一个系列。
通常,不为已终止的时间序列发送 stale 标记可能导致接收方 不必要的查询时间对齐问题。
Stale 标记必须通过特殊 NaN 值 0x7ff0000000000002 来信号化。此值不得用于其他任何目的。
通常,发送者可以使用以下技术检测时间序列何时不再被追加
- 通过服务发现检测到暴露该系列的目标已消失。
- 在连续抓取之间注意到目标不再暴露该时间系列。
- 抓取失败,抓取了最初暴露该系列的目标。
- 跟踪记录和警报规则的配置和评估。
- 跟踪非抓取指标源(例如,在 k6 中,当基准测试完成时,它可以为每个基准测试系列发出 stale 标记)的指标终止。
元数据
元数据应遵循 Prometheus 关于 类型 和 帮助 的官方指南。
元数据可以遵循 OpenMetrics 关于 单位 的官方 OpenMetrics 指南。
Exemplars(范例)
每个附加到 TimeSeries 的样本(exemplar)
- 必须包含一个值。
- 可以包含标签,例如引用跟踪或请求 ID。如果样本引用了一个跟踪,作为最佳实践,它应该使用
trace_id标签名称。 - 必须包含一个时间戳。虽然 Prometheus/Open Metrics 导出格式中的样本时间戳是可选的,但假设时间戳在抓取时分配,就像时间戳分配给抓取样本一样。接收方需要样本时间戳来可靠地处理(例如,去重)传入的样本。
范围外
与 1.0 相同。
未来计划
本节包含推测性计划,这些计划尚未被视为协议规范的一部分,但在此提及以求完整。请注意,2.0 规范完成了 1.0 中 3 个未来计划的第 2 个。
-
事务性 2.0 规范仍未定义事务性,主要是因为它使得可扩展的发送者实现变得困难。Prometheus 发送者旨在实现“事务性”,即从不向查询暴露部分抓取的目标。我们打算对 Remote-Write 也这样做——例如,将来我们希望将 Remote-Write 与抓取“对齐”,也许这样,单个抓取的所有样本、元数据和样本(exemplars)都在单个 Remote-Write 请求中发送。
然而,Remote-Write 2.0 规范通过原生的直方图支持自定义分桶(通过
io.prometheus.write.v2.Request线格式实现),解决了 经典直方图分桶 的一个重要事务性问题。发送者可能会以这种方式将所有经典直方图转换为原生直方图,但这超出了本规范的范围,不强制要求这样做。然而,因此,接收者可以忽略某些指标类型(例如,经典直方图)。 -
备选线格式。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 规范版本,我们可以扩展内容类型来协商乱序写入(如果需要)。
如何在顺序约束下并行化请求?样本必须是*给定系列*的顺序。然而,即使接收方不支持乱序写入,只要请求是针对不同系列的,它们就可以并行发送。Prometheus 将样本按其标签分片到不同的队列中,然后在每个队列中顺序写入。这保证了同一系列的样本按顺序传递,但不同系列的样本是并行发送的——并且可能在不同系列之间“乱序”。
Remote-Write 2.0 与 OpenTelemetry 的 OTLP 协议有什么区别? OpenTelemetry OTLP 是一个用于在遥测源、中间节点和遥测后端之间传输遥测数据(如指标、日志、跟踪和配置文件)的协议。推荐的传输方式是 gRPC 和 Protobuf,但也描述了 Protobuf 或 JSON 的 HTTP 传输。它从头开始设计,旨在支持各种不同的可观察性信号、数据类型和额外信息。对于 指标 ,这意味着额外的非标识标签、标志、时间聚合类型、资源或范围度量、模式 URL 等。OTLP 还要求使用 语义约定 。
Remote-Write 的设计目标是简单、高效和自然增长。第一个版本于 2023 年正式发布,当时 CNCF 生态系统中已有数十个经过实战检验的采用者 已经使用该协议多年。Remote-Write 2.0 基于之前的协议进行迭代,增加了几个新元素(元数据、样本、开始时间戳和原生直方图)以及字符串内部化。Remote-Write 2.0 始终是无状态的,仅专注于指标,并且有明确的观点;因此,它被限制在 Prometheus 社区认为足以构建健壮指标解决方案的元素范围内。其目的是确保 Remote-Write 协议稳定,并且比可观察性生态系统中的替代方案更便宜、更易于采用和使用。