2. Prometheus 核心概念:Pull模型、时间序列、Metric类型、Job与Instance

聊到 Prometheus,很多人第一反应是「又一个监控系统」。但真正上手后你会发现,它的设计哲学跟 Zabbix、Nagios 那套完全不一样。我最早接触 Prometheus 时也踩过不少坑,今天就把这些核心概念掰开揉碎讲清楚。

2.1 Pull 模型:为什么 Prometheus 不推数据?

传统监控系统大多是 Agent 主动推数据到服务端。Prometheus 反其道而行——它主动去拉(Pull)。

核心逻辑: Prometheus Server 定期访问目标端点(/metrics),抓取指标数据。

你可能会问:推和拉有什么区别?我举个例子。有一次线上服务突发高负载,如果是推模式,Agent 推数据本身就会消耗 CPU 和网络。而 Pull 模式下,服务端可以控制抓取频率,负载高时甚至可以暂停抓取。说白了,Pull 模式把控制权交给了监控系统本身。

Pull 模型的几个关键优势:

  • 健康检查天然自带——抓不到数据说明目标挂了
  • 避免雪崩——目标端不需要关心监控系统的负载
  • 配置即发现——改配置文件就能增减监控目标

注意: Pull 模式不适合短生命周期任务(如批处理作业)。这类场景需要用 Pushgateway 做中转,后面章节会细讲。

下面这张图展示了 Pull 模型的核心流程:

Prometheus Pull 模型架构 Prometheus Server 定期发起 HTTP GET Pull 目标 A (Job: api-server) /metrics 端点 目标 B (Job: db-node) /metrics 端点 目标 C (Job: batch-job) 已停止 → 抓取失败 关键点 • 服务端主动发起请求 • 目标只需暴露 /metrics • 抓取失败 = 目标异常 💡 短任务用 Pushgateway 避免 Pull 不到数据

2.2 时间序列:监控数据的灵魂

Prometheus 存储的所有数据都是时间序列。什么是时间序列?说白了就是「某个指标在时间轴上的连续快照」。

一个时间序列由三部分组成:

  • 指标名(Metric Name):如 http_requests_total
  • 标签(Labels):键值对,如 method="GET"、status="200"
  • 样本(Sample):时间戳 + 值

核心公式: 时间序列 = 指标名 + 标签组合 → 唯一标识

举个例子:

http_requests_total{method="GET", status="200"} 1024 @ 1700000000
http_requests_total{method="GET", status="200"} 1056 @ 1700000015
http_requests_total{method="POST", status="201"} 233 @ 1700000000

这三行数据代表三个不同的时间序列。标签组合不同,就是不同的序列。我在项目中见过有人给每个请求路径都加一个标签,结果时间序列数量爆炸,直接把存储打满。嗯,这里要注意——标签的基数(Cardinality)是 Prometheus 性能的关键瓶颈。

经验之谈: 标签值的高基数组合(如 user_id、request_id)千万不要直接作为标签。我见过一个团队把 session_id 当标签,Prometheus 直接 OOM。建议用日志系统处理这类数据。

2.3 Metric 类型:四种武器

Prometheus 提供了四种指标类型。每种都有它的适用场景,用错了会出大问题。

类型 特点 典型场景 注意事项
Counter 只增不减(重启归零) 请求总数、错误总数 不能用于当前值
Gauge 可增可减 CPU使用率、内存占用 小心瞬时抖动
Histogram 分桶统计 + 总和 + 计数 请求延迟分布 桶范围要合理
Summary 分位数计算(客户端侧) 延迟的 p99/p95 聚合困难

2.3.1 Counter:计数器

Counter 只能增加,不能减少。重启后归零重新计数。你想想看,什么场景下数据只增不减?请求总数、错误次数、写入字节数——这些都是典型的 Counter。

我习惯用 rate() 或 increase() 函数处理 Counter 数据:

# 每秒请求增长率
rate(http_requests_total[5m])

# 过去1小时的总增长
increase(http_requests_total[1h])

避坑指南: 我曾经看到有人直接用 Counter 的原始值画图,结果服务重启后曲线断崖式下跌。记住:Counter 必须配合 rate() 或 increase() 使用,否则毫无意义。

2.3.2 Gauge:仪表盘

Gauge 可增可减,反映当前状态。比如内存使用量、连接数、队列长度。说白了,Gauge 就是「快照值」。

Gauge 不需要 rate(),直接用原始值:

# 当前内存使用率
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100

但 Gauge 有个问题——瞬时抖动。比如 CPU 使用率在 1 秒内从 10% 跳到 90% 又跳回来。我建议对 Gauge 做适当的降噪处理,比如用 avg_over_time() 取一段时间均值。

2.3.3 Histogram:直方图

Histogram 是 Prometheus 里最强大的类型,也是最容易用错的。它把数据分到不同的桶(Bucket)里,同时记录总和和计数。

举个例子,监控 API 延迟:

# HELP http_request_duration_seconds 请求延迟分布
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 120
http_request_duration_seconds_bucket{le="0.5"} 450
http_request_duration_seconds_bucket{le="1.0"} 680
http_request_duration_seconds_bucket{le="+Inf"} 1000
http_request_duration_seconds_sum 320.5
http_request_duration_seconds_count 1000

这里 le="0.1" 表示延迟 ≤0.1 秒的请求有 120 个。通过 histogram_quantile() 可以计算分位数:

# 计算 p99 延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

我的建议: 桶的范围要覆盖业务特征。比如你的 API 大部分在 100ms 以内,但偶尔有 2s 的慢请求。桶可以设为 [0.01, 0.05, 0.1, 0.5, 1, 2, 5, +Inf]。桶太少精度不够,桶太多浪费存储。

2.3.4 Summary:预计算分位数

Summary 和 Histogram 很像,但分位数在客户端侧预先算好。这意味着你拿到的就是 p99、p95 等值,不需要再用 histogram_quantile() 计算。

# HELP rpc_duration_seconds 请求延迟摘要
# TYPE rpc_duration_seconds summary
rpc_duration_seconds{quantile="0.5"} 0.052
rpc_duration_seconds{quantile="0.9"} 0.120
rpc_duration_seconds{quantile="0.99"} 0.350
rpc_duration_seconds_sum 320.5
rpc_duration_seconds_count 1000

Summary 的优点是查询简单,缺点是无法跨实例聚合。比如你有 10 个实例,每个实例的 p99 取平均值不等于全局 p99。所以,如果你需要全局分位数,用 Histogram;如果只看单实例,Summary 更方便。

2.4 Job 与 Instance:监控目标的组织方式

Prometheus 用 Job 和 Instance 来组织监控目标。这两个概念很简单,但很多人搞混。

  • Job:逻辑分组,代表一类服务。比如 api-server、db-node。
  • Instance:具体实例,代表一个目标端点。比如 10.0.0.1:9090。

配置文件中这样定义:

scrape_configs:
  - job_name: 'api-server'
    static_configs:
      - targets: ['10.0.0.1:9090', '10.0.0.2:9090']
        labels:
          env: 'production'
  - job_name: 'db-node'
    static_configs:
      - targets: ['10.0.0.3:9100']

抓取后,Prometheus 会自动添加两个标签:

  • job="api-server"
  • instance="10.0.0.1:9090"

关键点: Job 和 Instance 是 Prometheus 内置标签,所有时间序列都会携带。你可以用它们做过滤和聚合。

我习惯用 Job 做粗粒度分组,用 Instance 做细粒度定位。比如告警规则里写 job="api-server" 表示整个服务挂了,而 instance="10.0.0.1:9090" 表示某个节点异常。

下面这张图展示了 Job 和 Instance 的关系:

Job 与 Instance 层级关系 Job: api-server Instance: 10.0.0.1:9090 Instance: 10.0.0.2:9090 Job: db-node Instance: 10.0.0.3:9100 说明 • Job 是逻辑分组 • Instance 是具体端点 • 一个 Job 可包含 多个 Instance

2.5 小结:这些概念怎么串起来?

Pull 模型决定了 Prometheus 怎么拿数据。时间序列决定了数据怎么存。Metric 类型决定了数据怎么用。Job 和 Instance 决定了数据怎么组织。

这四者缺一不可。我见过不少新手只关注告警规则怎么写,却忽略了底层概念。结果就是——告警配好了,但数据不准;数据准了,但存储爆了。嗯,基础打牢,后面才走得稳。