深入 OpenTelemetry 26:Collector 接收成功为何不等于后端查得到
SDK 的 OTLP exporter 返回成功,Trace 却在后端查询不到。排查时,至少要分开“Java 请求被 Collector 接受”“Collector 的 batch 处理”“出口发送成功”和“后端实际可查”四个事件;合并成一个布尔值,会把队列里尚未发出的数据误判成已存储。Collector 不是 Java SDK 的一部分,而是独立的 Go 进程。
本篇用 Java SDK 1.31.0、完整 JDK 21.0.12,独立运行 Collector Contrib 0.101.0 与 Jaeger all-in-one 1.57.0。两个 Go 可执行程序的归档 SHA-256 与解包二进制 SHA-256 均列于 writing-plans/opentelemetry-java/VERSIONS.md;归档经 release URL 的 HTTPS 转发取得,未获得上游签名验证,不能把“本地算了哈希”写成“发行方已认证”。Jaeger 使用内存存储,故意设置进程重启即可丢失数据的下游。
从 Java 返回值走到 Jaeger 查询
固定 Java SDK 源码 SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020。OtlpHttpSpanExporter.java L67–91 将 SpanData 编成 OTLP protobuf 并把请求交给 sender;单独的 exporter flush() 当即返回成功,不是“远端已经 flush”。OkHttpHttpSender.java L88–121 异步执行 HTTP POST,并由响应或错误回调完成请求。本篇直接调用 export(spans).join(5s).isSuccess(),只断言 Java → Collector 这一跳;不会把 BatchSpanProcessor 的后台结果混进这一次返回值。
如果改用 BatchSpanProcessor,onEnd L98–103 对 sampled Span 入队,exportCurrentBatch L321–339 在 worker 上等待有限时间、计数或记录失败并清空这一批;队列边界已经发生在 Java 进程里。这里的 Collector sending_queue 是另一个进程的队列,不能把二者写成一个重试机制。上游 OtlpHttpSpanExporterOkHttpSenderTest.java L19–45 与 BatchSpanProcessorTest.java L1 是静态阅读入口,不是本机运行的上游测试。Collector 的 pipeline 与 Jaeger API 则依据本篇固定可执行发行包的实际启动、self-metrics 和查询响应,而非伪称“Java 源码实现了 Collector”。
| 证据位置 | 本篇核对手段 | 能说明什么 / 不能说明什么 |
|---|---|---|
| SDK 出口 | CompletableResultCode 有期限完成 |
HTTP 请求的出口结果;不能说明后端已存储。 |
| Collector receiver | otelcol_receiver_accepted_spans |
该进程接受了 Span;不等于 exporter 已发送。 |
| Collector batch | otelcol_processor_batch_batch_send_size_sum |
本次 batch 窗口交出的 Span 单位数;不是所有 processor 的通用接收计数。 |
| Collector exporter | otelcol_exporter_sent_spans |
当前进程发往 Jaeger 的成功计数;并不证明另一端持久化。 |
| Jaeger query | /api/traces/{traceId} JSON 的 operationName |
此刻可查询这条 Trace;本次后端仅内存,重启不保存。 |
停下游、恢复、再停 Collector
Lab26Test 在测试临时目录生成实际 Collector 配置:loopback OTLP/HTTP receiver → batch (send_batch_size=1, timeout=200ms) → OTLP/gRPC exporter → Jaeger。Exporter 开启进程内 sending_queue(16 批,单 consumer)和有界重试(初始 200ms、最大间隔 1s、最长 30s)。六个服务/指标端口在测试运行时获取,不绑定远端网络。Jaeger 使用 in-memory 后端并暴露独立查询端口。Java 程序用确定的 Trace ID 区分四种场景;若 Jaeger 将前导零折成短的 16 位 Trace ID,按后端返回的 ID 与操作名一起核对。
在 examples/opentelemetry-java/ 目录运行(需先备妥 VERSIONS.md 中校验过的两个本地 Go 二进制):
1 | |
退出码 0。本篇的原始 self-metrics、Jaeger JSON、失败注入和命令在 examples/opentelemetry-java/evidence/26/RUN.md;测试标准输出含以下可核结果:
1 | |
第一条说明 SDK 成功后,Collector 的 receiver/batch/exporter 自身指标及 Jaeger 查询均有独立证据。关停 Jaeger 后,case2 的 SDK 仍返回成功,receiver/batch 计数增长,但此刻下游无法查询;Jaeger 恢复后,Collector 的出口计数增长且 case2 可查。随后再次停 Jaeger、让 Collector 接收 case3,强制终止 Collector:SDK 在 Collector 关闭时发送 case5 返回失败;Collector 和 Jaeger 重启后 case3 查询不到,而新发的 case4 能查到。这个无文件存储的进程内队列不能跨崩溃保留数据;Jaeger 的内存也会在重启后清空。两边的限制要分开解释,不能靠 HTTP 200 推出“最终一定到达”。
不要越过证据边界
本篇并未挂 Agent 直连 Collector;21–25 的 Agent/JDK 21 对照不能自动证明 Agent → 本篇 Collector 版本的链路。Java 使用直调 exporter,不代表 9 中 BatchSpanProcessor 队列的异步成功语义已经在这个故障组合里验证。也没有配置 file_storage、生产型 Jaeger 存储或查询其他后端,更没有 Metric/Log OTLP。自动重试要受队列容量、最大重试时间、进程存活和下游状态限制;持久队列至少还需要实际落盘/重启回放的独立验收。常见误解是把 receiver accepted、batch send、exporter sent 或 Jaeger 查询中的任一个单独当成“无丢失”的全链路证明。
- 修改测试配置,把 Collector
sending_queue.queue_size降到 1,保持下游停止并连续提交多条带不同 Trace ID 的 Span;分别记录 Java 出口、receiver accepted、exporter sent 和 Jaeger 恢复后查询集合。不能只用一个 HTTP 状态码推断每条都保存。 - 为 Collector 明确引入固定版本的
file_storage扩展,并把队列接上存储;注入与 case3 同样的强制重启,记录磁盘与重新发送的真实证据后,才能将“队列重启必丢”的结论缩小到无持久存储配置。还需再考虑 Jaeger 的存储生命周期。
参考资料:Java OTLP exporter 与上游测试(固定 SHA)、Java SDK batch processor(固定 SHA)、Collector Contrib v0.101.0 的版本边界、Jaeger v1.57.0 的版本边界、examples/opentelemetry-java/evidence/26/RUN.md。
导航:24 Servlet 与 JDBC · 25 自定义扩展 · 当前篇:26 Collector 接收与转发。
