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 消息,带来了新功能,以支持更多的使用场景和更广泛的采用,同时还提高了性能并节省了成本。第二版还弃用了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 消息中对此提供了支持。
合规性测试可在以下位置找到:
- 发送方:https://github.com/prometheus/compliance/tree/main/remotewrite/sender
- 接收方:https://github.com/prometheus/compliance/tree/main/remotewrite/receiver
术语表
在本文档中,遵循以下定义:
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-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 消息的完全限定名称,即上述两种之一。因此,发送方必须发送以下三种支持的头值之一:
对于 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。更多的 Protobuf 消息可能会在 2.x 或更高版本中出现。
接收方必须使用内容类型头来识别要使用的 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)未成功写入(无论是部分写入还是完全写入拒绝),接收方不得返回 2xx HTTP 状态码。在这种情况下,接收方必须在响应体中提供人类可读的错误消息。接收方的错误应包含有关被拒绝样本数量及其原因的信息。发送方不得尝试解释错误消息,并应按原样记录它。
以下小节指定了发送方和接收方围绕头和不同写入错误情况的语义。
必要的已写入响应头
在内容协商成功后,接收方处理(写入)接收到的数据批次。一旦每个重要数据块(目前是样本、直方图和 Exemplar)完成处理(成功或失败),接收方必须发送一个专用的 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 状态码,以查看接收方是否支持。
重试与退避
接收方可以返回 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 版本的发送端(Sender)可以支持 1.x 版本的接收端(Receiver),方法是允许用户配置发送端应使用的内容类型。如果接收端返回 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。对于(罕见地)会混合浮点型样本和直方图样本的序列,必须使用一个单独的TimeSeries消息。
- 建议提供
metadata的子字段。接收端可能会拒绝未指定Metadata.type的序列。 - 如果一个序列存在范例(exemplar),建议提供它们。
以下小节将详细定义一些数据结构元素。
符号表(Symbols)
io.prometheus.write.v2.Request Protobuf 消息被设计为对所有字符串进行字符串池化(interning) ,以在标准压缩之上获得已被证明的额外压缩和内存效率提升。
必须提供 symbols 表,并且它必须包含序列、范例标签和元数据字符串中使用的去重后的字符串。symbols 表的第一个元素必须是空字符串,用于表示空值或未指定的值,例如当 Metadata.unit_ref 或 Metadata.help_ref 未提供时。引用必须指向 symbols 字符串数组中已存在的索引。
序列标签(Series Labels)
每个 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 marker)。如果可以检测到时间序列的中断,发送端必须发送过时标记,例如:
- 对于被拉取(scraped)的序列,除非使用了显式的时间戳。
- 对于由记录规则评估产生的序列。
通常,对于已中断的序列不发送过时标记,可能导致接收端出现严重的查询时间对齐问题。
过时标记必须通过特殊的 NaN 值 0x7ff0000000000002 来表示。该值不得用于其他目的。
通常,发送端可以通过以下技术检测一个时间序列何时不再有数据追加:
- 通过服务发现,检测到暴露该序列的目标已消失。
- 在连续的抓取之间,注意到目标不再暴露该时间序列。
- 抓取最初暴露时间序列的目标失败。
- 跟踪记录规则和告警规则的配置和评估。
- 对于非抓取来源的指标,跟踪指标的中断(例如,在 k6 中,当一个基准测试的序列完成时,可以发出一个过时标记)。
元数据(Metadata)
元数据建议遵循 Prometheus 官方关于类型(Type)和帮助(Help)信息的指南。
元数据可以遵循 OpenMetrics 官方关于单位(Unit) 的指南。
Exemplars(范例)
每个范例,如果附加到 TimeSeries 上:
- 必须包含一个值。
- 可以包含标签,例如引用跟踪或请求 ID。如果范例引用了一个跟踪,作为最佳实践,它建议使用
trace_id作为标签名。 - 必须包含一个时间戳。虽然在 Prometheus/Open Metrics 暴露格式中范例的时间戳是可选的,但假设在抓取时会为其分配一个时间戳,就像为抓取样本分配时间戳一样。接收端需要范例的时间戳来可靠地处理(例如去重)传入的范例。
不在范围之内
与 1.0 版本相同。
未来计划
本节包含一些推测性的计划,这些计划尚未被视为协议规范的一部分,但为了完整性在此提及。请注意,2.0 规范完成了 1.0 版本中 3 个未来计划中的 2 个。
-
事务性 2.0 规范仍然没有定义事务性,这主要是因为它会使可扩展的发送端实现变得困难。Prometheus 发送端的目标是实现“事务性”——即永远不向查询暴露部分抓取的目标。我们打算在 Remote-Write 中也这样做——例如,未来我们希望将 Remote-Write 与抓取“对齐”,也许可以将单次抓取的所有样本、元数据和范例都放在一个 Remote-Write 请求中发送。
然而,Remote-Write 2.0 规范解决了传统直方图桶 的一个重要事务性问题。这是通过原生直方图实现的,它支持通过
io.prometheus.write.v2.Request线路格式进行自定义分桶。发送端可能会将所有传统直方图转换为原生直方图,但这并非本规范强制要求。然而,因此,接收端可能会忽略某些指标类型(例如传统直方图)。 -
替代线路格式。OpenTelemetry 社区已经证明了 Apache Arrow(以及其他潜在的列式格式)在通过其 OTLP 协议进行网络数据传输方面的有效性。我们希望进行实验,以确认类似格式与 Prometheus 数据模型的兼容性,并包括任何资源使用变化的基准测试。出于兼容性原因,我们可能会长期维护 protobuf 和列式两种格式,并使用内容协商为此目的添加不同的 Protobuf 消息。
-
全局符号表。用于池化(interning)的预定义字符串字典。该协议可以预定义一个静态的 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 是一种用于在遥测数据源、中间节点和遥测后端之间传输遥测数据(如指标、日志、跟踪和剖析数据)的协议。推荐的传输方式是 gRPC 与 protobuf,但也描述了 HTTP 与 protobuf 或 JSON 的方式。它是从头设计的,意在支持各种不同的可观测性信号、数据类型和额外信息。对于指标 ,这意味着额外的非标识性标签、标志、时间聚合类型、资源或范围指标、schema URL 等。OTLP 还要求使用语义约定 。
Remote-Write 的设计追求简单、高效和有机增长。第一个版本于 2023 年正式发布,而当时CNCF 生态系统中已有数十个经过实战检验的采用者使用此协议多年。Remote-Write 2.0 在前一个协议的基础上进行了迭代,增加了一些新元素(元数据、范例、起始时间戳和原生直方图)以及字符串池化。Remote-Write 2.0 始终是无状态的,只关注指标,并且有明确的立场;因此,它被限定在 Prometheus 社区认为足以构成强大指标解决方案的元素范围内。其意图是确保 Remote-Write 成为一个稳定的协议,比可观测性生态系统中的其他替代方案更便宜、更易于采用和使用。