3. Prometheus核心概念:数据模型与指标类型
聊到Prometheus,很多人第一反应是「哦,一个监控系统」。但说实话,真正用好它,你得先搞懂它的数据模型。这就像学开车,你得先知道油门刹车在哪,对吧?
我刚开始接触Prometheus时,也踩过不少坑。有一次生产环境告警没触发,查了半天,发现是我对指标类型的理解出了问题。嗯,从那以后,我每次讲Prometheus,都会把数据模型和指标类型放在最前面讲。
3.1 数据模型:Metric、Label、Sample
Prometheus的数据模型,说白了就三个核心概念:指标(Metric)、标签(Label)、样本(Sample)。它们组合在一起,构成了Prometheus存储和查询的基础。
3.1.1 Metric(指标)
指标就是你要监控的「东西」。比如CPU使用率、内存占用量、HTTP请求数。每个指标都有一个名字,命名规则很简单:namespace_subsystem_unit_suffix。举个例子:
# 指标名示例
node_cpu_seconds_total
http_requests_total
prometheus_tsdb_head_series
我个人习惯用下划线分隔,清晰明了。你想想看,如果指标名写成nodeCPUSecondsTotal,是不是看着就头疼?
3.1.2 Label(标签)
标签是Prometheus最强大的设计之一。它让你可以对同一个指标进行多维度的细分。每个标签是一个键值对,比如:
http_requests_total{method="GET", endpoint="/api/v1/users", status="200"}
这里method、endpoint、status就是标签。你可以通过标签组合,查询出「GET请求中,/api/v1/users接口,返回200的次数」。这在排查问题时特别有用。
3.1.3 Sample(样本)
样本就是实际的数据点。它包含三个部分:
- 时间戳:精确到毫秒
- 值:float64浮点数
- 指标+标签:标识这个样本属于哪个时间序列
一个样本长这样:
# 时间戳: 值
node_cpu_seconds_total{cpu="0", mode="idle"} 1234567890.123 382345.67
说白了,Prometheus就是不断地收集这些样本,然后存起来。你查询的时候,它把样本按时间序列组织好,返回给你。
3.2 四种指标类型
Prometheus有四种核心指标类型。我刚开始学的时候,觉得Counter和Gauge差不多,后来才发现区别大了去了。
3.2.1 Counter(计数器)
Counter只增不减。它用来记录累计值,比如:
- HTTP请求总数
- CPU时间累计
- 错误发生次数
代码示例:
// Go语言示例
httpRequestsTotal := prometheus.NewCounter(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests.",
})
// 每次请求加1
httpRequestsTotal.Inc()
你可能会问:「那我想看每秒的请求量怎么办?」用rate()函数啊:
rate(http_requests_total[5m])
这个函数会计算过去5分钟内,每秒平均增长量。我在项目中经常用这个来监控流量突增。
3.2.2 Gauge(仪表盘)
Gauge可增可减。它用来记录瞬时值,比如:
- 当前内存使用量
- 当前在线用户数
- 队列长度
代码示例:
memoryUsage := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "memory_usage_bytes",
Help: "Current memory usage in bytes.",
})
// 设置值
memoryUsage.Set(1024 * 1024 * 512) // 512MB
// 增加/减少
memoryUsage.Add(1024 * 1024) // 增加1MB
memoryUsage.Sub(1024 * 1024) // 减少1MB
avg_over_time()函数,可以计算一段时间内的平均值。比如监控CPU使用率,用avg_over_time(node_cpu_usage[5m]),能过滤掉瞬时抖动。
3.2.3 Histogram(直方图)
Histogram用来统计数据的分布情况。它会把数据分到不同的桶(bucket)里。比如HTTP请求延迟:
# 定义桶:0.1s, 0.5s, 1s, 2s, 5s
http_request_duration_seconds_bucket{le="0.1"} 1024
http_request_duration_seconds_bucket{le="0.5"} 2048
http_request_duration_seconds_bucket{le="1"} 3072
http_request_duration_seconds_bucket{le="2"} 3584
http_request_duration_seconds_bucket{le="5"} 4096
http_request_duration_seconds_bucket{le="+Inf"} 4096
你看,le="0.1"表示延迟小于等于0.1秒的请求有1024个。通过这个,你可以算出P50、P99等分位数。
代码示例:
requestDuration := prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds.",
Buckets: prometheus.DefBuckets, // 默认桶:[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
})
// 记录一个请求耗时0.3秒
requestDuration.Observe(0.3)
3.2.4 Summary(摘要)
Summary和Histogram有点像,但它直接计算分位数,不需要你事后算。不过它有个缺点:分位数是客户端算的,你没法在查询时动态调整。
requestDuration := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds.",
Objectives: map[float64]float64{
0.5: 0.05, // P50,允许误差5%
0.9: 0.01, // P90,允许误差1%
0.99: 0.001, // P99,允许误差0.1%
},
})
requestDuration.Observe(0.3)
我个人习惯:需要精确分位数用Histogram,需要低延迟查询用Summary。Histogram更灵活,但查询时计算分位数会消耗更多资源。
3.3 Pull模型原理
Prometheus采用Pull模型,而不是Push。什么意思呢?就是Prometheus主动去拉取数据,而不是等应用把数据推过来。
流程是这样的:
- 应用暴露一个HTTP端点,比如
/metrics - Prometheus定期(默认15秒)去这个端点拉数据
- 拉回来的数据经过解析、存储,就可以查询了
为什么用Pull而不是Push?我总结了几点:
- 更容易控制采集频率:Prometheus自己决定什么时候拉,不会因为应用推得太快而 overload
- 更容易发现故障:如果应用挂了,Prometheus拉不到数据,立刻就能发现
- 更容易做安全控制:只需要在Prometheus端配置认证,不需要每个应用都配
当然,Pull模型也有缺点。比如应用在防火墙后面,Prometheus可能拉不到。这时候可以用Pushgateway做中转。
核心逻辑图:下面这张SVG图展示了Prometheus的数据流转过程。
这张图很直观:Prometheus Server主动去拉各个应用的/metrics端点,数据存到TSDB,然后通过PromQL查询。整个过程就是「拉-存-查」三步走。
好了,Prometheus的数据模型和指标类型就讲到这里。记住一句话:Metric是名字,Label是维度,Sample是数据。搞懂这三者的关系,Prometheus就算入门了。
公众号:蓝海资料掘金营,微信deep3321