Skip to content

指标体系

指标是 Prometheus 体系里最基本的数据单元。在配置 Exporter 和写 PromQL 之前,先理清指标本身的结构——指标名、标签、类型和命名习惯。

一个指标长什么样

在 Prometheus 里,一条指标数据(称为"样本")由四个要素组成:

text
node_memory_MemAvailable_bytes{instance="192.168.10.11:9100", job="node"} 6011232256

拆开看:

部分含义
指标名node_memory_MemAvailable_bytes这行数据测量的是"Linux 可用内存字节数"
标签instance="192.168.10.11:9100"来自哪台机器
标签job="node"属于哪组采集任务
6011232256当前可用大约 6GB

同一个指标名加上同一组标签,就是一条"时间序列"——时间轴上每过一个采集间隔会增加一个新样本点。标签里任何一个值变了,就变成另一条序列。比如 instance="192.168.10.11:9100"instance="192.168.10.12:9100" 是两条不同的序列,因为 instance 不同了。Graph 视图里两条序列各自一条线,Table 视图里两条序列各自一行。

标签和基数

标签是 Prometheus 设计里很关键的一环。标签可以按不同维度过滤和聚合:

promql
# 只看 node job 的
up{job="node"}

# 加起来看 node job 一共几个目标
sum(up{job="node"})

但不是所有信息都适合放进标签。标签每多一种取值组合,就多出一条时间序列。如果把用户 ID 放进标签,几万个用户就是几万条序列,Prometheus 的内存、磁盘和查询性能都会被撑爆——这个叫标签基数爆炸。

一个简单的判断方法:这个字段有多少种可能的取值。

适合作为标签不适合作为标签
环境(env:prod/staging/dev)—— 3 种取值用户 ID —— 几万到几百万种取值
服务名(service)—— 几十种取值请求 ID —— 每次请求都不同
实例(instance)—— 几十到几百个订单号 —— 和用户 ID 类似,增长很快
状态码(status:200/404/500)—— 不到 10 种错误堆栈 —— 每次异常内容都不一样

请求 ID 和错误堆栈适合放在日志和链路追踪里。指标上需要关联到某次请求时,可以用 exemplar(在指标数据上附带一个 trace_id),而不是把 trace_id 变成指标标签。

四种指标类型

Prometheus 把指标分为四种类型。区分它们的关键是每种类型用什么函数、能不能直接看当前值。

Counter:计数器,只增不减

Counter 记录"到目前为止一共多少次"。请求总数、错误总数、网卡累计接收字节数、CPU 累计运行秒数——这些都是 Counter。

Counter 有两个特点:只增不减(重启后会归零);绝对值本身意义不大——"网卡一共收了 15TB"不说明当前是否在跑满带宽。所以 Counter 通常不直接看当前值,而是算它的增长速率:

promql
# 看最近 5 分钟每秒增长多少——这就是 Mbps
rate(node_network_receive_bytes_total{job="node"}[5m])

[5m] 的意思是"取最近 5 分钟的所有样本点",rate() 根据这些样本点的增长来算平均每秒速率。因为 Counter 会因进程重启重新从 0 开始计数(术语叫"reset"),rate() 内部会自动处理这种归零——它看到当前值比上一个值小,就知道发生了一次重置,会按实际增量来算而不是直接相减。

Gauge:仪表盘,可增可减

Gauge 是"当前是多少",和温度计一样,可以升可以降。当前内存用量、当前连接数、当前队列长度、磁盘可用空间——这些都是 Gauge。

Gauge 通常可以直接查当前值,也可以算平均值、最大值:

promql
# 直接看当前可用内存
node_memory_MemAvailable_bytes{job="node"}

# 看所有机器的平均 CPU 使用率
avg(node_cpu_usage_percent)

Gauge 没有"归零"的问题,它本来就随时在变。

Histogram:直方图,统计分布

Histogram 用多个"桶"(bucket)来记录数据分布。比如 HTTP 请求耗时,不是只记一个平均值,而是统计:

text
耗时 ≤ 0.1s  有几个请求
耗时 ≤ 0.5s  有几个请求
耗时 ≤ 1s    有几个请求
耗时 ≤ 2s    有几个请求
耗时 ≤ 10s   有几个请求
...直到 Infinity(兜底桶)

从这些桶里可以算出 P50、P90、P95、P99——比平均值有用得多。平均值 200ms 可能是"大部分请求 50ms,偶尔几个请求 5 秒"的结果,平均值把问题抹平了。

Histogram 每个桶本身也是一个 Counter(只增不减),所以也需要用 rate() 来算增长。从 Histogram 算 P95 的典型写法:

promql
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

Summary:在客户端侧算好的分位数

Summary 也在记录分位数,但分位数在客户端侧(被监控的应用自己)算好,直接暴露出来。P95 延迟用 Summary 时直接就是一个指标值。缺点也很明显——两台机器的 Summary 没法重新聚合:两台机器的 P95 取平均不代表整体的 P95。Histogram 没有这个问题,因为桶数据可以跨实例加和再算分位。

Prometheus 生态里 Histogram 用得更多,新接入的指标也优先选 Histogram。

类型特点例子通常怎么用
Counter只增不减,重启归零请求总数、错误数、网卡字节累计rate()increase() 算变化速率
Gauge可增可减当前内存、连接数、磁盘可用直接查当前值,或 avg/max
Histogram分桶记录分布HTTP 耗时、RPC 延迟histogram_quantile() 算分位
Summary客户端侧分位部分 SDK 暴露的分位值直接看,但跨实例无法聚合

RED 和 USE:两个选指标的角度

接入一个新服务时,可以从两个角度来想关注什么指标。

服务对外提供的接口,看 RED:

维度含义例子
Rate每秒请求数QPS 曲线是平稳、突增还是骤降
Errors错误率5xx 比例、业务失败比例
Duration耗时分布P95/P99 有没有变长

机器自身的资源,看 USE:

维度含义例子
Utilization使用率CPU 使用率、磁盘使用率、内存使用率
Saturation饱和度(排队)CPU load、队列长度、等待中的请求数
Errors错误磁盘读写错误、网卡丢包、TCP 重传

RED 回答"服务是不是在好好干活",USE 回答"机器是不是还能撑住"。接口开始超时时,两边一起看:RED 确认范围和趋势(是单个服务还是全局?是突然的还是缓慢变坏的?),USE 确认资源瓶颈(CPU 满了吗?磁盘 IO 饱和了吗?连接数打满了吗?)。

采集间隔和保留周期

采集间隔决定指标的精细程度。间隔越短,尖刺越不会被抹平——一个持续 10 秒的 CPU 高峰用 15 秒间隔抓取能抓到,用 5 分钟间隔可能就漏了。但间隔越短,存储压力越大。

场景常见间隔说明
主机基础指标15s30s资源瓶颈通常不会在几秒内出现又消失
业务接口指标15s30s和主机保持一致便于对比
低频批任务1m本身就几分钟才跑一次
黑盒拨测按接口重要性核心接口可能 10s,低频接口 1min

保留周期决定能回看多久。实验环境 7~15 天就够了。生产环境如果想回看更久(比如做月度报表、年度容量规划),只靠本地 Prometheus 保存几个月会很占磁盘和查询性能——更常见的做法是 Prometheus 本地保留短期的(几天到几周),远期数据交给 VictoriaMetrics、Thanos、Mimir 这类远端存储。

指标命名习惯

Prometheus 生态里指标名有固定习惯:

命名规则例子
单位和含义写进名字里node_memory_MemAvailable_bytes(bytes)
Counter 类以 _total 结尾http_requests_total
Histogram 桶以 _bucket 结尾http_request_duration_seconds_bucket
_seconds 表示时间scrape_duration_seconds

单位写进指标名能让 Grafana 面板自动识别合适的单位格式,也能避免同一条曲线里混着 s 和 ms 导致误读数值。

从指标到告警

不是所有指标都要变成告警。指标可以有一大堆——排查和观察时需要越丰富越好。告警要往回收——只触发需要人介入的情况。

判断一条告警值不值得设,可以问三个问题:异常能不能被指标稳定表达(还是偶尔出现一次就没了)、持续一段时间会不会恶化(还是自动恢复)、需要有人处理吗(还是系统会自动处理)。三条都满足,告警更有可能有用。缺一条,这条告警就可能变成群聊里的噪声。