深入 OpenTelemetry E03:Micrometer bridge 与 SDK 直调为什么会重复
一个业务计数器已经挂在 Micrometer MeterRegistry 上,又在同一处理函数直接调用 OpenTelemetry SDK 计数器;两个路径都叫 duplicate.orders。此时看到两条点,是 bridge 内部重试、reader 重复导出,还是业务代码真正记录了两次?答案要从记录入口和 instrumentation scope 入手,不能只看指标名。
本篇固定 Java SDK 1.31.0 源码 SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020、instrumentation bridge 1.31.0-alpha 源码 SHA 97d87f3f3b2bc61aa396d71a98387a1e857618af。测试 classpath 上引入 Micrometer Core 1.12.5 与桥接 JAR,具体 artifact/JAR SHA-256 在 writing-plans/opentelemetry-java/VERSIONS.md。本机完整 JDK 21.0.12 执行测试;没有挂 Agent,也没有建立第二套全局 SDK。
bridge 是 MeterRegistry,不是 exporter
固定 instrumentation SHA 的 OpenTelemetryMeterRegistry.java L33–52 返回桥接 registry,newCounter 在 L74–77 建立 OpenTelemetryCounter。OpenTelemetryMeterRegistryBuilder.java L86–102 从传入的 OpenTelemetry 对象获取 io.opentelemetry.micrometer-1.5 scope 的 Meter。这个 registry 把记录转成 SDK instrument,不负责连接 Collector。
OpenTelemetryCounter.java L29–49 以命名约定转换名称、以 base unit 建 double counter;increment 把 Micrometer tags 转为 Attributes,调用 SDK add;移除后不再记录。固定 SDK SHA 的 SdkMeter.java L83–90 创建直接 SDK long counter;InMemoryMetricReader.java L70–90 的 create() 默认 cumulative,而 createDelta() 是另一种显式选择。桥接上游 AbstractCounterTest.java L29–71 也检查单位、tag 与 add 后移除;这里是静态阅读上游测试,并未在本机跑其 Gradle 测试。
模型是 Micrometer counter → OpenTelemetryMeterRegistry → OTel double counter → reader;另一条是 业务代码 → OTel long/double counter → 同一个 reader。同名不代表同一条业务采集路径,scope 也参与标识。
固定样本:从两点到四点
本地自写 examples/opentelemetry-java/sdk-labs/src/test/java/org/example/otel/E03Test.java 使用 SDK 进程内 reader。向 bridge.orders 的 Micrometer counter 和 sdk.orders 的直接 SDK counter 分别记录 2,均附 route=checkout、单位 items。第一次 collect 精确得到两条 metric/两个点:bridge 为 double sum,direct 为 long sum,两个 temporality 都是 CUMULATIVE,值都为 2。
| 名称 | 记录入口 | scope | 单位 / temporality | 点数与值 |
|---|---|---|---|---|
bridge.orders |
Micrometer registry | io.opentelemetry.micrometer-1.5 |
items / CUMULATIVE |
1 点,double 2 |
sdk.orders |
SDK 直调 | manual |
items / CUMULATIVE |
1 点,long 2 |
duplicate.orders |
两个入口各执行一次 | 两个不同 scope | items / CUMULATIVE |
各 1 点,double 1 + 1 |
随后用两个代码入口记录同一个逻辑事件:
1 | |
第二次 collect 为四条 metric/四个点,其中 duplicate.orders 占两条:scope 各为 io.opentelemetry.micrometer-1.5 与 manual,每条一个 double sum 点,单位 items、值 1、CUMULATIVE。先前两点再次出现是 cumulative reader 的读取视图,不能把“第二次 collect 的四点”说成“只记录四个新事件”。重复的直接原因是registry.increment 与 direct.add 都运行了,不是桥接内部自动重复。完整命令、退出码和摘要见 examples/opentelemetry-java/evidence/E03/RUN.md。
边界与常见误解
- 直接创建 OTel Counter 并不能证明 Micrometer bridge 正常工作;本次确实创建
OpenTelemetryMeterRegistry,从 MicrometerCounter进入桥接。 - 同名两点分别属于不同 scope;不能简单相加称为一个 SDK 点,也不能据此推断某个后端的去重策略。多个 registry、重复绑定或附加 Agent 可能带来其他重复路径,本实验没有覆盖。
items是这里的显式 base unit;名称保持输入时的形态只适用于本实验默认 identity naming convention,不代表所有 Prometheus mode 命名规则。create()的 CUMULATIVE 是这里选定的进程内 reader 行为;没有运行 DELTA reader、导出 OTLP Metrics、Collector、存储查询或采样负载。
两道练习
- 修改
E03Test:只保留duplicateBridge.increment(),删去同名 direct 的add;重新运行并检查duplicate.orders只剩哪个 scope、几点。对照原结果,找出重复的必要第二条记录路径。 - 构造
InMemoryMetricReader.createDelta()的对照:连续两次 collect 前各 increment 一次,断言各窗口的值和 temporality。不要把第二次 Delta 点与本篇第二次 Cumulative 读取混为一谈。
