第四章 指标设计 (Metrics Design)
一、导读
1.1 本章学习目标
理解指标(Metrics)的类型体系:Counter、Gauge、Histogram、Summary
掌握指标设计的核心原则:可操作性、命名规范和维度设计
理解时间序列数据的特征和存储挑战
掌握 RED 方法和 USE 方法的指标设计框架
了解指标聚合的策略:预聚合、实时聚合和下钻分析
掌握 Prometheus 查询语言(PromQL)的核心用法
理解指标的基数(Cardinality)问题及其控制策略
能够为一个完整系统设计合理的指标体系
1.2 为什么本章如此重要
指标是可观测性三大支柱中使用最广泛、最成熟的信号类型。与日志和追踪不同,指标的设计质量直接决定了告警的有效性和仪表盘的价值。一个设计良好的指标体系可以让团队在秒级别发现系统异常,而设计不当的指标则会导致告警疲劳(大量无效告警)或盲区(关键问题无法被发现)。
指标设计的核心挑战在于:如何在有限的指标数量下,最大化对系统行为的描述能力。过多的指标会导致存储成本爆炸和查询性能下降,过少的指标则无法充分描述系统行为。维度的选择尤其关键——每个维度都会与指标值形成笛卡尔积,导致时间序列数量指数级增长。
本章将从指标的类型体系出发,深入指标设计的原则和方法论,介绍 RED 和 USE 两种经典的指标设计框架,并讨论指标基数控制和 PromQL 查询等实践话题。掌握指标设计的能力,是构建高效可观测性体系的关键。
1.3 核心问题引导
Counter、Gauge、Histogram 和 Summary 各适用于什么场景?
什么是指标的基数(Cardinality)?为什么高基数是问题?
RED 方法和 USE 方法分别适用于什么场景?如何应用?
如何设计指标的维度(Labels/Tags)?应该避免哪些常见错误?
PromQL 中 rate()、histogram_quantile() 和 increase() 的含义和使用场景是什么?
如何在指标的完整性和存储成本之间找到平衡?
什么是预聚合(Pre-aggregation)?它解决了什么问题?
二、核心概念详解
2.1 指标类型体系
可观测性中的指标分为四种基本类型,每种类型适用于不同的度量场景。
Counter(计数器)是一个只增不减的累积值。适用于记录事件的总次数:请求总数、错误总数、处理的消息总数等。Counter 的价值在于其变化率(rate)——通过计算单位时间内的增量,可以得到每秒请求数(QPS)、每秒错误数等关键指标。Counter 永远不会减少,即使应用重启也会从零开始重新累积(Prometheus 会自动处理重启导致的 Counter 重置)。
Gauge(仪表盘)是一个可以增减的数值。适用于记录当前的状态值:当前活跃连接数、队列深度、内存使用量、CPU 温度等。Gauge 的价值在于其当前值和变化趋势。通过监控 Gauge 的变化趋势,可以发现资源泄漏(如内存持续增长)和容量瓶颈(如连接数接近上限)。
Histogram(直方图)用于记录观测值的分布情况。它将观测值分配到预定义的桶(Bucket)中,记录每个桶中的计数和所有观测值的总和。适用于延迟、响应大小等需要了解分布的特征。例如,HTTP 请求延迟的 Histogram 可以告诉你"95% 的请求在 200ms 内完成"。Histogram 的桶边界需要在创建时定义,选择合适的桶边界对于准确描述分布至关重要。
Summary(摘要)与 Histogram 类似,用于记录观测值的分布,但分位数是在客户端计算的。Summary 直接提供 P50、P95、P99 等分位数值,不需要像 Histogram 那样在查询时计算。缺点是分位数不能在服务端聚合(多个实例的 P99 不能简单平均得到全局 P99),而 Histogram 可以。
2.2 指标设计的 RED 与 USE 方法
RED 方法(Request, Error, Duration)面向服务级别的指标设计,适用于请求驱动型系统(如 Web 服务、API 服务)。RED 方法定义了三个核心指标:Request(每秒请求数,反映系统负载)、Error(每秒错误请求数,反映系统正确性)和 Duration(每个请求的延迟分布,反映系统性能)。通过这三个指标,可以全面描述一个服务的行为:负载有多高、有多少请求失败、响应有多快。
USE 方法(Utilization, Saturation, Errors)面向资源级别的指标设计,适用于基础设施和中间件的监控。USE 方法定义了三个核心指标:Utilization(资源的使用率,如 CPU 使用率、内存使用率、磁盘 I/O 使用率)、Saturation(资源的排队程度,如 CPU 运行队列长度、磁盘 I/O 等待队列长度)和 Errors(资源的错误数,如网络丢包数、磁盘读写错误数)。USE 方法帮助识别资源瓶颈——当 Utilization 接近 100% 或 Saturation 持续增长时,说明该资源可能成为瓶颈。
在实际应用中,RED 和 USE 方法应该结合使用。RED 方法描述服务的行为(从用户视角),USE 方法描述基础设施的状态(从运维视角)。当 RED 指标显示服务性能下降时,通过 USE 指标可以定位是哪个资源(CPU、内存、磁盘、网络)导致了问题。
2.3 指标维度(Labels/Tags)设计
指标的维度(在 Prometheus 中称为 Labels,在其他系统中称为 Tags)是指标值的修饰符,用于区分同一指标的不同实例。例如,http_requests_total{method="GET", status="200"} 和 http_requests_total{method="POST", status="500"} 是同一指标的不同时间序列,通过 method 和 status 维度区分。
维度设计的核心原则是:每个维度都应该有明确的查询和告警用途。避免添加"可能有用的"维度——每个维度都会与指标值形成笛卡尔积,导致时间序列数量指数级增长。例如,如果有 10 个 method、50 个 status、100 个 path 和 10 个 instance,一个指标就会产生 10 x 50 x 100 x 10 = 500,000 条时间序列。
维度设计的常见错误包括:使用动态值作为维度(如 user_id、order_id、ip_address),这会导致时间序列数量无限增长;使用冗余维度(如 service_name 和 service_id 同时存在);维度命名不一致(如不同服务使用不同的维度名称表示相同含义)。
推荐的维度设计实践:使用枚举值作为维度(如 HTTP method、status code 的范围分组);对高基数维度进行降基数处理(如将具体的 URL path 分组为 route pattern);建立统一的维度命名规范(如所有服务使用 service_name 而非 app 或 service)。
2.4 PromQL 查询语言
PromQL 是 Prometheus 的查询语言,用于对时间序列数据进行查询、聚合和计算。掌握 PromQL 是有效使用 Prometheus 生态的基础。
基础查询包括即时查询(返回某个时间点的值)和范围查询(返回一段时间内的值序列)。http_requests_total 返回指标的最新值,http_requests_total[5m] 返回过去 5 分钟的所有数据点。
rate() 函数是最常用的 PromQL 函数,用于计算 Counter 类型指标的变化率。rate(http_requests_total[5m]) 返回过去 5 分钟内每秒的平均请求数。rate() 会自动处理 Counter 重置(应用重启导致的归零)。irate() 类似 rate(),但只使用最后两个数据点计算瞬时变化率,对短期波动更敏感。
histogram_quantile() 函数用于从 Histogram 数据计算分位数。histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) 返回过去 5 分钟内 P99 的请求延迟。这个函数需要 Histogram 的桶数据作为输入。
聚合操作包括 sum(求和)、avg(平均)、min/max(最小/最大值)、count(计数)和 topk(前 K 个)。聚合可以按维度分组:sum by (service) (rate(http_requests_total[5m])) 按服务分组计算每秒请求数。
2.5 指标基数控制
指标基数(Cardinality)是指一个指标产生的不同时间序列的数量。高基数是 Prometheus 等指标系统面临的主要挑战——每个时间序列都需要独立的存储空间和计算资源。当基数过高时,Prometheus 的内存使用会激增,查询性能会显著下降,甚至导致 Prometheus 崩溃。
控制指标基数的策略包括多个层面。在应用层面:避免使用高基数维度(如 user_id、email、ip_address);对 URL path 进行归一化(将 /api/users/12345 转换为 /api/users/:id);限制维度的取值范围(如 HTTP status code 只记录 2xx/3xx/4xx/5xx 分组而非具体值)。
在采集层面:使用 Metrics Relabel 规则在采集时丢弃或修改不需要的维度;通过 relabel 将高基数维度转换为低基数维度。在存储层面:使用 Recording Rules 预计算常用的聚合查询,减少查询时的计算量;使用 VictoriaMetrics 或 Thanos 等支持高基数的替代存储方案。
监控指标基数本身也很重要。Prometheus 提供了 prometheus_tsdb_head_series 指标,记录当前活跃的时间序列数量。通过监控这个指标的增长趋势,可以及时发现基数异常增长的问题。
三、深入分析与讨论
3.1 Histogram 桶边界的设计
Histogram 的桶边界设计直接影响分位数计算的准确性。桶边界设置不当会导致两种问题:桶太少或范围太窄,无法准确描述分布;桶太多,增加存储和计算开销。
设计桶边界的最佳实践是:首先了解指标的预期范围(如 HTTP 延迟通常在 1ms 到 10s 之间);使用指数增长的桶边界(如 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10);确保你关心的分位数附近有足够密集的桶边界(如关心 P99 是否在 200ms 以内,需要在 100ms、150ms、200ms、250ms 附近设置桶边界)。
OpenTelemetry 引入了 Exponential Histogram(指数直方图)的概念,使用指数增长的桶边界,可以自动适应不同范围的观测值,无需手动设置桶边界。这种方案在保持准确性的同时大幅简化了配置。
3.2 指标的预聚合与降采样
随着时间推移,指标数据的粒度可以逐渐降低。最近的數據(如过去 24 小时)保持原始粒度(如 15 秒间隔),历史数据可以降采样到更粗的粒度(如 1 分钟、5 分钟、1 小时间隔)。这种策略在保证近期数据精度的同时,大幅降低长期存储的成本。
Prometheus 的 Recording Rules 是一种预聚合机制。通过 Recording Rules,可以预先计算常用的聚合查询(如按服务分组的请求率和错误率),将结果存储为新的时间序列。这样在查询时直接使用预计算的结果,而不需要实时聚合原始数据,显著提升查询性能。
Thanos 和 VictoriaMetrics 等长期存储方案支持自动降采样:原始数据保留 2 周,5 分钟粒度数据保留 3 个月,1 小时粒度数据永久保留。这种分层存储策略在存储成本和查询性能之间取得了良好的平衡。
3.3 从指标到告警的设计原则
指标的最终价值在于驱动告警和决策。设计指标时应该同时考虑告警规则——如果一个指标无法产生有意义的告警,它可能不需要被采集。
基于 SLO 的告警设计是当前推荐的最佳实践。传统的基于阈值的告警(如 CPU > 80%)存在两个问题:阈值难以确定(80% 是否真的有问题?)和告警疲劳(大量阈值告警导致团队麻木)。基于 SLO 的告警通过 Error Budget(错误预算)来驱动告警:当错误预算消耗速度超过预期时发出告警。这种方式直接关联到用户体验,减少了无效告警。
告警的设计应该遵循"可操作"原则——每条告警都应该有明确的响应动作。如果收到告警后不知道该做什么,这条告警就是无效的。建议为每条告警编写 Runbook,描述告警的含义、可能的原因和处理步骤。
四、实践案例
4.1 API 服务的指标体系设计
某 API 网关服务使用 RED 方法设计了完整的指标体系。核心指标包括:http_requests_total{method, route, status_group}(请求总数,Counter)、http_request_duration_seconds{method, route}(请求延迟,Histogram,桶边界:0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10)、http_request_size_bytes{method, route}(请求大小,Histogram)、http_response_size_bytes{method, route}(响应大小,Histogram)。
维度设计遵循低基数原则:route 使用路由模式(如 /api/users/:id)而非具体路径;status_group 使用分组(2xx, 3xx, 4xx, 5xx)而非具体状态码。通过 Recording Rules 预计算了按服务分组的 QPS、错误率和 P99 延迟,供仪表盘和告警使用。基于 SLO 的告警规则监控 30 天窗口内的错误预算消耗,当消耗超过 50% 时发出 Warning,超过 80% 时发出 Critical。
4.2 基础设施的 USE 指标监控
某运维团队使用 USE 方法为 Kubernetes 集群的基础设施设计了监控指标体系。CPU 维度:Utilization 通过 node_cpu_seconds_total 计算使用率,Saturation 通过 node_cpu_running 和 node_cpu_queued 监控运行队列和等待队列。内存维度:Utilization 通过 node_memory_MemAvailable_bytes 计算使用率,Saturation 通过 node_vmstat_pgmajfault 监控缺页中断。磁盘维度:Utilization 通过 node_disk_io_time_seconds_total 计算使用率,Saturation 通过 node_disk_io_queued 监控 IO 队列。网络维度:Utilization 通过 node_network_receive_bytes_total 计算带宽使用率,Errors 通过 node_network_receive_errs_total 监控错误包。
通过 Grafana 仪表盘展示所有节点的 USE 指标,配合热力图快速识别异常节点。当某个节点的 CPU Saturation 持续升高时,自动触发告警并关联到该节点上的 Pod 列表,帮助快速定位资源争抢问题。
五、常见误区与注意事项
误区:指标维度越丰富越好。每个维度都会导致时间序列数量的乘法增长。一个有 5 个维度、每个维度 10 个取值的指标会产生 100,000 条时间序列。应该只添加有明确查询和告警用途的维度。
误区:Histogram 的桶边界可以随意设置。桶边界直接影响分位数计算的准确性。桶边界太稀疏会导致分位数计算不准确,桶边界太密会增加存储开销。应该基于指标的预期范围和关注的分位数来设计桶边界。
注意事项:Counter 的重置处理。当应用重启时,Counter 会归零。Prometheus 的 rate() 函数会自动处理重置,但如果直接使用 Counter 的原始值(而非 rate),会得到错误的结果。始终使用 rate() 或 increase() 来处理 Counter 类型的指标。
注意事项:指标采集的频率。Prometheus 的默认 scrape 间隔是 15 秒。对于高频变化的指标(如每秒数万次请求),15 秒的间隔可能丢失精度。可以适当缩短 scrape 间隔(如 5 秒),但需要注意对 Prometheus 性能的影响。
注意事项:指标命名的规范性。遵循统一的命名规范有助于跨服务的指标聚合和查询。推荐使用 {namespace}_{subsystem}_{name}_{unit} 的命名格式,如 http_requests_total、http_request_duration_seconds。
六、本章小结
本章深入介绍了指标设计的原理和实践。指标分为 Counter、Gauge、Histogram 和 Summary 四种类型,各自适用于不同的度量场景。RED 方法和 USE 方法提供了系统化的指标设计框架:RED 面向服务级别(请求量、错误率、延迟),USE 面向资源级别(利用率、饱和度、错误数)。
维度设计是指标设计中最关键的决策之一,需要在信息丰富度和基数控制之间找到平衡。PromQL 作为 Prometheus 生态的查询语言,提供了强大的数据查询和聚合能力。指标的基数控制是长期运维的关键挑战,需要从应用设计、采集配置和存储策略多个层面综合应对。
指标的最终价值在于驱动告警和决策。基于 SLO 的告警设计比传统阈值告警更有效,因为它直接关联到用户体验和业务影响。
七、思考题与练习
为一个数据库连接池设计指标体系:包含哪些指标?使用什么类型?需要哪些维度?
解释以下 PromQL 表达式的含义:(a) rate(http_requests_total[5m]) (b) histogram_quantile(0.99, rate(http_request_duration_bucket[5m])) (c) sum by (service) (rate(http_requests_total{status_group="5xx"}[5m])) / sum by (service) (rate(http_requests_total[5m]))
分析一个指标的基数问题:http_requests_total{method, path, status, instance, region},其中 method 有 5 个取值,path 有 1000 个取值,status 有 20 个取值,instance 有 100 个取值,region 有 5 个取值。计算总的时间序列数量,并给出降基数方案。
对比 RED 方法和 USE 方法的适用场景,为一个包含 Web 服务、数据库和消息队列的系统设计完整的指标体系。
设计 Histogram 的桶边界:HTTP 请求延迟,SLO 要求 P99 < 200ms,P95 < 100ms。
解释为什么 Summary 的分位数不能在服务端聚合,而 Histogram 可以。给出一个具体例子说明。
设计基于 SLO 的告警规则:API 成功率 SLO 为 99.9%,使用 30 天窗口的 Error Budget。
分析指标预聚合(Recording Rules)的策略:哪些查询应该预聚合?预聚合的粒度如何确定?