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解决多线程日志跟踪(转载)

相关推荐
有来技术1 天前
Spring Boot 4 + Vue3 企业级多租户 SaaS:从共享 Schema 架构到商业化套餐设计
java·vue.js·spring boot·后端
东东5161 天前
学院个人信息管理系统 (springboot+vue)
vue.js·spring boot·后端·个人开发·毕设
三水不滴1 天前
Redis缓存更新策略
数据库·经验分享·redis·笔记·后端·缓存
小邓吖1 天前
自己做了一个工具网站
前端·分布式·后端·中间件·架构·golang
大爱编程♡1 天前
SpringBoot统一功能处理
java·spring boot·后端
好好研究1 天前
总结SSM设置欢迎页的方式
xml·java·后端·mvc
小马爱打代码1 天前
Spring Boot:第三方 API 调用的企业级容错设计
java·spring boot·后端
csdn2015_1 天前
springboot task
java·spring boot·后端
czlczl200209251 天前
Spring Boot :如何高性能地在 Filter 中获取响应体(Response Body)
java·spring boot·后端
码界奇点1 天前
基于Spring Boot和Vue3的无头内容管理系统设计与实现
java·spring boot·后端·vue·毕业设计·源代码管理