线程池需显式透传追踪上下文,否则span断开、trace id丢失;应通过taskdecorator、context.wrap或框架自动装饰实现mdc与span context跨线程继承,并验证父子span关系与ui链路完整性。

线程池本身不传递追踪上下文,必须显式做上下文透传,否则 Span 会断开、Trace ID 丢失,导致调用链在异步环节“消失”。关键不是换线程池,而是让 MDC 或 OpenTelemetry 上下文能跨线程延续。
确保 Trace ID 和 Span Context 能跨线程继承
Java 中常用 ThreadLocal 存储追踪上下文(如 Sleuth 的 BraveContext 或 OpenTelemetry 的 Context),但线程池复用线程后,ThreadLocal 不自动复制。必须在任务提交前手动捕获、执行时手动还原。
- Spring Cloud Sleuth:启用 sleuth.async.enabled=true 并配合 @Async 方法,框架会自动装饰 Runnable/Callable,完成 MDC 和 TraceContext 的拷贝
- OpenTelemetry:使用 Context.current().wrap(runnable) 包装任务,或通过 ExecutorService 的 decorateExecutor 工具方法封装线程池
- 自定义线程池时,务必在 setTaskDecorator 中注入上下文拷贝逻辑(如复制 MDC Map、绑定当前 Span)
配置支持异步传播的线程池 Bean
不能直接用 Executors.newFixedThreadPool,它不支持上下文透传。需声明带装饰能力的线程池:
- 使用 ThreadPoolTaskExecutor,并设置 taskDecorator,例如:new MdcTaskDecorator()(Sleuth 场景)或自定义 OpenTelemetryTaskDecorator
- 若用 CompletableFuture,需显式用 CompletableFuture.supplyAsync(Supplier, Executor) 传入已装饰的线程池,避免默认 ForkJoinPool 导致上下文丢失
- 线程工厂中不要设为 daemon 线程,否则可能在 Span 结束前被回收,造成上报不全
验证跨线程链路是否真正连通
光看日志里有 traceId 不代表链路完整——要确认父子 Span 关系正确、时间轴连续、UI 中能展开多层异步节点。
- 在异步任务入口打点:用 tracer.spanBuilder("async-process").setParent(Context.current()).startSpan() 显式声明父级关系
- 检查 Zipkin/Jaeger UI 中该 Span 是否有正确的 parentSpanId,且与上游 Span 的 spanId 匹配
- 日志中比对同一 traceId 下不同线程名(如 http-nio-8080-exec-3 vs price-fetch-1)的日志条目是否共享相同 spanId
避开常见断链陷阱
很多“看似连上”的链路其实在关键节点已断裂,尤其在以下场景:
- 使用 scheduleAtFixedRate 等定时任务时,未在每次执行前重新绑定 Context,导致每次都是新 Trace
- 线程池 submit(Runnable) 后又在内部开启新线程(如 new Thread().start()),该子线程完全脱离管控
- 日志框架未集成 MDC(如 Log4j2 未配置 %X{traceId}),导致异步日志无法关联,误判为无链路










