3、指标采集原理:Pull模型 vs Push模型,Exporter设计模式,指标格式规范(OpenMetrics)
好,咱们今天聊一个非常核心的话题——指标到底是怎么从你的服务里「掏」出来的。
我见过不少团队,监控系统搭得挺漂亮,Grafana 面板画得跟艺术品似的,但一问到指标怎么采集的,大家就含糊了。嗯,这块其实特别重要。你想想看,数据源都搞不清楚,后面的告警、分析全是空中楼阁。
这一章,我就把指标采集的三种核心要素掰开揉碎讲清楚:Pull 与 Push 模型、Exporter 设计模式,以及OpenMetrics 格式规范。
3.1 Pull 模型 vs Push 模型:两种哲学
先说第一个选择题:你的监控系统,是主动去拉数据,还是等数据自己送上门?
这俩思路,说白了就是「拉」和「推」的区别。
3.1.1 Pull 模型:服务端主动出击
Pull 模型是 Prometheus 的招牌动作。监控系统(比如 Prometheus Server)定期去访问你的应用暴露的 HTTP 端点,把指标数据「拉」回来。
优点很明显:
- 更容易控制采集频率:你可以在监控端统一配置拉取间隔,比如 15 秒一次。想调快调慢,改一处就行。
- 天然的服务健康检查:如果某个目标拉取失败,说明它可能挂了或者网络不通。这个信息本身就是告警信号。
- 避免数据洪峰:Push 模式下,如果所有服务同时推送,监控系统可能被冲垮。Pull 模式下,监控端自己掌握节奏。
缺点也实在:
- 需要服务发现:你得知道哪些目标在运行、在哪个端口。Kubernetes 里还好说,传统环境就得靠 Consul 或文件发现。
- 网络可达性要求高:监控端必须能连上每个目标。跨机房、跨 VPC 的场景下,这个限制挺头疼的。
3.1.2 Push 模型:应用主动上报
Push 模型正好反过来。你的应用主动把指标数据发到中央收集器(比如 Graphite、InfluxDB 或 Prometheus Pushgateway)。
优点:
- 适合短任务:批处理脚本、定时任务,跑完就结束。Pull 模型根本来不及拉,Push 模型可以在任务结束时把指标发出去。
- 网络拓扑简单:应用只需要能连上收集器,不需要收集器反过来连应用。防火墙规则好配很多。
缺点:
- 容易产生数据孤岛:如果应用挂了,它就不会 Push 了。你很难区分「没有数据」和「数据为零」。
- 推送压力不可控:我曾经遇到过一个场景,上千个 Agent 同时推送,直接把收集器的网卡打满了。嗯,那场面挺壮观的。
3.1.3 怎么选?
我个人习惯:长期运行的服务,用 Pull;短生命周期任务,用 Push。
如果你用 Prometheus,默认就是 Pull。遇到批处理场景,配合 Pushgateway 做补充。别搞反了。
3.2 Exporter 设计模式:把数据「翻译」成指标
好,模型选完了。下一个问题:你的应用怎么暴露指标?
你总不能在每个业务代码里都写一堆监控逻辑吧?那太耦合了。这时候就需要 Exporter 出场。
Exporter 本质上是一个适配器。它负责从目标系统(比如 MySQL、Redis、Linux 内核)里采集原始数据,然后转换成 Prometheus 能理解的指标格式,通过 HTTP 端点暴露出来。
Exporter 的核心职责就两件事:
- 采集:调用目标系统的 API、读取文件、执行命令,拿到原始数据。
- 转换:把原始数据映射成 Prometheus 的指标类型(Counter、Gauge、Histogram、Summary)。
我举个例子,你写一个自定义 Exporter 监控某个中间件的连接数:
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
// 定义一个 Gauge 指标,用于记录当前连接数
connections = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "myapp_connections_total",
Help: "Current number of active connections",
})
)
func collectMetrics() {
// 模拟从中间件 API 获取连接数
connCount := getConnCountFromMiddleware()
connections.Set(float64(connCount))
}
func main() {
prometheus.MustRegister(connections)
// 启动一个 goroutine 定期采集
go func() {
for {
collectMetrics()
time.Sleep(15 * time.Second)
}
}()
// 暴露 /metrics 端点
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
你看,这个 Exporter 只做一件事:每 15 秒去中间件拿一次连接数,然后更新到 Prometheus 的 Gauge 指标里。业务代码完全不用改。
3.3 指标格式规范:OpenMetrics
最后,数据采集回来了,用什么格式传输?
早期大家各玩各的,Prometheus 自己搞了一套文本格式。后来社区觉得这样不行,得统一标准。于是 OpenMetrics 诞生了。
OpenMetrics 是 CNCF 的孵化项目,它定义了指标数据的标准格式。说白了,就是大家商量好:指标长什么样、怎么描述、怎么换行。
一个标准的 OpenMetrics 指标长这样:
# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",code="200"} 1027 1598970495000
http_requests_total{method="POST",code="500"} 3 1598970495000
我来拆解一下:
- # HELP:指标的说明文字。告诉运维人员这个指标是干嘛的。
- # TYPE:指标类型。counter、gauge、histogram、summary 四种之一。
- 指标行:指标名 + 标签(label)+ 值 + 可选的时间戳。
几个关键规范:
| 规范项 | 说明 |
|---|---|
| 指标名 | 只能包含字母、数字、下划线、冒号。不能以数字开头。 |
| 标签 | 键值对,用花括号包裹。标签名只能包含字母、数字、下划线。 |
| 时间戳 | 可选,Unix 毫秒时间戳。如果不带,由接收端自己打时间戳。 |
| 编码 | 必须用 UTF-8,换行符用 \n。 |
3.4 知识体系总览
为了让你更直观地理解这三者的关系,我画了一张图:
这张图把三个核心概念串起来了。你从左往右看:先决定用 Pull 还是 Push,然后通过 Exporter 把数据翻译成标准格式,最后用 OpenMetrics 规范传输。一条链路,清清楚楚。
好了,这一章的内容就到这里。下一章我们会深入指标类型和聚合计算,到时候再聊。
公众号:蓝海资料掘金营,微信deep3321