暴露格式 · /metrics 与 OpenMetrics
指标怎么从进程里吐出来?一个纯文本的 HTTP 端点。这一章讲清这个「简单到近乎粗糙」的格式为什么赢了、OpenMetrics 标准化了什么,以及 Prometheus 源码里其实有三套解析器。
暴露格式 · /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 文本格式 |
| OpenMetrics | openmetricsparse.go(+ openmetricslex.l) | OpenMetrics 格式(带 exemplar) |
| Protobuf | protobufparse.go | 二进制格式(native histograms 走这条) |
| NHCB | nhcbparse.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小结与下一章
暴露格式就是一个返回纯文本的 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。