深入 OpenTelemetry 10 - OTLP 导出的 HTTP 成功不等于完整接收
SpanData 出现在内存 exporter 里,只能证明 SDK 把 Span 交给本地导出器。若改用 OTLP/HTTP,导出器何时返回成功?服务端报告“部分拒收”时,SDK 能否据此识别完整成功?这两个问题需要一个真实监听 HTTP 的进程内测试端点,而不是再读一遍内存列表。
冻结的协议与链路
本篇选 io.opentelemetry:opentelemetry-exporter-otlp:1.31.0、OTLP/HTTP protobuf,请求明确指向 http://127.0.0.1:<随机端口>/v1/traces;VERSIONS.md 保存 artifact 校验值。未运行 gRPC exporter,不能用 HTTP 的行为替代 gRPC。测试接收端是 com.sun.net.httpserver.HttpServer,属教学实现;它不是 Collector,更不是后端存储。
| 层次 | 实验观察 | 不能推出 |
|---|---|---|
| SpanData | 内存 exporter 构造一条 wire-span |
已发网络 |
| HTTP 请求 | 测试端点收到 /v1/traces 与 protobuf 字节 |
Collector 已收到 |
| HTTP 200 | exporter CompletableResultCode.isSuccess() |
每一条均被接受 |
| partial success | 解码响应含 rejected_spans=1 |
数据完整可查 |
固定 SHA 的实现路径
业务线程向 OtlpHttpSpanExporter.export 提交 SpanData;TraceRequestMarshaler.create 将 ResourceSpans 组织为 OTLP ExportTraceServiceRequest;HttpExporter.export 委托发送器。在本版本默认 OkHttp sender 中 enqueue 异步发送,回调处理响应;exporter 返回的 result 必须有期限等待,不代表 export() 调用返回时网络已经完成。
关键边界在 HTTP 回调对 2xx 的处理:直接标记 success,不解析 protobuf partial success;非 2xx 则标记失败,IOException 同样失败。OtlpHttpSpanExporterBuilder 的 endpoint 默认值 是 localhost:4318/v1/traces,但实验显式设置动态端口;重试默认关闭,这里只对 429 情形显式配置两次尝试。上游 OkHttp exporter 测试类 与 通用 HTTP exporter 重试测试 是静态阅读的参照;本篇测试未运行上游 Gradle 测试。
五种接收响应的运行事实
Lab10Test 本地构造一个已结束的 SpanData,先通过 OTLP/HTTP 送到测试 HTTP server;接收端读取请求体,使用固定测试依赖解析出 wire-span,断言 Content-Type 为 application/x-protobuf。其他端点分别返回 500、首个 429 后 200、闩锁阻塞直到客户端超时,以及带 rejected_spans=1 的 HTTP 200 protobuf 响应。运行 JAVA_HOME=/tmp/otel-20260930/jdk-extract/usr/lib/jvm/java-21-openjdk-amd64 ./mvnw -q -pl sdk-labs -Dtest=Lab10Test test,日志中的 500 警告和 timeout 错误正是预期反例,命令最终退出码为 0;摘要见 evidence/10/RUN.md。
结果是:500 返回失败;显式重试使 429→200 总共两次请求并返回成功;timeout 返回失败;部分成功的响应虽报告拒收一条,本版本 HTTP exporter 仍报告成功。那不是“服务端完整接受”的证据。协议字段由固定版本 protobuf 测试代码实际解码,不是手写一个字符串冒充接收结果。无任意 sleep;各网络调用的结果在五秒期限内等待,timeout 通过 CountDownLatch 控制服务端响应时机。
常见误解与练习
“HTTP 200 等于每一条 Span 均收到”被 partial success 反例否定。“有重试就会自动重试所有错误”不成立:此版本默认不启用重试,500 与超时在本实验中均未配置重试;显式策略只对 429→200 的选择性路径被验证。“接收端能解析 protobuf 就等于后端可查询”也不成立:教学端点没有存储或查询接口。
练习一。 把 Lab10Test 中的 429 改成 500,保持同一 retry policy。先记录请求次数,再查固定 SHA 的 RetryUtil.retryableHttpResponseCodes(),解释为什么 500 不应简单等同于可重试错误。
练习二。 将 partial success 改为 rejected_spans=0,再比较 HTTP result 与服务端解析到的字段;解释为什么客户端应用需要分别检查状态码和服务端业务级接受计数,而不能拿本版本的 isSuccess() 当作全量接收证明。
导航与参考资料
09 SpanProcessor · 当前篇:10 OTLP/HTTP。下一篇:11 Flush 与 Shutdown(待写)。
参考资料:OpenTelemetry Java SDK c25c0a0ee0da01ab2f74ba83052d1c249ed57020 的 OTLP exporter、marshal、HTTP sender 与上游测试;运行记录 examples/opentelemetry-java/evidence/10/RUN.md。
