深入 OpenTelemetry 17:Exemplar 怎样携带一次 Trace 的线索
从一个延迟桶能找到对应的请求吗?
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 | |
断言见 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。
- 把
latency.record(30, route)移出 Scope,断言/orders仍是 count=2、sum=40,但 exemplar 为空;修改测试验证该反例。 - 在同一桶和 sampled Scope 里连续记录两个不同延迟,观察 exemplar 的数量和值;结合
HistogramExemplarReservoir解释为何不能要求两个样本全部保留。
参考资料:固定 SHA 的 TraceBasedExemplarFilter、AggregatorHandle、HistogramExemplarReservoir、ReservoirCell 及上游测试(链接见正文);本地 examples/opentelemetry-java/evidence/17/RUN.md。
导航:16 Histogram · 当前篇:17 Exemplar。18 Logs SDK 尚待验证。
