12.1 指标工程:基数、直方图与 PromQL
可观测性成本和查询质量,往往在埋点命名与 label 选择时就已经决定。指标不是“以后也许有用”的事件仓库,而是为聚合、告警和容量判断设计的有界数据模型。
一条时间序列由完整标签集决定
http_server_requests_total{
service="checkout",
route="/orders/{id}",
method="GET",
status_code="200"
}metric name 与全部 label value 的唯一组合形成一条 series。粗略估算:
series ≈ services × routes × methods × status codes × instances例如 10 个服务、20 个 route、4 种方法、5 种状态码和 30 个实例,笛卡尔积是 120,000 条 series。若同样的维度用于带 10 个有限边界的 classic histogram,再计 +Inf、_sum 和 _count,就是 13 倍,即 1,560,000 条,尚未计其他元数据。实际组合未必全部出现,但乘法效应足以让一个 label 成为主要成本。user_id、email、原始 URL、trace ID、错误全文和随机任务 ID 都不应进入普通 Prometheus label;它们更适合日志、trace 或分析系统。
使用模板化 route,例如 /orders/{id},不要使用 /orders/817263。状态码若无需逐码分析,可记录 2xx、4xx、5xx;在失去诊断能力和控制基数之间做明确取舍。
Counter 与 Gauge 的查询语义不同
Counter 只递增并可能在进程重启时归零。分析速率时先 rate(),再跨实例聚合:
sum by (service) (
rate(http_server_requests_total[5m])
)rate() 应在 sum() 里面,才能让每条原始 series 分别识别 reset。对告警与平滑趋势通常用 rate();irate() 只看最后两个点,更适合快速变化的交互图,不宜默认用于稳定告警。
Gauge 表示可升可降的瞬时状态,如队列深度和并发请求。对 Gauge 使用 rate() 通常没有业务意义。
延迟需要分布,而不是平均值
平均延迟会把少量极慢请求隐藏起来。Classic histogram 将观测计入累积 bucket:
histogram_quantile(
0.99,
sum by (service, le) (
rate(http_server_request_duration_seconds_bucket[5m])
)
)le 必须保留到 classic histogram 的 histogram_quantile() 输入中。结果是在 bucket 内插值得到的估算,精度受埋点时选择的边界影响。若要直接计算“300 ms 内完成的比例”,应有精确的 le="0.3" bucket,并用它的观测增量除以 _count 的增量;只在附近设边界仍需估算。分位数查询则受整组 bucket 边界影响,查询时不能找回已丢失的分辨率。
Native histogram 把一个 histogram 样本作为一条时间序列处理,聚合查询不再要求 le:
histogram_quantile(
0.99,
sum by (service) (
rate(http_server_request_duration_seconds[5m])
)
)当前 Prometheus 官方在条件允许时优先推荐 native histogram,但是否采用还要核对 Prometheus、远程存储、查询层、客户端库和遥测管线的端到端支持。迁移期不要默认 classic 与 native 数据能被所有下游等价处理。一条 native series 的样本可包含很多 bucket,series 数少也不等于样本大小和计算成本固定。
带分位数的 Summary 在客户端计算预设分位点,不能通过平均各实例的分位数得到整体分位数,也不能事后选择新分位点;它的 _sum 和 _count 仍可聚合以计算平均值。除非明确需要客户端滑动窗口语义,否则服务级延迟通常优先 histogram。
向量匹配要写出关系
计算每个服务错误比例:
(
sum by (service) (
rate(http_server_requests_total{status_code=~"5.."}[5m])
)
or
0 * sum by (service) (
rate(http_server_requests_total[5m])
)
)
/
sum by (service) (
rate(http_server_requests_total[5m])
)这里仍使用数字状态码,因此匹配 5..。or 右侧只为已有请求指标的服务补一个零错误分子,避免从未出现 5xx 时整条比例消失。这假设缺少错误计数器表示未观察到错误;表达式无法区分它与部分遥测丢失,仍需检查埋点契约和采集健康。若请求速率为零,0/0 仍是 NaN;若全部遥测缺失,则不凭空补成健康。此处聚合掉 region/cluster 适合整体视图,需要定位故障域时,两侧都应保留那些标签。
算术二元运算两侧要有可匹配的 label。使用 on()、ignoring()、group_left 或 group_right 前,先写清一对一、一对多还是多对一;意外的多对多匹配通常说明模型或聚合层级有问题。
缺失 series 也要有明确语义。up == 0 表示抓取目标存在但失败,absent(up{job="api"}) 检查匹配序列是否全部不存在,不能发现多个目标中只少了一个;后者需要期望目标清单或其他独立信号。把 no data 自动当作健康,会在采集链路故障时制造绿色面板。
Recording rule 固化常用聚合
高频 dashboard 和告警不应每次扫描全部原始 series:
groups:
- name: atlas-api-recording
interval: 30s
rules:
- record: service:http_server_requests:rate5m
expr: sum by (service) (rate(http_server_requests_total[5m]))
- record: service:http_server_errors:ratio_rate5m
expr: |
(
sum by (service) (rate(http_server_requests_total{status_code=~"5.."}[5m]))
or
0 * sum by (service) (rate(http_server_requests_total[5m]))
)
/
sum by (service) (rate(http_server_requests_total[5m]))规则要有一致命名、单元测试和所有者。预聚合会丢失 label,之后无法恢复,因此只固化稳定的查询层级,不能为了速度删掉未来诊断需要的关键维度。
基数预算是一项接口约束
每个新指标进入生产前应回答:
- 用哪个用户问题或 SLO 驱动?
- label 的有限值集合是什么,最坏基数是多少?
- 每增加一个实例、租户或区域,series 如何增长?
- 保留多久,是否需要降采样或远程存储?
- 哪个 dashboard、alert 或 runbook 消费它?
- 无消费者时,谁负责删除?
还要监控监控系统本身:active series、抓取样本数、规则评估耗时、查询并发、WAL/磁盘、remote write 积压和丢弃样本。可观测性平台失控时,最先消失的恰好是事故证据。
参考资料
Prometheus, Instrumentation
Prometheus, Metric and label naming
Prometheus, Histograms and summaries
Prometheus, Query functions
Prometheus, Recording rules
Prometheus, Vector matching and operators