从一个延迟桶能找到对应的请求吗?

Histogram 的聚合点只保留 count、sum 和桶分布,正常情况下不能从一个桶反推出特定请求。Exemplar 是保留在某些观测上的样本:记录时间、原始测量值及关联的 SpanContext。它提供可能的跳转线索,不是每条延迟都必有一条 Trace,也不等于系统已经保存了该 Trace。

固定事件只有三条:/orders 在无 Span 时记录 10,在 sampled Span 中记录 30;/stock 无 Span 时记录 50。两个 route 属性集合分别形成一个点。期望前者聚合值为 count=2、sum=40,同时保留一个值为 30 且匹配 sampled Span 的 exemplar;后者 count=1,无 exemplar。无 Span 的观测仍然增加聚合计数,不会因 exemplar filter 被丢弃。

指标属性 测量值 / 记录上下文 聚合点 Exemplar
route=/orders 10/无 Span、30/sampled Span count=2、sum=40 值 30,Span ID 匹配
route=/stock 50/无 Span count=1、sum=50 无

从过滤器到 reservoir

以下是 Java SDK 1.31.0 固定完整 SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020 的源码与测试阅读。SdkMeterProviderBuilder 默认采用 trace-based exemplar filter。TraceBasedExemplarFilter.hasSampledTrace 检查传入 Context 中的 SpanContext isSampled();这里的前提是记录时上下文中确有 sampled Span,不是仅仅存在同名业务请求。

AggregatorHandle.recordLong 先向 reservoir 提供测量,再无条件执行聚合写入;ExplicitBucketHistogramAggregation.createAggregator 为显式桶包上 exemplar filter 和 bucket reservoir。HistogramExemplarReservoir 为每桶维护有限单元,不能承诺对所有满足条件的事件逐个保留。ReservoirCell.offerMeasurement 提取有效 SpanContext;ExemplarData 提供过滤后属性、纳秒时间戳及 SpanContext。上游 FilteredExemplarReservoirTest 覆盖拒绝/接受两条 offer 路径;HistogramExemplarReservoirTest 检查采集后实例状态。多个 sampled 测量竞争同一个 reservoir 时本篇的“一条对应一条”推断不适用。

本地关联断言

Lab17Test 显式创建 Sampler.alwaysOn() 的 tracer;只有对 /orders 的值 30 记录处于 span.makeCurrent() 的 Scope 中。结束 Scope 后才采集,避免将 /stock 错误带入当前 Span。内存 reader 的 MetricData 直接断言值为 30 的 exemplar、Trace ID/Span ID 与实验 Span 相等;ID 由 SDK 生成,不把随机 ID 抄成固定输出。

在 examples/opentelemetry-java/ 运行 JAVA_HOME=/tmp/otel-20260930/jdk-extract/usr/lib/jvm/java-21-openjdk-amd64 ./mvnw -q -pl sdk-labs -Dtest=Lab17Test test,退出码 0,原始 stdout:

1
LAB17 orders=count:2,sum:40,exemplar:1,traceMatch:true,spanMatch:true stock=count:1,exemplar:0

断言见 examples/opentelemetry-java/sdk-labs/src/test/java/org/example/otel/Lab17Test.java,环境与判定见 examples/opentelemetry-java/evidence/17/RUN.md。这里没有注册 Span exporter,也没有导出指标;关联 ID 相等不表示后端可查询。要从曲线跳到请求,还需要保留带该 ID 的 Trace、导出链路与可查询后端。

误解与练习

“没有 exemplar 的数据点就是没有采样请求”不成立:可能没有可用上下文,也可能 reservoir 没保留那次观测。“Exemplar 是这个桶内的全部请求”也不成立;它是有选择且容量有限的样本。异步仪表的回调所在上下文与原请求线程不同,不能假定回调得到原始请求 Span;本篇只用同步 histogram。

  1. 把 latency.record(30, route) 移出 Scope,断言 /orders 仍是 count=2、sum=40,但 exemplar 为空;修改测试验证该反例。
  2. 在同一桶和 sampled Scope 里连续记录两个不同延迟,观察 exemplar 的数量和值;结合 HistogramExemplarReservoir 解释为何不能要求两个样本全部保留。

参考资料:固定 SHA 的 TraceBasedExemplarFilter、AggregatorHandle、HistogramExemplarReservoir、ReservoirCell 及上游测试(链接见正文);本地 examples/opentelemetry-java/evidence/17/RUN.md。

导航:16 Histogram · 当前篇:17 Exemplar。18 Logs SDK 尚待验证。