1. OpenMetrics协议
1.1 OpenMetrics 是什么
OpenMetrics 是指标暴露文本协议规范 ,从 Prometheus 原始文本格式进化而来,目标是统一云原生指标的线上传输标准,脱离 Prometheus 本身,做到客户端、采集器无关。
-
诞生:基于 Prometheus
/metrics文本格式,2020 发布 1.0 稳定版; -
现状:2024 年归档,合并回 Prometheus 项目,不再独立作为顶级 CNCF 项目,但协议本身继续演进(2.0 实验版);
-
传输方式:服务暴露 HTTP
/metrics端点,Prometheus/其他采集器主动拉取(scrape)。Content-Type:application/openmetrics-text; version=1.0.0。
一句话定位:应用导出指标给监控系统的文本有线格式(wire format) 。 前面我们在 WAL 文档里看到的 Exemplar(追踪样例)、Native Histogram(原生直方图)、指标
# HELP/# TYPE元数据,全部来自 OpenMetrics 规范定义。
1.2 OpenMetrics 支持的指标类型(和 WAL 记录一一对应)
-
Counter:只增计数器(请求总数) -
Gauge:瞬时值(内存、CPU) -
Histogram:原生直方图(Native Histogram,对应 WAL type7/8/9/10) -
Summary:分位数摘要 -
Info:静态元数据(版本、环境) -
Stateset:状态枚举(状态机:running/stopped) -
Untyped:未定义类型
1.3 OpenMetrics 文本样例(OM 1.0)
# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
# UNIT http_requests_total requests
http_requests_total{method="GET",status="200"} 1234 # {trace_id="abc123",span_id="xyz789"} 1750000000000 10.5
# EOF
-
# HELP:指标描述 -
# TYPE:指标类型 -
# UNIT:单位(OM新增,老Prom格式没有) -
# {trace_id...}:Exemplar 样例,就是 WAL type=4 记录对应的来源,把追踪信息绑定到单个样本上。 -
# EOF:OM 规范强制末尾结束标记。
这就是 WAL 里 Exemplar、Metadata 的源头:应用按 OpenMetrics 输出文本 → Prometheus scrape 拉取 → 解析成时序、元数据、exemplar、直方图 → 写入 WAL(type1/2/4/6/7...)。
1.4 OpenMetrics 和 Prometheus WAL 的数据流关系
应用程序 → OpenMetrics文本(/metrics)
↓ scrape拉取
Prometheus 采集器解析:
1. 读到新的label集合 → WAL type=1 Series Record
2. 读到普通样本 → WAL type=2 Samples Record
3. 读到Exemplar追踪样例 → WAL type=4 Exemplar Record
4. 读到#HELP/#UNIT/#TYPE → WAL type=6 Metadata Record
5. 读到Native Histogram → WAL type7/8/9/10 Histogram Record
6. 用户调用删除API → WAL type=3 Tombstones Record
↓
写入WAL(先写WAL,再写入Head内存)
↓
Head 内存(chunks_head)
↓ 达到块大小,切块
Block(磁盘,包含index、chunks、tombstones文件)
1.5 OpenMetrics 和老 Prometheus 文本格式的核心差异
-
新增
# UNIT单位元数据; -
原生支持 Exemplar(追踪样例);
-
原生支持 Native Histogram 原生直方图;
-
强制
# EOF结束标记; -
规范更严谨,想推动成为 IETF 标准;
-
支持类型更丰富:Info、Stateset。
1.6 常见误区
-
❌ OpenMetrics ≠ Prometheus。OpenMetrics只是指标导出文本协议;Prometheus是完整时序数据库,包含WAL、存储、查询、采集。
-
❌ OpenMetrics 和 OpenTelemetry Metrics 不是同一个东西:
-
OpenMetrics:文本拉取协议,用于应用暴露指标;
-
OpenTelemetry Metrics:遥测SDK,可以推指标,支持多种编码(protobuf/json),也可以导出为OpenMetrics。
-
-
❌ OpenMetrics 2024归档=废弃:归档只是合并回Prometheus仓库,协议继续迭代,大量exporter仍在输出OpenMetrics。
2. OpenMetrics 7种指标类型示例
全部遵循 OpenMetrics 1.0 文本格式,包含
# HELP/# TYPE,部分带# UNIT。
2.1. Counter 只增计数器
含义:单调递增,只往上加,不会下降;只有进程重启才会归零。适合请求总数、错误总数。
# HELP http_requests_total Total HTTP requests received
# TYPE http_requests_total counter
# UNIT http_requests_total requests
http_requests_total{method="GET",status="200"} 15689
http_requests_total{method="POST",status="500"} 247
# EOF
2. Gauge 仪表盘/瞬时值
含义:可增可减,代表某一瞬间的快照。内存、连接数、CPU使用率、队列长度。
# HELP process_memory_usage_bytes Current memory used by the process
# TYPE process_memory_usage_bytes gauge
# UNIT process_memory_usage_bytes bytes
process_memory_usage_bytes{pid="1234"} 23560192
# EOF
3. Histogram
传统桶式直方图
# HELP http_request_duration_seconds HTTP request latency
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.01"} 120 # ≤ 0.01s
http_request_duration_seconds_bucket{le="0.05"} 890 # ≤ 0.05s
http_request_duration_seconds_bucket{le="0.1"} 3200 # ≤ 0.1s
http_request_duration_seconds_bucket{le="0.5"} 4500 # ≤ 0.5s
http_request_duration_seconds_bucket{le="+Inf"} 4560 # ≤ +∞
http_request_duration_seconds_sum 125.4
http_request_duration_seconds_count 4560
# EOF
原生直方图 Native Histogram
注意:这是Native Histogram(原生直方图),不是传统的 bucket 拆分直方图;一条时序内部自带分布桶,对应 WAL type7/8/9/10。
# HELP http_request_duration_seconds Request latency distribution
# TYPE http_request_duration_seconds histogram
# UNIT http_request_duration_seconds seconds
http_request_duration_seconds{method="GET"} {schema="3",zero_threshold="0.001"} 128 0.45 10 20 30
# EOF
传统分桶直方图(老版本,不是Native Histogram)是拆成多个bucket时序,OM同样支持,但不属于Native Histogram。
4. Summary 摘要(分位数)
含义:预先在客户端计算好分位数(p50/p90/p99),直接上报分位结果。
缺点:分位数在SDK侧预先算死,服务端不能换分位数查询。
# HELP api_response_latency_seconds Summary of API latency
# TYPE api_response_latency_seconds summary
# UNIT api_response_latency_seconds seconds
api_response_latency_seconds{quantile="0.5"} 0.023
api_response_latency_seconds{quantile="0.9"} 0.081
api_response_latency_seconds{quantile="0.99"} 0.215
api_response_latency_seconds_sum 1254.32
api_response_latency_seconds_count 48200
# EOF
5. Info 静态元数据指标
含义:一次性静态信息,版本号、环境、构建信息,值固定为1,标签携带信息。
# HELP app_info Application metadata
# TYPE app_info info
app_info{version="v2.1.0",env="prod",git_commit="aef123"} 1
# EOF
6. Stateset 状态集合(状态枚举)
含义 :一组互斥状态,同一时间只能有一个状态=1,其余=0,描述状态机。例如服务运行状态。
# HELP service_state Service running state
# TYPE service_state stateset
service_state{state="running"} 1
service_state{state="stopped"} 0
service_state{state="error"} 0
# EOF
7. Untyped 未定义类型
含义:不知道指标类型,兼容旧数据;不推荐新指标使用。
# HELP legacy_metric A legacy metric without defined type
# TYPE legacy_metric untyped
legacy_metric{name="old_job"} 88.5
# EOF
3. 传统分桶直方图(Classic Bucket Histogram) vs Native Histogram(原生直方图)
传统直方图:Prometheus 早期方案,也是很多exporter默认输出的; Native Histogram:OpenMetrics 1.0引入,Prometheus 2.40+正式支持,对应WAL type7/8/9/10。
3.1. 文本格式对比(OpenMetrics)
传统 Bucket Histogram(多时序方案)
同一个逻辑直方图,拆成多条独立Counter时序
# HELP http_request_duration_seconds HTTP request latency
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.01"} 120 # ≤ 0.01s
http_request_duration_seconds_bucket{le="0.05"} 890 # ≤ 0.05s
http_request_duration_seconds_bucket{le="0.1"} 3200 # ≤ 0.1s
http_request_duration_seconds_bucket{le="0.5"} 4500 # ≤ 0.5s
http_request_duration_seconds_bucket{le="+Inf"} 4560 # ≤ +∞
http_request_duration_seconds_sum 125.4
http_request_duration_seconds_count 4560
# EOF
-
le:bucket上限标签; -
每一个桶都是独立时序;
_sum、_count额外两条; -
上面这个例子:5个桶 → 一共 7条时序。
Native Histogram(原生直方图,单时序承载全部分布)
# HELP http_request_duration_seconds HTTP request latency
# TYPE http_request_duration_seconds histogram
# UNIT http_request_duration_seconds seconds
http_request_duration_seconds{method="GET"} {schema="3",zero_threshold="0.001"} 4560 125.4 120 770 2310 1300 60
# EOF
-
仅1条时序 ;桶、count、sum、零桶信息全部编码在单样本内部;
-
不再产生一堆带
le标签的子时序。
区间内的**增量计数**:
1. [0, 0.01] → 120
2. (0.01, 0.05] → 770
3. (0.05, 0.1] → 2310
4. (0.1, 0.5] →1300
5. (0.5, ∞) →60
语法结构:
metric_name{labels} {options} count sum bucket1 bucket2 bucket3 ...
分段拆解
-
http_request_duration_seconds{method="GET"}时序名称 + 标签。代表:GET 请求的延迟指标,只有这1条时序 (传统直方图会拆成一堆带le的子时序)。 -
{schema="3",zero_threshold="0.001"}直方图配置参数
-
schema="3":指数桶的分辨率。 schema 取值范围-4 ~ 8,数字越大,桶越精细。 schema=3:每一个桶的上限 = 前一个桶 ×2^(1/2^3)=2^(1/8)≈ 1.0905。 → 每下一个桶比上一个桶大9.05%。 schema越大,精度越高,但桶数量越多,存储开销上升。 -
zero_threshold="0.001":零桶阈值 0.001秒(1ms) 落在区间[-0.001, +0.001]的样本,全部归入零桶,不再进入正负指数桶。 目的:把极靠近0的微小噪声收拢到一个桶,避免产生大量极小值空桶,减少存储。
-
4560→ count(总观测样本数) 一共采集到 4560次GET请求。 -
125.4→ sum(所有样本总和,单位秒) 全部4560次请求耗时加起来 = 125.4秒。平均延迟 = sum / count = 125.4 / 4560 ≈ 0.0275s = 27.5ms
-
后面一串数字:
120 770 2310 1300 60这是各个桶里面的请求计数(正数侧桶,本次例子没有负数样本)。这里简化写法,省略了完整的spans描述,实际wire格式/WAL里是用
span描述:哪些桶索引有值,空桶不保存,实现稀疏压缩。-
120:落在第一个有效桶的请求数量
-
770:第二个桶
-
2310:第三个桶(最多的请求落在这个延迟区间)
-
1300:第四个桶
-
60:第五个桶
-
校验总和:120+770+2310+1300+60 = 4560,刚好等于count,说明零桶里面样本数量=0,没有请求落在 -1ms,1ms 区间。
语义一句话总结
GET请求延迟,使用schema=3指数桶,1ms以内的请求归入零桶;一共4560次请求,总耗时125.4秒;所有请求分布在5个正数侧指数桶,没有请求落在±1ms以内。 Prometheus收到这条样本后,写入WAL type7 整数原生直方图记录。
和传统Bucket直方图对比(直观感受)
传统直方图要写成多条时序:
http_request_duration_seconds_bucket{le="0.01"} 120
http_request_duration_seconds_bucket{le="0.05"} 890
http_request_duration_seconds_bucket{le="0.1"} 3200
http_request_duration_seconds_bucket{le="0.5"} 4500
http_request_duration_seconds_bucket{le="+Inf"} 4560
http_request_duration_seconds_sum 125.4
http_request_duration_seconds_count 4560
-
传统:多条独立时序
-
Native Histogram:单条时序,样本内部打包全部分布信息
对应的PromQL查询示例
# p99延迟
histogram_quantile(0.99, http_request_duration_seconds)
# p50中位数
histogram_quantile(0.5, http_request_duration_seconds)
# 5m速率,再算p99(推荐生产写法)
histogram_quantile(0.99, rate(http_request_duration_seconds[5m]))
重要细节
-
桶边界不是固定写死的数字 ,是由
schema公式动态计算 ,不是像传统直方图那样写死le="0.1"。 -
空桶不存储,用span压缩,所以不会保存全部桶,只保存有计数的桶。
-
零桶是独立的,专门收拢靠近0的噪声。
如果你想,我可以把这条原生直方图转成WAL type7的二进制结构伪代码,对照前面wal.md的格式。
3.2. 核心差异总表
| 对比项 | 传统Bucket Histogram | Native Histogram |
|---|---|---|
| 时序数量 | N个桶 → N+2条时序(bucket + sum + count),基数爆炸 | 1条时序承载完整分布,极大降低cardinality |
| 分位数 | 查询时服务端基于le桶插值计算;桶边界写死在exporter侧 | 服务端任意分位数(p50/p90/p99.9/p99.99),不用预定义桶 |
| 桶边界 | 固定线性/自定义桶,修改桶边界等于创建全新时序 | 指数桶(默认),也支持NHCB自定义桶;桶边界可动态调整,不新增时序 |
| 聚合能力 | 多实例聚合麻烦,只能聚合到固定桶边界;跨实例分位数失真 | 支持sum聚合分布,合并多个直方图,聚合后依然可以算任意分位数 |
| 存储 | 每个bucket独立chunk,存储开销大,高基数场景压力巨大 | 内置稀疏桶Span压缩,空桶不存储,存储效率更高 |
| WAL记录 | 普通type=2 Samples Record,多条时序分别写入 | 专用type7/8/9/10 Histogram记录,单条记录保存完整分布 |
| 查询开销 | 计算分位数需要遍历所有bucket时序,查询复杂 | 直接操作分布结构,分位数计算更快;但直方图合并逻辑更重 |
| 兼容性 | 所有版本Prometheus都支持 | 需要Prometheus ≥2.40,老版本不识别 |
| 精度 | 桶越密精度越高,但桶越多时序爆炸;桶稀疏则分位数误差大 | 指数桶,小值区域桶细、大值区域桶变粗;可通过schema调整分辨率 |
3.3. 优缺点拆解
传统 Bucket Histogram
✅ 优点
-
兼容性极好,生态成熟,所有exporter都支持;
-
逻辑简单,底层就是普通counter;
-
可以直接对固定桶做告警(
le="0.1")。
❌ 缺点
-
基数爆炸(最大痛点):一个实例+10个桶就是12条时序;几百实例直接几万时序;
-
桶边界写死在采集侧,想增加/修改桶边界,会生成新时序;
-
分位数只能在预先定义的桶之间插值;
-
多实例聚合后,分位数估算偏差很大。
Native Histogram
✅ 优点
-
大幅降低时序基数,解决高基数噩梦;
-
服务端任意分位数查询,不需要预先定义桶;
-
天然支持分布聚合:多个实例直方图sum合并后,依然可以计算p99/p99.9;
-
稀疏桶压缩,空桶不占用存储;
-
桶分辨率schema可调整,不需要改动标签、不产生新时序。
❌ 缺点
-
逻辑复杂:采集、WAL、chunk、查询、compaction全链路都有特殊处理;
-
旧版本Prometheus不识别;
-
无法直接基于固定桶边界写告警(桶边界不是固定离散标签);
-
直方图合并、计算会有少量精度损失;
-
客户端SDK支持还在普及阶段。
3.4. 生产选型建议
-
新业务、延迟类指标:优先 Native Histogram;解决基数爆炸,灵活查询任意分位数;
-
老系统、需要基于固定桶告警:继续传统bucket直方图;
-
不要混用:同一个指标不要同时输出传统bucket和native histogram。
4. Grafana、Mimir、VictoriaMetrics(VM)Native Histogram 支持版本
新格式 = Prometheus Native Histogram(指数桶+NHCB自定义分桶,对应 WAL type7/8/9/10) 传统Bucket直方图所有版本全部支持,下面只讲原生直方图。
1. Grafana(前端可视化)
Grafana 从 v9.4 开始实验性支持原生直方图; v10.0+ 正式稳定支持,可以直接渲染原生直方图的热图、分位数面板。
-
v10.x:支持
histogram_quantile()、原生直方图heatmap; -
v11+:进一步完善NHCB自定义分桶直方图可视化。
注意:Grafana只是展示层,底层TSDB(Mimir/Prometheus/VM)必须支持原生直方图查询,Grafana才能拿到数据。
2. Grafana Mimir
| 版本 | 状态 | 说明 |
|---|---|---|
| 2.7 | 实验 | 仅支持写入Ingest,不能查询 ,需要开启flag ingester.native-histograms-ingestion-enabled |
| 2.8+ | beta | 支持写入+查询指数桶Native Histogram;NHCB自定义桶后续迭代完善 |
| 2.16 | 稳定,默认开启 | Ingester默认开启原生直方图,支持乱序原生直方图,生产可用(推荐最低基线) |
| 2.17+ | 完善 | 支持OTel直方图转NHCB自定义分桶直方图,MQE引擎深度优化直方图聚合 |
| 3.x | 成熟 | MQE默认引擎,NHCB完整支持,支持直方图trim算子等高级PromQL |
生产建议:Mimir ≥ 2.16 再上Native Histogram。
3. VictoriaMetrics(VM,单节点/集群)
⚠️ VM和Mimir/Prometheus实现逻辑不一样:收到Prometheus原生直方图后,自动内部转换成VM自己的直方图格式,对外暴露为
_count/_sum/_bucket时序,带vmrange标签 ,可以直接用histogram_quantile()查询。
-
v1.143.0 :首次引入原生直方图支持,vmagent、单节点、集群都可以摄入Prometheus原生直方图(指数桶)
-
v1.153.0:完善NHCB自定义分桶原生直方图支持,remote read模式支持迁移原生直方图,保留自定义桶边界
重要限制:VM 不原生保留Prometheus原生直方图的内部结构,写入即转成VM直方图模型; Remote Write 2.0协议VM目前不支持。
4. Prometheus 本身(顺带补齐,前面WAL的源头)
-
v2.40:正式GA支持Native Histogram(指数桶),WAL type7/8
-
v2.45:增加NHCB自定义分桶直方图 type9/10
-
v3.0:完善Remote Write v2.0,支持原生直方图远程转发给Mimir
5. 生产部署完整链路版本底线(端到端)
采集端(Prometheus/Alloy)→ RemoteWrite → Mimir/VM → Grafana看板
方案A:Mimir(原生保存Native Histogram,推荐)
-
Prometheus ≥2.40 或 Grafana Alloy ≥1.11
-
Mimir ≥2.16
-
Grafana ≥10.0
方案B:VictoriaMetrics(自动转换)
-
Prometheus ≥2.40
-
VictoriaMetrics ≥1.143.0
-
Grafana ≥10.0
关键坑点
-
Mimir 2.7~2.15:虽然能写入,但查询、聚合、NHCB不稳定,不建议生产;
-
VM:原生直方图进入VM后会被转成
vmrange桶,不再是Prometheus原生直方图结构;跨系统迁移要留意; -
远程写入协议:传递Native Histogram必须使用 Remote Write v2(protobuf),老的v1文本remote write不支持;
-
老Grafana <10:能查分位数,但无法渲染原生直方图热图。