Prometheus remote_storage_adapter 详解:自建 Graphite、OpenTSDB、InfluxDB 的 Remote Write/Read 适配器
本文基于 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/ 目录下,按存储后端拆成三个子包:graphite、opentsdb、influxdb,由 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 客户端特殊之处是它同时实现了 writer 和 reader 两个接口,所以启用 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 函数:
- 解码请求:调用 storage/remote/codec.go 的 DecodeWriteRequest(该函数注释明确写着 "Used also by documentation/examples/remote_storage"),完成 snappy 解压 + protobuf 反序列化,得到
prompb.WriteRequest; - 转换为内部样本:
protoToSamples把每条TimeSeries的 labels 还原为model.Metric,样本时间戳为毫秒; - 并行分发:为每个 writer 起一个 goroutine 并发写入,
sync.WaitGroup等全部完成后才响应 Prometheus,避免 remote write 队列在写慢时过早释放数据; - 埋点:每次分发记录
received_samples_total(总接收量)、sent_samples_total{remote}(按后端计成功发送量)、failed_samples_total{remote}(失败量)与sent_batch_duration_seconds{remote}(批量发送耗时),全部暴露在--web.telemetry-path(默认/metrics)下,可用于对适配器自身做监控告警。
三种后端的数据映射与限制
Graphite:标签折叠进指标路径
Graphite 没有标签概念,graphite/client.go 的 pathFromMetric 把标签“压”进点分路径: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__bucket。tagvalue_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)的样本会体现在客户端自行注册到 /metrics 的 prometheus_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-protobuf 的 ReadResponse 返回(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.go、opentsdb/client_test.go、opentsdb/tagvalue_test.go、influxdb/client_test.go,可作为二次开发时的行为参照。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00