指标与 PromQL:四大类型、查询语言与告警路由
基于 Prometheus 3.x · 核于 2026-08
速查
- 数据模型:时序 = metric name + labels(key=value 集合) + (timestamp, float64)。labels 决定唯一性——同名不同 label 是不同时序,所有聚合按 labels 切片。
- 四大类型:①Counter 单调递增(请求/错误数),必须
rate/increase;②Gauge 可增可减(CPU/内存/温度),直接读或*_over_time;③Histogram 分桶累计(_bucket{le="0.1"}),服务端聚合算分位数;④Summary 客户端预算分位数(_quantile),不可聚合,少用。 - Histogram vs Summary:算延迟分布都能算,但 Histogram 在服务端聚合(可跨实例合并 P99),Summary 在客户端预算(不能跨实例合并)——生产几乎只用 Histogram。
- PromQL 三类查询:①即时向量(无
[],当前最新值);②范围向量([5m],一段时间序列,要套rate/avg_over_time才能用);③标量(纯数字,做计算)。 ratevsiratevsincrease:rate(counter[5m])= 窗口内平均速率(告警用,稳);irate(counter[5m])= 最后两点斜率(大屏用,灵);increase(counter[1h])= 窗口内总增量。- 聚合:
sum/avg/max/min/count/count_values/topk/bottomk+by/without分组——等价 SQL 的GROUP BY。先rate后sum(聚合速率),顺序不能反。 - 向量匹配:两向量运算靠 label 对齐——
on(service)仅按这些 label 匹配,ignoring(code)忽略这些 label;group_left一对多匹配(如把实例元信息挂到指标上)。 histogram_quantile:从_bucket算分位数——histogram_quantile(0.99, sum by (le) (rate(http_duration_bucket[5m]))),必须先 sum 再 quantile。- Recording Rule:把昂贵的查询(如 P99)预算成新指标写入 TSDB,面板/告警直接读新指标——加速、降低查询压力、复用。
- Alertmanager 职责:路由(
route.receiver按 label 分流)、分组去重(group_by)、抑制(inhibit_rules:A 触发则屏蔽 B)、静默(手动维护窗口)。
一、数据模型:metric name + labels
Prometheus 的所有数据都是时序(time series),由三要素唯一标识:
metric name: http_requests_total
labels: {method="GET", code="200", service="api"}
samples: [(t1, 1234), (t2, 1235), ...] // (timestamp, float64)- labels 是「身份证」:
http_requests_total{method="GET"}和http_requests_total{method="POST"}是两条不同时序——labels 的不同组合产生新的时序。所有聚合(sum by (method))就是按 label 切片。 - 高基数陷阱:label value 取值空间大的(
user_id、session_id、trace_id)会让时序数爆炸——每多一个 user_id 就多一条时序,几万用户就是几万条时序,吃光 TSDB 内存。绝对不要把高基数字段做 label(这些归日志/追踪)。 __name__:metric name 本质是个特殊 label__name__="http_requests_total",可被relabel改写。
二、四大指标类型详解
理解类型才能选对函数。一个指标被声明为某类型(# TYPE xxx counter),客户端库据此决定暴露方式:
Counter:单调递增
promql
# 错误:counter 一直涨,直接读没意义
http_requests_total # 100 万 → 没信息量
# 正确:算速率
rate(http_requests_total[5m]) # 50 req/s ← 有意义
increase(http_requests_total[1h]) # 过去 1 小时新增 18 万次- 重启归零:进程重启 counter 从 0 开始,
rate客户端会自动处理(检测到下降即视为重启,重置基线)。 - 只能涨:counter 不能减。如果要减(如队列长度、连接数),用 Gauge。
Gauge:瞬时值
promql
node_memory_MemAvailable_bytes # 直接读,当前可用内存 8GB
max_over_time(cpu_temp[1h]) # 过去 1 小时最高温度
deriv(queue_size[10m]) # 队列大小变化趋势- Gauge 可增可减,反映当前状态——CPU%、内存、温度、磁盘剩余、连接数、队列长度都是 Gauge。
Histogram:分桶累计(服务端聚合)
应用按桶边界累计计数,Prometheus 在服务端聚合算分位数:
http_request_duration_seconds_bucket{le="0.1"} 1200 # ≤100ms 累计 1200 次
http_request_duration_seconds_bucket{le="0.5"} 1500 # ≤500ms 累计 1500 次
http_request_duration_seconds_bucket{le="+Inf"} 1600 # 全部 1600 次
http_request_duration_seconds_sum 450 # 总延迟 450 秒
http_request_duration_seconds_count 1600 # 总次数 1600promql
// 算 P99:先 rate 再 sum 再 quantile(顺序关键)
histogram_quantile(0.99,
sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)
// → 0.42s(99% 请求 < 420ms)- 为什么先 sum 后 quantile:分位数不能跨实例直接平均,要先按
le(桶边界)把所有实例的累计计数加起来,再算分位数。反过来会得到错误的「P99 的 P99」。 - 桶设计:默认桶
[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, +Inf]秒。若 SLO 是 P99 < 300ms,务必保证 0.3 这个桶存在(否则算出的 P99 误差大)。用buckets: [0.1, 0.3, 1, +Inf]自定义。
Summary:客户端预算分位数(不推荐)
rpc_duration_seconds{quantile="0.99"} 0.42
rpc_duration_seconds_sum 450
rpc_duration_seconds_count 1600- 致命缺陷:分位数在客户端算好,不能跨实例聚合——两个实例各算 P99,合并后不是总 P99。集群规模稍大就废了。
- 唯一适用场景:单实例监控(如某个守护进程),且不想暴露分桶。生产环境一律用 Histogram。
三、PromQL 查询模式
即时 vs 范围 vs 标量
promql
node_memory_MemAvailable_bytes // 即时向量:每个实例最新一个点
node_memory_MemAvailable_bytes[5m] // 范围向量:过去 5 分钟所有点(不能直接展示)
1024 * 1024 * 8 // 标量:纯数字,做计算- 范围向量必须套函数:
metric[5m]是「一堆点」,直接展示无意义,要套rate/avg_over_time/max_over_time降成即时向量。
rate vs irate vs increase
promql
rate(counter[5m]) // 窗口内平均速率(线性回归),平滑稳定 → 告警用
irate(counter[5m]) // 最后两点斜率,灵敏但抖 → 实时大屏用
increase(counter[1h]) // 窗口内总增量(rate × 窗口时长)- 告警必用 rate:irate 在流量突降时可能误报(最后两点恰好都低),rate 更稳。
- rate 自动处理重启:counter 重启归零时 rate 会检测到下降并重置基线,无需手动处理。
聚合与分组
promql
// 每秒总 QPS(所有实例求和)
sum(rate(http_requests_total[5m]))
// 按接口分组的 QPS
sum by (handler) (rate(http_requests_total[5m]))
// 每个实例的错误数 Top 3
topk(3, sum by (instance) (rate(http_errors_total[5m])))- 顺序关键:
sum(rate(...))是「先算每条时序速率,再求和」(正确);rate(sum(...))是「先把不同时序的累计值加起来,再算速率」(也行但语义不同,且 counter 加和可能错)。聚合速率要先 rate 后 sum。
向量匹配(多指标运算)
算「错误率 = 错误数 / 总数」时,两个向量的 label 要对齐:
promql
// 错误数(带 code=5xx label)和总数(所有 code)按 service 匹配
sum by (service) (rate(http_requests_total{code=~"5.."}[5m]))
/
sum by (service) (rate(http_requests_total[5m]))
// → 两个向量都按 service 分组,自动按 service 对齐做除法- label 不完全相同:用
ignoring(code)忽略某些 label 匹配,或on(service)仅按指定 label 匹配。 - group_left/group_right:一对多匹配,如把「实例元信息」(1 条)挂到「业务指标」(多条)上。
四、Recording Rule 与 Alerting Rule
Recording Rule:预算昂贵查询
P99、错误率这类查询涉及大量时序聚合,每次面板刷新都算很慢。用 Recording Rule 把结果预先算好存成新指标:
yaml
groups:
- name: precompute
rules:
- record: job:http_requests:rate5m # 新指标名(约定 namespace:metric:operations)
expr: sum by (job) (rate(http_requests_total[5m]))
- record: job:http_request_duration_seconds:p99
expr: histogram_quantile(0.99, sum by (le, job) (rate(http_request_duration_seconds_bucket[5m])))- 面板/告警直接读
job:http_requests:rate5m——快、复用、降低查询压力。 - 命名约定:
level:metric:operations(如job:...:rate5m)便于识别。
Alerting Rule:触发条件 + FOR
yaml
groups:
- name: alerts
rules:
- alert: HighErrorRate
expr: |
sum by (service) (rate(http_requests_total{code=~"5.."}[5m]))
/ sum by (service) (rate(http_requests_total[5m])) > 0.01
for: 5m # 持续 5 分钟才 firing
labels:
severity: critical # 带到 Alertmanager 做路由
annotations:
summary: "{{ $labels.service }} 错误率超 1%"
description: "当前错误率 {{ $value | humanizePercentage }}"for: 5m:条件成立后持续 5 分钟才进 firing 状态推送告警,避免瞬时抖动。$labels/$value:模板变量,让告警信息带具体 service、当前值。
五、Alertmanager:路由、分组与抑制
Prometheus 触发告警后推送给 Alertmanager,后者做「最后一公里」的处理:
yaml
route:
group_by: ['alertname', 'service'] # 按告警名+服务分组(同组聚合发一条)
group_wait: 30s # 首次等 30s 再发(攒一批)
group_interval: 5m # 同组下次发送间隔
repeat_interval: 4h # 未解决的告警 4h 重发一次
receiver: default # 默认接收方
routes:
- matchers: ['severity="critical"'] # P0 走 PagerDuty
receiver: pagerduty
- matchers: ['severity="warning"'] # P2 走钉钉
receiver: dingtalk
inhibit_rules: # 抑制:集群宕机时屏蔽单节点告警
- source_matchers: ['alertname="ClusterDown"']
target_matchers: ['alertname="NodeDown"']
equal: ['cluster']
receivers:
- name: dingtalk
webhook_configs: [url: 'https://oapi.dingtalk.com/...']- 分组(group_by):同一 service 的多个告警合并成一条通知,避免告警风暴。
- 抑制(inhibit):定义「A 触发则屏蔽 B」——如集群级故障时屏蔽单节点故障(前者已涵盖)。
- 静默(silence):维护窗口手动创建(matcher + 时长),临时屏蔽特定告警。
下一步
掌握了 PromQL 与告警流水线后,下一步看架构与服务发现——TSDB 如何存储、Service Discovery 如何动态发现目标、relabel 如何改写、远程读写如何扩展、与 Grafana 如何集成。