3. 核心数据模型:Metric(指标)的定义、数据类型

在可观测性体系中,Metric(指标) 是描述系统状态随时间变化的数值化度量。与 Log 的离散事件和 Trace 的请求链路不同,Metric 通过聚合统计的方式,以较低的存储成本提供系统健康度、性能趋势和异常检测能力。OpenTelemetry 将 Metric 数据模型标准化,定义了四种基础数据类型,覆盖从单调递增计数到可增减状态值的全部场景。

3.1 Metric 的定义与核心特征

Metric 是对某一可观测对象(如 CPU 使用率、HTTP 请求数、队列深度)在时间维度上的数值采样。其核心特征包括:

  • 时间序列:每个 Metric 由一组带时间戳的数值点构成,按时间顺序排列。
  • 维度(Attributes):通过键值对(如 method="GET"、status="200")对指标进行细分,形成多维数据模型。
  • 聚合语义:指标在采集端或后端进行聚合(如 Sum、Count、Min/Max/Sum),减少数据量并提取统计特征。
  • 低开销:相比 Log 和 Trace,Metric 的采集和传输开销更低,适合高频采样。
关键概念区分: Metric 描述“是什么”(如当前请求数),而 Trace 描述“为什么”(如请求的完整路径)。两者互补,共同构成可观测性的三大支柱。

3.2 四种核心数据类型

OpenTelemetry 定义了四种基础 Metric 数据类型,每种类型对应不同的语义和适用场景。下表总结了它们的核心差异:

数据类型 语义 是否可增减 典型场景 聚合方式
Counter 单调递增计数器 仅增加 请求总数、错误次数、字节发送量 Sum
UpDownCounter 可增减计数器 可增可减 活跃连接数、队列长度、库存量 Sum
Histogram 数值分布统计 仅增加(采样点) 请求延迟、响应大小、批处理耗时 Count、Sum、Min/Max、分位数
Gauge 瞬时值快照 可增可减(瞬时) CPU 使用率、内存占用、温度 LastValue

3.3 数据类型详解

3.3.1 Counter(单调递增计数器)

Counter 是最常用的指标类型,其值只能增加,不能减少。它适用于记录累计总量,如 HTTP 请求总数、数据库查询次数、错误发生次数。Counter 的典型特征包括:

  • 单调性:每次调用 Add() 方法时,值增加一个正数;不支持减法操作。
  • 聚合语义:后端通常对同一时间窗口内的 Counter 值进行 Sum 聚合,得到总累计值。
  • 重置处理:当进程重启时,Counter 值会从 0 重新开始;后端通过速率计算(如 per-second rate)来消除重置影响。
// 伪代码示例:创建并增加 Counter
Counter httpRequests = meter.createCounter("http.requests.total");
httpRequests.add(1, Attributes.of("method", "GET", "status", "200"));

3.3.2 UpDownCounter(可增减计数器)

UpDownCounter 允许值增加或减少,适用于跟踪可以上下波动的累计量。与 Counter 不同,它不要求单调性,但同样基于 Sum 聚合。典型场景包括:

  • 活跃连接数:新连接建立时 +1,连接关闭时 -1。
  • 队列深度:任务入队时 +1,出队时 -1。
  • 并发请求数:请求开始 +1,请求结束 -1。
使用注意: UpDownCounter 不适合记录瞬时值(如 CPU 使用率),因为其聚合方式是 Sum,而非 LastValue。对于瞬时值,应使用 Gauge。
// 伪代码示例:创建并操作 UpDownCounter
UpDownCounter activeConnections = meter.createUpDownCounter("connections.active");
activeConnections.add(1);  // 新连接
activeConnections.add(-1); // 连接关闭

3.3.3 Histogram(直方图)

Histogram 用于记录数值的分布情况,而不是简单的总和或计数。它通过将数值分配到预定义的桶(Bucket)中,统计每个桶内的样本数量,从而提供分位数、平均值、最小值、最大值等统计信息。Histogram 的核心特性:

  • 桶边界:用户可自定义桶边界(如 [0, 10, 50, 100, 500]),或使用默认指数边界。
  • 聚合输出:后端可计算 Count(样本总数)、Sum(数值总和)、Min/Max、以及分位数(如 p50、p95、p99)。
  • 适用场景:请求延迟、响应大小、批处理耗时等需要了解分布特征的指标。
// 伪代码示例:创建 Histogram 并记录值
Histogram requestLatency = meter.createHistogram("http.request.duration");
requestLatency.record(120.5, Attributes.of("method", "POST"));

3.3.4 Gauge(瞬时值)

Gauge 用于记录某一时刻的瞬时值,不进行累加或聚合。它适合表示当前状态,如 CPU 使用率、内存占用、磁盘空间、温度等。Gauge 的关键特征:

  • 瞬时性:每次记录的值代表该时刻的快照,不保留历史累计信息。
  • 聚合方式:后端通常使用 LastValue 聚合,即取最后一个上报的值作为当前状态。
  • 回调模式:OpenTelemetry 支持通过回调函数异步获取 Gauge 值,避免高频轮询。
// 伪代码示例:创建 Gauge 并设置回调
meter.createGauge("system.cpu.usage", () -> getCurrentCpuUsage());

3.4 数据类型选择决策流程

在实际开发中,选择正确的 Metric 类型是保证数据语义准确的关键。以下决策流程可帮助快速判断:

  1. 是否只关心累计总量? → 是 → Counter(仅增加)或 UpDownCounter(可增减)。
  2. 是否需要了解数值分布? → 是 → Histogram(如延迟、大小)。
  3. 是否只关心当前瞬时值? → 是 → Gauge(如 CPU、内存)。
  4. 是否需要在进程重启后保持语义? → Counter 和 Histogram 支持速率计算;Gauge 和 UpDownCounter 需注意重置影响。
最佳实践: 优先使用 Counter 和 Histogram,因为它们语义清晰、聚合效率高。UpDownCounter 和 Gauge 仅在需要跟踪可增减累计量或瞬时状态时使用。

3.5 Metric 数据模型与四种类型的关系

下图展示了 OpenTelemetry Metric 数据模型的核心结构,以及四种数据类型在采集、聚合、导出流程中的位置:

OpenTelemetry Metric 数据模型与四种数据类型 采集层(Instrumentation) Counter UpDownCounter Histogram Gauge 聚合层(Aggregation) Sum Sum Count / Sum / Min/Max LastValue 导出层(Exporter) OTLP / Prometheus / 其他后端 说明: 每种数据类型在采集时使用不同的 API,聚合阶段根据语义选择对应的聚合器,最终通过统一的导出接口发送到后端。

3.6 数据类型对比与选型建议

下表从多个维度对比四种数据类型,帮助开发者在实际场景中快速选型:

维度 Counter UpDownCounter Histogram Gauge
值变化方向 仅增加 可增可减 仅增加(采样点) 可增可减(瞬时)
聚合方式 Sum Sum Count、Sum、Min/Max、分位数 LastValue
典型输出 总请求数、总错误数 活跃连接数、队列深度 p50/p95/p99 延迟、平均大小 CPU 使用率、内存占用
重置处理 支持速率计算 需注意重置影响 支持速率计算 无需处理(瞬时值)
推荐使用频率 高 中 高 中
选型口诀: 累计总量用 Counter,可增可减用 UpDown,分布统计用 Histogram,瞬时快照用 Gauge。

3.7 常见误区与注意事项

  • 误区一:用 Gauge 记录累计值。Gauge 的 LastValue 聚合会丢失历史累计信息,应使用 Counter 或 UpDownCounter。
  • 误区二:用 Counter 记录可增减值。Counter 不支持减法,会导致数据语义错误;应使用 UpDownCounter。
  • 误区三:忽略 Histogram 的桶配置。默认桶边界可能不适合业务场景,需根据实际数据分布自定义桶。
  • 误区四:高频更新 Gauge。Gauge 的瞬时性意味着高频更新不会带来额外价值,反而增加开销;建议使用回调模式或降低采样频率。