深入 OpenTelemetry 09 - SpanProcessor 的调用线程与批量队列
出口变慢时业务线程会不会跟着变慢?把同步导出换成 Batch 处理器后,线程可能不再被 exporter 阻塞,但队列容量又引出第二种失败:Span 已结束,入队却失败。只有同时检查执行线程、生产数量和实际导出数量,才能区分背压与丢弃。
两条路径的边界
| 路径 | onEnd 所在线程 |
exporter 所在线程 | 出口被挡住时 |
|---|---|---|---|
SimpleSpanProcessor |
结束 Span 的调用线程 | 同一调用线程 | 本篇 Future 尚未完成 |
BatchSpanProcessor |
结束 Span 的调用线程 | SDK daemon worker | 当前请求可继续;队列满时丢弃 |
从冻结源码看:SimpleSpanProcessor.onEnd 直接调用 export(spans),不是将 Span 自动推入后台线程;它对未 sampled Span 先返回,exporter 抛运行时异常时记录告警而非回滚业务。SpanExporter.export 返回 CompletableResultCode,所以“同步”是调用发生在同一线程,不保证结果已经异步完成;本实验的教学 exporter 在调用内部阻塞,恰好显示调用线程会被占用。
BatchSpanProcessor.onEnd 对 sampled Span 调用 worker 的 addSpan,queue.offer 在有界队列满时直接计数并丢弃。构造器在 85 行 启动 daemon worker;worker loop 负责 drain、按批大小或时间触发导出。默认队列为 2048、批量为 512,本篇故意设成 4 与 1。上游 exportMoreSpansThanTheMaximumLimit 也用阻塞 exporter 控制满队列分支。以上源码阅读与上游测试不是本机吞吐指标。
有期限同步而非猜延时
Lab09Test 的 BlockingExporter 是教学导出器:第一次进入 export 时发出闩锁通知并等待另一闩锁释放(最长 5 秒);不引入任意 sleep。Simple 实验在专用 producer 线程结束 Span:收到“进入出口”的信号时,Future.isDone() 为 false;放行后断言 exporter 的线程名等于 producer 名。Batch 实验让 worker 的第一次 export 卡在闩锁,生产线程立刻结束首条与后续九条 Span;第一条在 worker、四条在容量为四的队列,剩下五条未入队。释放后 forceFlush().join(5s),本地 exporter 收到五条。
运行 JAVA_HOME=/tmp/otel-20260930/jdk-extract/usr/lib/jvm/java-21-openjdk-amd64 ./mvnw -q -pl sdk-labs -Dtest=Lab09Test test,原始摘要:
1 | |
测试比较的不是“网络返回成功”或吞吐基准,而是闩锁到达、线程身份、生产数和本地导出集合;另记录两段调用线程的纳秒耗时,仅作为这次运行的观察值,不推广为性能基准。这里的 forceFlush 可以推动尚在队列中的 Span,不能复活 offer 失败的五条;第 11 篇再独立验证生命周期边界。未用 Collector,不能把此处五条“已导出”称为 OTLP 端点已接收。
误解与练习
“Batch 就不会丢数据”不成立:本实验生产十条,仅五条进入教学 exporter。“Simple 只是在出口排队,不阻塞业务线程”也不成立:若 exporter 方法自身阻塞,调用线程就会等待;但 exporter 返回未完成的 CompletableResultCode 时,不能从本实验推断它一定阻塞到远端持久化。
练习一。 将 Batch maxQueueSize 从 4 改成 8(保留闩锁),对应修改断言并重跑。区分更大的队列改善的是本次接纳量,还是 exporter 的实际吞吐速率。
练习二。 将首次阻塞 exporter 的返回值改成 ofFailure(),重跑后分别检查 exporter 收到的 Span 数和结果码;解释“已经传给出口”和“出口报告成功”为什么需要单独统计。
导航与参考资料
08 头部采样 · 当前篇:09 SpanProcessor。下一篇:10 OTLP 导出(待写)。
参考资料:OpenTelemetry Java SDK 固定 SHA 的 SimpleSpanProcessor、BatchSpanProcessor、builder 与上游 Batch 测试(固定链接见上文);本地运行记录 examples/opentelemetry-java/evidence/09/RUN.md。
