3. Prometheus 核心概念:数据模型与指标类型

好,咱们今天聊聊 Prometheus 最核心的东西——数据模型和指标类型。说实话,我见过不少新手,一上来就急着配告警、搭 Grafana 面板,结果数据采集上来发现根本没法用。为什么?因为没搞懂 Prometheus 到底是怎么存数据的。

我个人习惯,学任何监控系统,先看它的数据模型。数据模型决定了你能查什么、怎么查、查多快。Prometheus 的数据模型,说白了就是「带标签的时间序列」。嗯,就这么简单,但背后门道不少。

3.1 Metric、Label、Sample——数据三要素

Prometheus 里一条数据,由三部分组成:指标名(Metric)标签(Label)样本(Sample)。我习惯把它们比作「谁、在哪、什么值」。

  • Metric:指标名,比如 http_requests_total,表示 HTTP 请求总数。命名有规范,通常用 应用_模块_单位 的格式。
  • Label:标签,键值对。比如 method="GET"status="200"。标签是 Prometheus 的灵魂——没有标签,你只能看个总数;有了标签,你可以按任意维度切分。
  • Sample:样本,包含时间戳和值。比如 @1620000000 1024,表示在某个时间点,这个指标的值是 1024。

重要:Prometheus 里,metric + label 的组合唯一确定一条时间序列。同一个指标名,不同标签值,就是不同的序列。

举个例子:

http_requests_total{method="GET", status="200"} 1024
http_requests_total{method="POST", status="200"} 512
http_requests_total{method="GET", status="404"} 23

这三条数据,指标名都是 http_requests_total,但标签不同,所以是三条独立的时间序列。你想想看,如果不用标签,你得定义三个不同的指标名——http_requests_get_200http_requests_post_200……那得多乱?

我的经验:标签设计是门学问。我在项目中遇到过,有人把用户 ID 直接当标签,结果一个用户一个序列,Prometheus 直接撑爆了。记住:标签的基数不能太高。一般建议标签取值不超过 1000 种,否则性能会急剧下降。

3.2 四种指标类型——选对工具干对活

Prometheus 提供了四种指标类型。很多人觉得「反正都是数字,用哪个不一样?」——还真不一样。每种类型背后,Prometheus 的存储和查询方式都有区别。

3.2.1 Counter——只增不减的计数器

Counter 只能增加,不能减少。重启后归零。适合统计「发生了多少次」——比如请求数、错误数、任务完成数。

为什么设计成只增不减?因为 Prometheus 的 rate() 函数需要基于单调递增的数据来计算每秒速率。如果数据忽高忽低,速率就没法算了。

# 统计 HTTP 请求总数
http_requests_total{method="GET"} 1024

# 查询每秒请求速率
rate(http_requests_total[5m])

避坑指南:我曾经犯过一个错——用 Counter 统计当前在线人数。结果发现人数下降时,Counter 不会减少,数据完全不对。后来才意识到,在线人数应该用 Gauge。

3.2.2 Gauge——可增可减的仪表盘

Gauge 可以任意增减,反映当前状态。适合内存使用率、CPU 温度、队列长度、在线人数等。

Gauge 没有「速率」的概念,你直接看值就行。常用函数是 avg()max()min()

# 当前内存使用率
node_memory_used_percent 67.5

# 查询过去 1 小时的平均内存使用率
avg_over_time(node_memory_used_percent[1h])

嗯,这里要注意:Gauge 的值可能抖动得很厉害。我建议配合 avg_over_timemax_over_time 使用,别直接拿原始值做告警,否则容易误报。

3.2.3 Histogram——分桶统计的直方图

Histogram 是 Prometheus 里最复杂、也最强大的指标类型。它把数据分到不同的桶(bucket)里,统计每个桶里的样本数。

举个例子,你想统计 API 响应时间。用 Histogram 可以定义桶:0.1s0.5s1s2s5s+Inf。Prometheus 会自动统计每个桶里的请求数。

# 响应时间分布
http_request_duration_seconds_bucket{le="0.1"} 500
http_request_duration_seconds_bucket{le="0.5"} 1200
http_request_duration_seconds_bucket{le="1.0"} 1500
http_request_duration_seconds_bucket{le="+Inf"} 1600

# 总请求数
http_request_duration_seconds_count 1600
# 总耗时
http_request_duration_seconds_sum 320.5

有了这些数据,你可以算 P50、P90、P99:

# P99 响应时间
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

核心要点:Histogram 的桶是客户端算好的,Prometheus 直接存结果。所以查询时性能很好,但桶的精度有限——你只能知道数据落在哪个桶里,不知道精确值。

3.2.4 Summary——预计算的分位数

Summary 和 Histogram 很像,也是统计分布。但区别在于:Summary 在客户端就计算好了分位数(比如 P50、P90、P99),直接上报给 Prometheus。

# Summary 直接上报分位数
http_request_duration_seconds{quantile="0.5"} 0.2
http_request_duration_seconds{quantile="0.9"} 0.8
http_request_duration_seconds{quantile="0.99"} 1.5
http_request_duration_seconds_count 1600
http_request_duration_seconds_sum 320.5

你想想看,Summary 的好处是查询时不用算,直接拿分位数。但坏处是——分位数是固定的,你没法在查询时动态调整。比如客户端只算了 P50、P90、P99,你想看 P95?对不起,没有。

特性 Histogram Summary
分位数计算位置 服务端(PromQL 计算) 客户端(预计算)
分位数可调整 是,查询时任意指定 否,客户端固定
聚合能力 强,支持跨实例聚合 弱,不能跨实例聚合
存储开销 取决于桶数量 取决于分位数数量

我的建议:大部分场景用 Histogram。灵活性更高,而且 PromQL 的 histogram_quantile 函数很成熟。只有当你对性能要求极高、且分位数固定不变时,才考虑 Summary。

3.3 作业与实例——Prometheus 的发现机制

聊完数据怎么存,咱们看看数据从哪来。Prometheus 通过 作业(Job)实例(Instance) 来管理采集目标。

  • Job:一组同类任务的逻辑分组。比如 node_exporternginx_exporter。一个 Job 可以包含多个实例。
  • Instance:一个具体的采集端点。比如 192.168.1.1:9100192.168.1.2:9100

Prometheus 会自动给每个采集到的数据加上两个标签:jobinstance。比如:

node_cpu_seconds_total{job="node_exporter", instance="192.168.1.1:9100"}

这样,你就能按作业或实例来过滤数据了。我个人习惯,在配置 Prometheus 时,job_name 一定要起得有意义。别用 job1job2 这种名字,三个月后你自己都看不懂。

避坑指南:我曾经在配置里把 instance 写成了 IP 加端口,结果后来机器迁移,IP 变了,所有历史数据都关联不上了。后来我改用主机名作为 instance 标签,再配合 DNS 解析,才解决了这个问题。

Prometheus 支持多种服务发现方式——静态配置、Consul、Kubernetes、DNS 等。生产环境里,我强烈建议用服务发现,别手动写 IP 列表。手动维护 100 台机器的 IP?那简直是噩梦。

# 静态配置示例
scrape_configs:
  - job_name: 'node_exporter'
    static_configs:
      - targets: ['192.168.1.1:9100', '192.168.1.2:9100']

# 基于 Consul 的服务发现
  - job_name: 'consul_services'
    consul_sd_configs:
      - server: 'localhost:8500'
    relabel_configs:
      - source_labels: [__meta_consul_service]
        target_label: job

嗯,说到 relabel_configs,这是 Prometheus 里最灵活、也最容易出错的地方。我后面会专门讲,今天先记住:relabel 可以让你在采集前修改标签,实现动态的采集配置

好了,这一章的内容就这些。数据模型是 Prometheus 的基石,搞懂了它,后面的查询、告警、Grafana 面板都会顺手很多。下一章咱们聊聊 PromQL——怎么把这些数据查出来、算明白。