TransmittableThreadLocal(TTL)简介-如何多线程传递TRACEID

1. 背景与问题

在多线程编程中,ThreadLocal 常用于保存线程本地的变量(如用户会话、日志跟踪ID等),确保每个线程独立访问自己的数据。但传统 ThreadLocal 存在一个致命缺陷
当任务提交到线程池时,ThreadLocal 的值无法自动传递到线程池的工作线程中 ,导致上下文丢失。

例如:

java 复制代码
ThreadLocal<String> context = new ThreadLocal<>();
ExecutorService executor = Executors.newFixedThreadPool(2);

context.set("user123"); // 主线程设置值
executor.submit(() -> {
    System.out.println(context.get()); // 输出 null(值丢失)
});

2. TransmittableThreadLocal 的解决方案

阿里巴巴开源的 TransmittableThreadLocal(TTL) 解决了这一问题。它通过以下机制实现线程池中的上下文传递:

  1. 任务提交时 :捕获当前线程的 TTL 值(生成快照)。
  2. 任务执行时:将快照中的值恢复到线程池的工作线程中。
  3. 任务完成后:清理工作线程中的上下文,避免污染后续任务。

3. 工作原理

  1. 提交任务

    • 调用 executor.submit(task) 时,TTL 自动调用 Transmitter.capture() 捕获当前线程的所有 TTL 值。
    • 将捕获的快照附加到任务中。
  2. 执行任务

    • 工作线程执行任务前,TTL 通过 Transmitter.replay(snapshot) 将快照值注入工作线程的 TTL
    • 任务内通过 get() 获取到的是提交时的上下文值。
  3. 清理阶段

    • 任务执行完成后,调用 Transmitter.restore() 恢复工作线程的原始上下文。

4. 典型场景:跨线程记录统一的日志标签

4.1、自定义 MDCTransmittableThreadLocal 值

java 复制代码
public class MDCTransmittableThreadLocal extends TransmittableThreadLocal<Map<String, String>> {
    /**
     * 在线程池中执行任务前调用,MDCTransmittableThreadLocal中的值赋值到本线程的MDC中
     */
    @Override
    protected void beforeExecute() {
        Map<String, String> mdc = get();
        mdc.forEach(MDC::put);
    }

    /**
     *  在线程池中执行任务后调用,清楚本线程MDC值 .MDCTransmittableThreadLocal值由线程池清理
     */
    @Override
    protected void afterExecute() {
        MDC.clear();
    }

    /**
     * 当线程第一次访问 TransmittableThreadLocal 时调用。
     * @return
     */
    @Override
    protected Map<String, String> initialValue() {
        return Maps.newHashMap();
    }
}

4.2、定义filter拦截请求

java 复制代码
public class RequestStreamFilter implements Filter {
    private static MDCTransmittableThreadLocal ttlMDC = new MDCTransmittableThreadLocal();
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            //为每一个请求创建一个ID,方便查找日志时可以根据ID查找出一个http请求所有相关日志
            String uuid=UUIDUtil.getUUID();
            MDC.put(SystemConstants.TRACE_ID_KEY,uuid );
            ttlMDC.get().put(SystemConstants.TRACE_ID_KEY, uuid);//跨线程池传递使用
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
            ttlMDC.get().clear();
            ttlMDC.remove();
        }
    }
    @Override
    public void destroy() {
    }
}

4.3、测试代码

java 复制代码
public static void test() throws Exception {
    //单一线程池
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    //需要使用TtlExecutors对线程池包装一下
    executorService= TtlExecutors.getTtlExecutorService(executorService);
    //TransmittableThreadLocal创建
    TransmittableThreadLocal<String> name = new TransmittableThreadLocal<>();
    String s1 = UUID.randomUUID().toString();
    String s2 = UUID.randomUUID().toString();
    System.out.println("MDC1:"+s1);
    System.out.println("MDC2:"+s2);
    MDC.put("traceId", s1);
    for (int i = 0; i < 5; i++) {
        if(i%2 == 0){
            ttlMDC.get().put("traceId", s2);
        }else{
            ttlMDC.get().put("traceId", s1);
        }
        name.set("🗡光如影"+i);
        Thread.sleep(3000);
        CompletableFuture.runAsync(()-> {
            System.out.println(name.get());
            System.out.println("MDC:"+MDC.get("traceId"));
            System.out.println(Thread.currentThread().getName());
        },executorService);
    }
    name.remove();
    CompletableFuture.runAsync(()-> System.out.println(name.get()),executorService);
    executorService.shutdown();
}

参考

Slf4j MDC使用transmittable-thread-local解决多线程日志跟踪(转载)

相关推荐
Victor3565 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor3565 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
灰子学技术7 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Gogo8168 小时前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang8 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
毕设源码_廖学姐9 小时前
计算机毕业设计springboot招聘系统网站 基于SpringBoot的在线人才对接平台 SpringBoot驱动的智能求职与招聘服务网
spring boot·后端·课程设计
野犬寒鸦10 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
逍遥德11 小时前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
MX_935911 小时前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring
程序员泠零澪回家种桔子12 小时前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构