首页
/ Prometheus Remote Read API 全解:/api/v1/read 端点、外部标签语义与流式 XOR 分块

Prometheus Remote Read API 全解:/api/v1/read 端点、外部标签语义与流式 XOR 分块

2026-09-04 23:57:55作者:庞眉杨Will

本文围绕 Prometheus 仓库中的 Remote Read API 文档 展开,系统讲解 /api/v1/read 端点的请求/响应协议(基于 protobuf)、external_labels 在读数据两端的影响机制,以及流式 XOR 分块(STREAMED_XOR_CHUNKS)响应的编码方式;并结合 web/api/v1/api.gostorage/remote/read_handler.gostorage/remote/read.go 等源码,说明服务端的完整处理链路与测试验证方法,帮助读者既能正确对接远端读取协议,也能理解其底层实现与边界约束。

一、接口定位与稳定性说明

Remote Read API 是 Prometheus 对外暴露的数据读取接口,它允许外部系统(如 Thanos、Cortex 等长期存储)直接从 Prometheus 的 TSDB 中拉取原始时序数据。需要注意官方文档中明确给出的稳定性声明:

NOTE: This is not currently considered part of the stable API and is subject to change even between non-major version releases of Prometheus.(Remote Read API 目前不属于稳定 API,甚至可能在小版本之间发生变化。)

这意味着在对接该接口时,客户端实现应做好兼容演进的准备,例如对响应头、字段变化做宽容处理。接口在管理 API 文档中的对应条目见 POST /api/v1/read,其中说明该端点“允许外部系统(如 Thanos)从 TSDB 读取数据”,并要求请求使用 snappy 压缩(该链接为原文档保留的外部参考)。

docs/storage.md 中,Remote Read 同样被列为 TSDB 数据暴露通道之一,与 --web.enable-remote-write-receive 等写入类端点共同构成 Prometheus 的远端集成面。

二、端点与请求格式

2.1 调用方式

请求发送到一个固定端点(文档原文保留):

POST /api/v1/read

在仓库源码中,该路由注册于 web/api/v1/api.go#L488

r.Post("/read", api.ready(api.remoteRead))

其中 api.ready 包装保证了只有服务完全就绪(TSDB 可用)后该端点才响应;remoteRead 本身(web/api/v1/api.go#L2165-L2168)只是把请求转发给真正的处理逻辑:

func (api *API) remoteRead(w http.ResponseWriter, r *http.Request) {
    if api.remoteReadHandler != nil {
        api.remoteReadHandler.ServeHTTP(w, r)
    }
    ...
}

remoteReadHandlerstorage/remote/read_handler.go 中的 NewReadHandler 构造(web/api/v1/api.go#L360),构造时接收三个重要的容量约束参数(web/api/v1/api.go#L301-L303):

  • remoteReadSampleLimit:单次请求允许读取的样本数上限;
  • remoteReadConcurrencyLimit:并发读取的查询上限;
  • remoteReadMaxBytesInFrame:流式模式下单个帧(frame)的最大字节数。

这些参数共同构成服务端的自我保护:当匹配到的样本量或返回字节量超过限额时,端点会返回错误而不是拖垮进程。

2.2 压缩约定

文档明确指出:该接口期望使用 snappy 压缩("This interface expects snappy compression")。在源码中,SAMPLES 模式的响应会显式设置 Content-Encoding: snappy 响应头(storage/remote/read_handler.go#L124);测试用例 storage/remote/read_handler_test.go 中则使用 github.com/golang/snappy 对响应体进行 snappy.Decode 验证(如 read_handler_test.go#L55#L235-L247)。

请求体本身是一个 protobuf 编码的 ReadRequest 消息,且整体经 snappy 压缩后发送。

三、Protobuf 协议定义

文档声明“API 定义位于 prompb/remote.proto”。在当前仓库中,核心消息定义见 prompb/remote.proto,它引用 prompb/types.proto 中的 TimeSeriesLabelMatcher 等基础类型。关键定义如下(摘自源码,字段编号即 protobuf 线格式字段号):

3.1 请求:ReadRequest

// ReadRequest represents a remote read request.
message ReadRequest {
  repeated Query queries = 1;

  enum ResponseType {
    // Server will return a single ReadResponse message with matched series that
    // includes list of raw samples. ...
    SAMPLES = 0;
    // Server will stream a delimited ChunkedReadResponse message that contains
    // XOR or HISTOGRAM(!) encoded chunks for a single series. ...
    STREAMED_XOR_CHUNKS = 1;
  }

  // accepted_response_types allows negotiating the content type of the response.
  // Response types are taken from the list in the FIFO order. If no response type
  // in `accepted_response_types` is implemented by server, error is returned.
  // For request that do not contain `accepted_response_types` field the SAMPLES
  // response type will be used.
  repeated ResponseType accepted_response_types = 2;
}

要点:

  • ReadRequest 支持一次携带多个查询repeated Query queries),服务端按序返回结果;
  • accepted_response_types响应类型协商字段:服务端按 FIFO 顺序选取第一个自己实现的类型;若请求未携带该字段,则回退为 SAMPLES 模式。源码中对应的兜底逻辑见 storage/remote/read_handler.go#L109-L111:“On empty or unknown types in req.AcceptedResponseTypes we default to non streamed, raw samples response.”

3.2 查询与结果:Query / QueryResult

message Query {
  int64 start_timestamp_ms = 1;   // 查询起始时间(毫秒)
  int64 end_timestamp_ms = 2;    // 查询结束时间(毫秒)
  repeated prometheus.LabelMatcher matchers = 3; // 标签匹配器
  prometheus.ReadHints hints = 4;               // 读取提示(可选)
}

message QueryResult {
  // Samples within a time series must be ordered by time.
  repeated prometheus.TimeSeries timeseries = 1;
}

Query 与 PromQL 的 matcher 语义一致(等值/正则等匹配),时间范围以毫秒为单位;QueryResult.timeseries 中每条时序内的样本必须按时间升序排列。

3.3 响应:ReadResponse / ChunkedReadResponse

// ReadResponse is a response when response_type equals SAMPLES.
message ReadResponse {
  // In same order as the request's queries.
  repeated QueryResult results = 1;
}

// ChunkedReadResponse is a response when response_type equals STREAMED_XOR_CHUNKS.
// We strictly stream full series after series, optionally split by time. ...
message ChunkedReadResponse {
  repeated prometheus.ChunkedSeries chunked_series = 1;
  // query_index represents an index of the query from ReadRequest.queries
  // these chunks relates to.
  int64 query_index = 2;
}

两个值得注意的契约:

  1. ReadResponse.results 的顺序严格对应请求中 queries 的顺序;
  2. 流式模式下,ChunkedReadResponse 严格按“整条时序一条接一条”的顺序流式下发,可选按时间切分;一旦开始流式输出新的一条时序,就不会再回头补发之前时序的 chunk。多查询场景下用 query_index 标明本帧属于哪个查询。

四、external_labels 与 Remote Read:两端都要注意的语义

这是原文档最核心的部分。global 配置中的 external_labels同时影响 remote read 的读端(暴露方)和写端(查询方),两端行为在源码中都能找到对应实现。

4.1 暴露方(服务端)行为

文档描述:Prometheus 会在通过 /api/v1/read 返回的每条时序上附加 external_labels。这些标签并不存储于 TSDB,而是在响应组装时动态注入。

源码印证:storage/remote/read_handler.go#L88-L91 在处理请求时从当前全局配置中读取外部标签:

externalLabels := h.config().GlobalConfig.ExternalLabels.Map()

随后在返回样本时把这些标签写入序列。同时,入参中匹配外部标签的等值 matcher 会被改写为匹配空字符串,这样 TSDB 内部(并不存储外部标签)才能满足该查询——例如请求 up{env="prod"}env 是 external label)时,服务端会把 env="prod" 改写为 env="" 再下推给 TSDB 查询,从而保证只要序列本体存在,该请求仍能命中。

4.2 查询方(客户端)行为

文档描述:作为查询方的 Prometheus 会把自己的 external_labels 作为额外的等值 matcher 加入请求,并在收到响应后把它们从结果中剥离

对应实现位于 storage/remote/read.goSelect 方法注释(read.go#L136-L137)明确写道:

// Select also adds equality matchers for all external labels to the list of
// matchers before calling remote endpoint.
// The added external labels are removed from the returned series sets.

addExternalLabelsread.go#L175-L183)则把每个外部标签转换为等值 matcher 追加到查询条件中。查询方通常配置于 remote_read 段(见 docs/configuration.md),用于把 Prometheus 自己的 TSDB 与远端存储(如 Thanos、VictoriaMetrics)的结果合并。

4.3 一致性要求:为什么可能查不到数据

文档特别警告:

If the querying Prometheus has external_labels that differ from or are absent on the remote server, queries may return no results because the matchers will not match. Ensure that any external_labels on the querying side are consistent with the labels present on the remote side.(如果查询方的 external_labels 与远端不同或远端缺失,查询可能无结果。)

结合两侧机制可以推断其成因:查询方把 env="prod" 作为等值 matcher 发给远端,而远端暴露的序列上若没有 env="prod"(例如远端根本没配置该外部标签,或值不同),matcher 匹配失败,返回空结果。因此部署规范是:查询侧 external_labels 必须是远端序列标签的子集且值一致,这是排查 remote read “查不到数据”问题的第一检查项。

五、服务端实现链路(源码级)

把前面的协议与语义串起来,一次 POST /api/v1/read 在仓库中的处理链路为:

  1. 路由层web/api/v1/api.go#L488 注册 POST /readapi.remoteRead#L2165)委托给 remoteReadHandler
  2. 解析与协商readHandler.ServeHTTPstorage/remote/read_handler.go)反序列化 ReadRequest,读取 accepted_response_types 决定响应模式:命中 STREAMED_XOR_CHUNKS 时调用 remoteReadStreamedXORChunks#L109#L189),否则走原始样本模式;
  3. 响应头
  4. 限额保护remoteReadSampleLimit / remoteReadConcurrencyLimit / remoteReadMaxBytesInFrameNewReadHandler 中注入(read_handler.go#L46-L48),超限即返回错误。

测试侧的端到端验证可见 storage/remote/read_handler_test.go:其中既有对 SAMPLES 模式“响应体 snappy 解压后可反序列化”的断言,也有对流式模式响应头的精确断言(read_handler_test.go#L413-L414):

require.Equal(t, "application/x-streamed-protobuf; proto=prometheus.ChunkedReadResponse", recorder.Result().Header.Get("Content-Type"))
require.Empty(t, recorder.Result().Header.Get("Content-Encoding"))

OpenAPI 层面,/read 路径也注册于管理 API 文档生成器中(web/api/v1/openapi_paths.go#L666-L669,operationId 为 remoteRead),因此它出现在 Prometheus 自动生成的 OpenAPI 规范里。

六、两种响应模式对比

维度 SAMPLES(默认) STREAMED_XOR_CHUNKS
请求协商 accepted_response_types 留空或含 0 accepted_response_types1 且服务端支持
Content-Type application/x-protobuf application/x-streamed-protobuf; proto=prometheus.ChunkedReadResponse
Content-Encoding snappy 无(空)
载荷 单个 ReadResponse,含全部匹配时序的原始样本 分隔符切分的 ChunkedReadResponse 流,XOR 编码 chunk
适用场景 通用兼容,客户端只需 protobuf 解码 大数据量拉取:服务端无需逐样本解压重组,带宽更省
源码位置 read_handler.go#L124 read_handler.go#L189-L190

七、Streamed Chunks 与 XOR 编码

文档“Streamed Chunks”一节指出:

  • 流式 chunk 采用一种受 Gorilla 压缩算法启发的 XOR 编码来编码 chunk(Gorilla 是 Facebook 在 VLDB 2015 提出的时间序列压缩方法,此处不给出外部链接);
  • 与 Gorilla 原始方案按“秒”解析时间戳不同,Prometheus 的版本将时间精度提升到毫秒

这一点与仓库中 TSDB 的 chunk 编码实现相呼应:XOR 编码块位于 tsdb/chunkenc/ 目录,Prometheus 自身写入 TSDB 的样本即使用该编码;remote read 的流式模式本质上把“块内压缩数据”原样下发,省去样本级序列化开销。对客户端而言,这意味着:

  1. 需要理解 delimited protobuf 流的分帧格式:每个 ChunkedReadResponse 帧前是 varint 长度 + 固定 4 字节大端 CRC32-Castagnoli 校验(prompb/remote.proto#L42-L49);
  2. 解码 chunk 需要实现与 TSDB 相同的 XOR 解压,或使用支持该协议的高层客户端库。

八、实践建议与适用边界

综合文档与源码,对接或排查 Remote Read API 时建议:

  1. 先核对 external_labels 一致性(第四节):查询侧 global.external_labels 必须与远端暴露的标签一致,否则查询静默返回空结果;
  2. 默认用 SAMPLES 模式:不传 accepted_response_types 即回退原始样本,兼容性最好;只有在客户端具备 XOR chunk 解码能力时才协商流式模式;
  3. 注意服务端限额:样本数、并发、单帧字节数超限会被拒绝(构造参数见 web/api/v1/api.go#L301-L303),大查询应缩小时间范围或拆分 matcher;
  4. 记住非稳定 API 声明:协议字段与响应头可能在次版本间演进,客户端应做版本探测与降级(例如流式模式失败时回退 SAMPLES);
  5. 区分读写方向:本文讨论的是 Prometheus 作为数据暴露方/api/v1/read;作为查询方拉取远端数据的配置见 remote_readdocs/configuration.md),其 matcher 注入与标签剥离逻辑在 storage/remote/read.go

相关延伸阅读:查询 API 总览存储与远端集成说明、协议源文件 prompb/remote.proto

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384