Skip to content

指标与 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 才能用);③标量(纯数字,做计算)。
  • rate vs irate vs increaserate(counter[5m]) = 窗口内平均速率(告警用,稳);irate(counter[5m]) = 最后两点斜率(大屏用,灵);increase(counter[1h]) = 窗口内总增量。
  • 聚合sum/avg/max/min/count/count_values/topk/bottomk + by/without 分组——等价 SQL 的 GROUP BY。先 ratesum(聚合速率),顺序不能反。
  • 向量匹配:两向量运算靠 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_idsession_idtrace_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             # 总次数 1600
promql
// 算 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 如何集成。