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 模型的核心流程:
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 的关系:
2.5 小结:这些概念怎么串起来?
Pull 模型决定了 Prometheus 怎么拿数据。时间序列决定了数据怎么存。Metric 类型决定了数据怎么用。Job 和 Instance 决定了数据怎么组织。
这四者缺一不可。我见过不少新手只关注告警规则怎么写,却忽略了底层概念。结果就是——告警配好了,但数据不准;数据准了,但存储爆了。嗯,基础打牢,后面才走得稳。