前言
在在请求中如何实现 traceId中,我们解决了单线程环境下的链路追踪。但在实际项目中,为了提升接口性能,往往会开启异步线程去处理非核心业务。
由于 MDC 底层是基于 ThreadLocal 实现的,当主线程切换到子线程时,Trace ID 会直接丢失,导致日志链路“断掉”。如果不能实现跨线程传递,异步逻辑产生的日志将变成无法追溯的“孤儿日志”。因此,我们需要一种机制,在创建新线程时,将主线程的 MDC 上下文自动复制到子线程中。
解决思路
- 手动传递:在每个
Runnable中手动获取并设置 MDC(极其繁琐,不推荐)。 - 线程池适配:通过自定义
ThreadPoolTaskExecutor并配置TaskDecorator,在任务提交给线程池前,完成MDC上下文的“快照”传递。
通过这种方案,即便业务逻辑进入异步阶段,排查效率依然能保持在“秒级定位”的水平。
TaskDecorator
TaskDecorator 是 Spring Framework(Spring 5+) 提供的一个 任务装饰器接口,主要用于 在异步任务执行前后,对任务进行统一“包装/增强” ,最典型的用途是—— 线程上下文(ThreadLocal)在异步线程中的传递问题。
定义
public class TraceTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}
使用
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("trace-task-");
// 使用 TaskDecorator
executor.setTaskDecorator(new TraceTaskDecorator());
executor.initialize();
return executor;
}
这样,traceId 也可以在线程池中优雅传递了!