首页
/ Prometheus remote_storage_adapter 详解:自建 Graphite、OpenTSDB、InfluxDB 的 Remote Write/Read 适配器

Prometheus remote_storage_adapter 详解:自建 Graphite、OpenTSDB、InfluxDB 的 Remote Write/Read 适配器

2026-09-06 09:03:19作者:董斯意

本文基于 README 讲解 Prometheus 官方示例中的 remote_storage_adapter:一个通过 remote write 协议接收样本并写入 Graphite、OpenTSDB、InfluxDB 的写适配器,同时还是支持 remote read 协议从 InfluxDB 回读数据的读适配器。读完本文,你将掌握该适配器的构建与启动方法、全部命令行参数、prometheus.yml 的对接配置,以及三个存储后端各自的标签映射、转义规则与查询转换等源码级实现细节。

背景:为什么需要一个独立适配器

Prometheus 曾内置 Graphite、OpenTSDB、InfluxDB 等 remote storage 实现,这些实现后来从 Prometheus 主程序中被移除。README 明确指出,remote_storage_adapter 就是为这些被移除的实现提供替代方案而存在的:

  • 写适配器(全部三种存储):以 HTTP 服务形式接收 Prometheus remote write 协议发来的样本,再转发到 Graphite、OpenTSDB 或 InfluxDB,可以一次配置多个目标,并行写入;
  • 读适配器(目前仅 InfluxDB):实现 remote read 协议的 /read 端点,把 Prometheus 的读请求翻译成 InfluxDB 查询并回传时序数据。

从源码结构看,这个适配器位于 documentation/examples/remote_storage/remote_storage_adapter/ 目录下,按存储后端拆成三个子包:graphiteopentsdbinfluxdb,由 main.go 统一装配。它属于 documentation/examples/remote_storage 下的独立 Go module,与 Prometheus 主 module 分离,依赖版本互不影响。

构建

进入适配器目录后直接构建即可(该目录归属于 documentation/examples/remote_storage 这个独立 Go module):

cd documentation/examples/remote_storage/remote_storage_adapter
go build

构建产物为同名可执行文件 remote_storage_adapter

运行:三种存储的启动示例

README 给出的三种典型启动命令分别如下。

Graphite(走 TCP 明文协议,默认端口由你的 Graphite 服务端决定):

./remote_storage_adapter --graphite-address=localhost:8080

OpenTSDB(走 HTTP API):

./remote_storage_adapter --opentsdb-url=http://localhost:8081/

InfluxDB(同时启用写与读;认证 token 通过环境变量传入):

INFLUXDB_AUTH_TOKEN=<token> ./remote_storage_adapter --influxdb-url=http://localhost:8086/ --influxdb.organization=<organization_name> --influxdb.bucket=<bucket_name>

执行 ./remote_storage_adapter -h 可查看全部 flag。结合 main.go 的 parseFlags,完整参数如下表(默认值均取自源码):

Flag 默认值 说明
--graphite-address Graphite 服务的 host:port,为空则不启用 Graphite 写入
--graphite-transport tcp 与 Graphite 通信的传输协议(net.Dial 语义,即 tcp
--graphite-prefix 写入 Graphite 时给所有指标路径加的前缀
--opentsdb-url OpenTSDB 服务 URL,为空则不启用
--influxdb-url InfluxDB 服务 URL,为空则不启用
--influxdb.bucket InfluxDB 的 bucket(2.x 概念)
--influxdb.organization InfluxDB 的 organization
INFLUXDB_AUTH_TOKEN(环境变量) InfluxDB 认证 token,注意它不是命令行 flag,而是 在 parseFlags 中从环境变量读取
--send-timeout 30s 向各 remote storage 发送样本的超时时间
--web.listen-address :9201 HTTP 服务监听地址,/write/read/metrics 都挂在这里
--web.telemetry-path /metrics 适配器自身指标暴露路径

此外还可通过 flag.AddFlags 注册的一组 --log.level / --log.format 参数调整日志(基于 promslog)。

buildClients 的逻辑(main.go)值得注意:配置了哪个后端,就实例化哪个 writer;三者可以任意组合、同时启用,同一批样本会被并行写入所有已配置的存储。InfluxDB 客户端特殊之处是它同时实现了 writerreader 两个接口,所以启用 InfluxDB 后 /read 端点才可用。

配置 Prometheus 对接适配器

README 给出的 prometheus.yml 片段如下(地址对应适配器默认的 :9201 监听端口):

# Remote write configuration (for Graphite, OpenTSDB, or InfluxDB).
remote_write:
  - url: "http://localhost:9201/write"

# Remote read configuration (for InfluxDB only at the moment).
remote_read:
  - url: "http://localhost:9201/read"

要点:

  • remote_write 指向适配器的 /write,Graphite、OpenTSDB、InfluxDB 均可用;
  • remote_read 指向 /read,当前仅支持 InfluxDB。从 main.go 的 /read 处理逻辑 可以看到:源码中明确写了 TODO: Support reading from more than one reader and merging the results,即 reader 数量必须恰好为 1,否则直接返回 500。所以 remote_read 只应在只配置了 InfluxDB 的场景下启用。

写链路:/write 端点的实现

Prometheus 的 remote write 客户端把 prompb.WriteRequest 做 protobuf 序列化、snappy 压缩后 POST 到 url。适配器侧的处理在 main.go 的 serve 函数

  1. 解码请求:调用 storage/remote/codec.go 的 DecodeWriteRequest(该函数注释明确写着 "Used also by documentation/examples/remote_storage"),完成 snappy 解压 + protobuf 反序列化,得到 prompb.WriteRequest
  2. 转换为内部样本protoToSamples 把每条 TimeSeries 的 labels 还原为 model.Metric,样本时间戳为毫秒;
  3. 并行分发:为每个 writer 起一个 goroutine 并发写入,sync.WaitGroup 等全部完成后才响应 Prometheus,避免 remote write 队列在写慢时过早释放数据;
  4. 埋点:每次分发记录 received_samples_total(总接收量)、sent_samples_total{remote}(按后端计成功发送量)、failed_samples_total{remote}(失败量)与 sent_batch_duration_seconds{remote}(批量发送耗时),全部暴露在 --web.telemetry-path(默认 /metrics)下,可用于对适配器自身做监控告警。

三种后端的数据映射与限制

Graphite:标签折叠进指标路径

Graphite 没有标签概念,graphite/client.gopathFromMetric 把标签“压”进点分路径:prefix.指标名.标签名.标签值...,标签按字典序排序保证同一条 series 路径稳定。写入采用 Graphite 的 plain-text 行协议(路径 值 时间戳),经 TCP 直连(--graphite-transport 指定,默认 tcp)一次性发送。

由于 Graphite 对合法字符的限制很严,escape.go 实现了一套近似百分号编码的转义:.%/= 一律百分号编码;(){},=.'"\ 前加反斜杠;其余可打印字符原样保留,非标打印字节统一转 %XX。例如 http://example.org:8080 会变成 http:%2F%2Fexample%2Eorg:8080。该编码保证任意标签值都能落进 Graphite 文件名(whisper 后端以文件名存数据)。相关行为有 escape 测试client_test.go 覆盖。注意 client.go静默跳过 NaN/±Inf 样本并打 debug 日志。

OpenTSDB:JSON put API 与下划线转义

opentsdb/client.go 把整批样本序列化为 JSON 数组,POST 到 OpenTSDB 的 /api/put 端点(Content-Type: application/json,带 --send-timeout 超时控制)。指标名成为 OpenTSDB 的 metric,其余标签一一映射为 tags。成功时 API 返回 204;失败返回 400,响应体是 {"failed": n, "success": m} 形式的统计,适配器据此报错。

与 Graphite 同理,OpenTSDB 字符串只允许 [a-zA-Z0-9._/-]tagvalue.go 因此定义了 TagValue 类型并实现自定义 MarshalJSON/UnmarshalJSON:下划线 _ 转义为 __,冒号 : 转义为 _.(因为 Prometheus 指标名常含 :),其余非法字节转 _XX 十六进制。例如指标 http_request_duration_seconds_bucket 会变成 http__request__duration__seconds__buckettagvalue_test.go 对编解码往返做了测试。OpenTSDB 客户端同样跳过 NaN/±Inf 样本。

InfluxDB:写为 Line Protocol Point,读为 Flux 查询

influxdb/client.go 基于官方 influxdb-client-go/v2,面向 InfluxDB 2.x(organization + bucket 模型):

写入Write):指标名映射为 measurement,其余标签映射为 tags,样本值放入 field value,时间精度设为毫秒(influx.DefaultOptions().SetPrecision(time.Millisecond));通过 WriteAPIBlocking 逐点写入并开启批处理(默认批大小 5000)后 Flush。被跳过(NaN/±Inf)的样本会体现在客户端自行注册到 /metricsprometheus_influxdb_ignored_samples_total 计数器上(见 main.go 对 InfluxDB 客户端 MustRegister 的处理)。

读取Read):Prometheus remote read 请求里的 prompb.Query 携带标签 matcher 和时间范围,buildCommand 将其翻译成 Flux 管道 from(bucket: "…") |> range(start:…, stop:…) |> filter(...),matcher 映射规则为:

Prometheus matcher Flux 条件 适用对象
== r.x == "v" 指标名 / 普通标签
!= r.x != "v" 普通标签
=~ r.x =~ /re/ 指标名 / 普通标签
!~ r.x !~ /re/ 普通标签

注意一个源码中明确的限制:!~(正则不等于)matcher 用于指标名时会直接报错 "non-equal or regex-non-equal matchers are not supported on the metric name yet"(client.go)。结果侧,mergeResult 把 Flux 记录转回 prompb.TimeSeries:measurement 还原为 __name__ 标签、tags 还原为标签对,并过滤掉 _time_value_measurement 等 Flux 内置字段;当查询选中不同标签集合的 series 时,InfluxDB 会对所有 series 返回全量标签名(缺失的为空值),代码用“空标签值等价于不存在的标签”这一约定跳过空值,再按时间戳归并去重,最终包成 snappy 压缩、Content-Type: application/x-protobufReadResponse 返回(main.go)。

小结与适用边界

  • remote_storage_adapter 是“协议翻译器”:它对 Prometheus 暴露标准 remote write/read HTTP 接口,对后端使用各存储的原生协议(Graphite plain text / OpenTSDB HTTP put / InfluxDB client API),部署上与 Prometheus 解耦,可按需独立扩容与重启;
  • 能力边界由源码直接决定:写端点支持多后端并行;读端点当前仅支持 InfluxDB 且 reader 必须唯一;非有限浮点值(NaN/±Inf)在三种后端中都会被丢弃,其中 InfluxDB 有独立计数器可观测;
  • 相关行为均有测试覆盖:graphite/client_test.goopentsdb/client_test.goopentsdb/tagvalue_test.goinfluxdb/client_test.go,可作为二次开发时的行为参照。
登录后查看全文
热门项目推荐
相关项目推荐