暴露格式 · /metrics 与 OpenMetrics

指标怎么从进程里吐出来?一个纯文本的 HTTP 端点。这一章讲清这个「简单到近乎粗糙」的格式为什么赢了、OpenMetrics 标准化了什么,以及 Prometheus 源码里其实有三套解析器。

作者 David更新于 第 9 篇(共 52 篇)

暴露格式 · /metrics 与 OpenMetrics

类型定好了,指标怎么从进程里吐出去?答案朴素得让人意外:一个返回纯文本的 HTTP 端点。这一章讲清这个格式,以及“简单”为什么是它最大的优势。

就是一段文本

curl http://your-app:8080/metrics 得到的就是这样一段东西:

# HELP http_requests_total HTTP 请求总数
# TYPE http_requests_total counter
http_requests_total{method="POST",status="200"} 1027
http_requests_total{method="POST",status="500"} 3
# HELP process_resident_memory_bytes 常驻内存字节数
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 1.8874368e+07

规则少到一眼看完:

  • # HELP <名字> <描述> —— 人类可读的说明。
  • # TYPE <名字> <类型> —— 四种类型之一。
  • 数据行 —— 指标名{标签="值",...} 数值 [时间戳]。时间戳可选(几乎总是省略,让 Prometheus 用抓取时刻)。

这个格式的设计哲学是“简单压倒一切”——它不高效、不紧凑、甚至有点浪费,但它赢了。为什么?

  • 人能直接读 —— 出问题时 curl 一下,当前所有指标一目了然。这个“随时可自查”的性质在排障时价值巨大——你不需要任何工具就能确认“这个服务到底吐没吐这个指标”。
  • 谁都能实现 —— 不需要 SDK、不需要 protobuf、不需要客户端库。用任何语言拼字符串都能吐。这是它能席卷整个生态(每个数据库、每个中间件都提供 /metrics)的根本原因。
  • HTTP 是通用底座 —— 复用了整个 HTTP 生态:反向代理、认证、TLS、防火墙规则、curl……不用发明任何东西。

代价是浪费:指标名和标签在每一行重复出现,文本比二进制大得多。但 Prometheus 认为这笔交易划算——网络传输的那点开销,远不及“任何人 5 分钟就能接入”带来的生态价值。这是个“用机器效率换人类效率和生态采纳”的经典决策,也解释了它为什么能成为事实标准。

OpenMetrics:把事实标准写成规范

这个格式最初只是 Prometheus 的实现细节,后来被标准化为 OpenMetrics。它规范化并收紧了几点:

  • _total 后缀 —— counter 必须以 _total 结尾,从“惯例”变成“规范”。
  • # EOF 结束标记 —— 明确标记文档结束,避免“截断的响应被当成完整数据”这类隐患。
  • exemplar 支持 —— 可以在采样点上挂一个 exemplar(带 trace_id 的样例),这是 metrics 与 traces 关联的桥,第 7 部分细讲。
  • 时间戳精度、单位约定等细节。

实战上你几乎不用关心 OpenMetrics 与旧格式的区别——客户端库会处理,Prometheus 靠 HTTP 的 Accept 头协商用哪个。 值得记住的只有两点:①counter 命名带 _total;②要做 metrics↔traces 关联时,exemplar 需要 OpenMetrics 格式。

源码里其实有三套解析器

抓回来的文本要被解析。看 model/textparse/ 目录,有意思的是不止一套解析器:

解析器源码解析什么
Prometheus 文本promparse.go(+ promlex.l 词法)经典 /metrics 文本格式
OpenMetricsopenmetricsparse.go(+ openmetricslex.l)OpenMetrics 格式(带 exemplar)
Protobufprotobufparse.go二进制格式(native histograms 走这条)
NHCBnhcbparse.go经典直方图转 native histogram

两个值得注意的细节:

  • 词法分析器是生成的 —— promlex.l 是 golex 的输入,生成 promlex.l.go(model/textparse/README.md 说明了怎么重新生成)。解析 /metrics 这么“简单”的格式,他们仍然上了正经的词法分析器——因为这是抓取热路径,每秒要解析几十万行,性能极度敏感。
  • protobuf 格式回来了 —— 早年 Prometheus 弃用了 protobuf 暴露格式(为了简单),但 native histograms 又把它请了回来——因为直方图的复杂结构用文本表达太笨重。这是“简单优先”原则的一次务实妥协:主路径保持文本,复杂结构走二进制。

源码里还有个细节能说明性能压力:解析器用了 yoloString(把 []byte 零拷贝转成 string,见 promparse.go:337 附近)——连一次字符串拷贝都要省。这就是热路径的样子。

实战:自查清单

/metrics 是你排查采集问题的第一现场:

# 1. 服务到底吐没吐?
curl -s http://localhost:8080/metrics | head -20

# 2. 我关心的指标在不在?
curl -s http://localhost:8080/metrics | grep http_requests_total

# 3. 有多少条 series?(基数自查!)
curl -s http://localhost:8080/metrics | grep -vc '^#'

# 4. 哪个指标的 series 最多?(揪出基数元凶)
curl -s http://localhost:8080/metrics | grep -v '^#' | sed 's/{.*//' | sort | uniq -c | sort -rn | head

第 4 条命令值得你现在就去跑一遍。 它按指标名统计 series 数,排在最前面的就是你的基数大户。很多团队直到 Prometheus 内存爆了才去查,其实在服务端 curl 一下就能提前发现——某个指标吐出几万行,基本就是标签里混进了高基数的东西(user_id、URL 路径带 ID、错误消息……)。第 2 部分和第 9 部分会讲怎么根治,但这个自查命令是最便宜的早期预警。

小结与下一章

暴露格式就是一个返回纯文本的 HTTP 端点:# HELP/# TYPE 加数据行(名字{标签} 值 [时间戳],时间戳几乎总省略)。它的哲学是“简单压倒一切”——人能直接 curl 读、任何语言拼字符串就能实现、复用 HTTP 生态;代价是文本浪费,但换来了生态采纳,这是它成为事实标准的根本。OpenMetrics 把它标准化(_total 后缀、# EOF、exemplar)。源码 model/textparse/ 里有三套解析器(文本 / OpenMetrics / protobuf,外加 NHCB),词法分析器是 golex 生成的、还用 yoloString 零拷贝——因为这是每秒解析几十万行的热路径;native histograms 让 protobuf 格式回归,是“简单优先”的一次务实妥协。实战上,curl /metrics 是排查采集问题的第一现场,也是最便宜的基数自查。

指标端点有了,Prometheus 怎么知道去抓谁?下一章看服务发现。👉 服务发现。

参考资料

  • [1] Prometheus 源码 model/textparse/(promparse.go、openmetricsparse.go、protobufparse.go、nhcbparse.go、README.md、promlex.l)。
  • [2] OpenMetrics 规范(CNCF)。
  • [3] Prometheus 官方文档:Exposition formats。

本页目录