可观测性落地:如何在 Java 项目中统一埋点 Trace ID?(二)

5 阅读1分钟

前言

在请求中如何实现 traceId中,我们解决了单线程环境下的链路追踪。但在实际项目中,为了提升接口性能,往往会开启异步线程去处理非核心业务。

由于 MDC 底层是基于 ThreadLocal 实现的,当主线程切换到子线程时,Trace ID 会直接丢失,导致日志链路“断掉”。如果不能实现跨线程传递,异步逻辑产生的日志将变成无法追溯的“孤儿日志”。因此,我们需要一种机制,在创建新线程时,将主线程的 MDC 上下文自动复制到子线程中。

解决思路

  1. 手动传递:在每个 Runnable 中手动获取并设置 MDC(极其繁琐,不推荐)。
  2. 线程池适配:通过自定义 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 也可以在线程池中优雅传递了!