4、Counter(计数器)详解:适用场景、创建方式、Add方法、常见错误(如重复累加)

Counter 是 OpenTelemetry 中最基础、最常用的同步指标类型,用于记录单调递增的累积值。它只支持增加操作,不支持减少或重置(除非进程重启)。理解 Counter 的语义、创建方式和常见陷阱,是构建可靠可观测性体系的第一步。

4.1 适用场景

Counter 适合统计只增不减的事件或量,典型场景包括:

  • 请求计数:HTTP 请求总数、RPC 调用次数。
  • 错误计数:4xx/5xx 响应次数、异常抛出次数。
  • 业务量统计:订单创建数、用户注册数、消息投递数。
  • 吞吐量统计:处理的字节数、写入的记录数。
提示:如果指标需要支持减少或重置(如当前连接数、队列深度),应使用 UpDownCounter 或 Gauge,而非 Counter。

4.2 创建方式

Counter 通过 Meter 实例创建,需要指定名称、可选的描述和单位。以下是 Java 和 Python 的典型创建方式:

Java (OpenTelemetry SDK)

// 获取 Meter
Meter meter = GlobalOpenTelemetry.getMeter("com.example.metrics");

// 创建 Counter
LongCounter requestCounter = meter
    .counterBuilder("http.requests.total")
    .setDescription("Total number of HTTP requests")
    .setUnit("1")
    .build();

Python (opentelemetry-api)

from opentelemetry import metrics

meter = metrics.get_meter("com.example.metrics")
request_counter = meter.create_counter(
    name="http.requests.total",
    description="Total number of HTTP requests",
    unit="1",
)
参数 说明 建议
name 指标名称,全局唯一 使用点分命名空间,如 http.requests.total
description 可读描述,帮助理解指标含义 必填,便于团队协作
unit 单位,如 1、ms、bytes 推荐使用 UCUM 标准单位

4.3 Add 方法详解

Counter 的核心操作是 Add(),用于增加计数。每次调用必须传入非负整数或浮点数,并可以附带标签(Attributes)。

基本用法

// Java
requestCounter.add(1, Attributes.of(
    AttributeKey.stringKey("method"), "GET",
    AttributeKey.stringKey("status"), "200"
));

# Python
request_counter.add(1, {"method": "GET", "status": "200"})

批量累加

当需要一次性增加较大数值时,直接传入增量值:

// 记录一次请求处理了 2048 字节
bytesCounter.add(2048, Attributes.of(
    AttributeKey.stringKey("endpoint"), "/upload"
));
警告:Add() 的增量值必须 ≥ 0。传入负数会抛出异常或导致未定义行为,不同 SDK 实现可能不同。

4.4 常见错误:重复累加

Counter 最常见的错误是无意中重复累加,导致指标值远高于实际值。以下场景极易触发此问题:

  • 中间件/拦截器重复调用:请求经过多个拦截器,每个拦截器都执行了一次 Add()。
  • 异步回调重复触发:回调函数被多次调用,每次调用都增加计数。
  • 重试逻辑未去重:客户端重试时,每次重试都记录一次“请求总数”,而非仅记录一次。
  • 指标对象被多次创建:每次请求都重新创建 Counter 实例,导致同一标签组合被多个实例累加。

正确做法

  1. 单点计数:在请求处理的最外层或唯一入口处执行 Add()。
  2. 使用标签区分:如果必须多次计数,用不同标签区分(如 stage: "before_retry" vs stage: "after_retry")。
  3. 复用 Meter 和 Counter 实例:Counter 应在应用启动时创建一次,全局复用。
最佳实践:在关键路径上打印日志或使用单元测试验证 Counter 的最终值,确保累加次数与预期一致。

4.5 Counter 数据流与累加逻辑

下图展示了 Counter 从创建到被后端系统消费的完整数据流,以及重复累加如何影响最终值:

Counter 数据流与重复累加风险 应用代码 中间件 1 (计数) 中间件 2 (计数) 业务逻辑 (计数) Counter 实例 http.requests.total 重复累加 ×3 最终值 = 实际值 ×3 ⚠ 重复累加后果 指标值膨胀 告警阈值误触发 容量规划失真

4.6 Counter 与 UpDownCounter 对比

特性 Counter UpDownCounter
增减方向 仅增加(单调递增) 可增可减
典型场景 请求总数、错误总数 当前连接数、队列深度
Add 参数 必须 ≥ 0 可为负数
聚合语义 Sum(求和) Sum(求和)
重置行为 进程重启后重置 进程重启后重置

4.7 避坑清单

  • 不要用 Counter 记录瞬时值:如 CPU 使用率、内存占用,应使用 Gauge。
  • 不要用 Counter 记录可减少的量:如活跃用户数,应使用 UpDownCounter。
  • 不要在热点路径中频繁创建 Counter 实例:应在初始化阶段创建并复用。
  • 注意标签基数:每个唯一的标签组合都会产生一个时间序列,避免高基数标签(如用户 ID、请求 ID)。
  • 测试时验证累加次数:使用单元测试或集成测试,确保每个请求只触发一次 Add()。
总结:Counter 是 OpenTelemetry 指标体系的基石,正确使用它需要理解单调递增语义、合理选择标签、避免重复累加。掌握这些要点后,你将能构建出准确、可靠的自定义指标。