TransmittableThreadLocal解决了什么问题?

850 阅读3分钟

1. 需求描述

在项目开发的过程中,有用到ThreadLocal保存请求调用链的RequestId的值,使得在每次请求,业务在执行的过程中的日志打印能够串联起来。便于排查问题。

项目中因为用到了线程池,如果业务逻辑只在请求的业务线程中执行,那是没问题的。但因为有需求需要从业务线程分发部分业务逻辑到线程池去异步执行,导致线程池任务的逻辑打印的日志并没有与请求的RequestId串起来,不太方便排查问题。

2. 尝试解决问题

封装Runnable显示的传入RequestId,这种方式确实可行,可是不太友好,侵入式。不符合开闭原则

class BizRunnable implements Runnable {

    private String requestId;

    public BizRunnable(String requestId) {
        this.requestId = requestId;
    }

    @Override
    public void run() {
        // TODO: 2020/11/28 process requestId 
    }
}

操作如下
public static ThreadLocal<String> THREAD_LOCAL = new TransmittableThreadLocal<>();
String requestId = THREAD_LOCAL.get(); 
// 显示的获取已存在THREAD_LOCAL中的requestId,再构造new BizRunnable(requestId)传入该值。
Runnable task = new BizRunnable(requestId);
threadPoolExecutor.execute(task);

或者使用java8的lambda表达式,也是需要显示的传入requestId,不太优雅。

String requestId = THREAD_LOCAL.get(); 
threadPoolExecutor.execute(() -> {
    System.out.println(requestId);
    // TODO: 2020/11/28 process requestId
});

以上的方式虽然也能够实现

3. 在Github中找到了一个开源的公共组件

组件地址:github.com/alibaba/tra…

3.1. 以下部分描述从官方文档摘抄过来。

在ThreadLocal的需求场景即是TransmittableThreadLocal的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal』则是TransmittableThreadLocal目标场景。

下面是几个典型场景例子。

  • 分布式跟踪系统 或 全链路压测(即链路打标)
  • 日志收集记录系统上下文
  • Session级Cache
  • 应用容器或上层框架跨应用代码给下层SDK传递信息

3.2. 主要提供两种方式实现 ThreadLocal 的传递。

  • 一种的是在构建 Runable/Callable 的时候,使用 TtlRunnable/TtlCallable 再次封装修饰目标执行体。
public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
    private final AtomicReference<Object> capturedRef;
    private final Runnable runnable;	// 目标Runable
    private final boolean releaseTtlValueReferenceAfterRun;

    private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
        this.capturedRef = new AtomicReference<Object>(capture());
        this.runnable = runnable;	
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
    }
    ...略
    TtlCallable 略,大致同上。具体可以查看源码实现。
    
    
// runnable
Runnable runnable = getRunnable();
TtlRunnable ttlRunnable = TtlRunnable.get(runnable);
threadPoolExecutor.submit(ttlRunnable);

// callable
Callable<Long> callable = getCallable();
TtlCallable<Long> ttlCallable = TtlCallable.get(callable);
threadPoolExecutor.submit(ttlCallable);
  • 第二种方式是使用 TtlExecutors 封装修饰自定义的线程池 threadPoolExecutor。

ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor);
Runnable runnable = getRunnable();
Callable<Long> callable = getCallable();

ttlExecutorService.submit(runnable);
ttlExecutorService.submit(callable);

4. 代码实战

/**
 * https://github.com/alibaba/transmittable-thread-local
 *
 * @author shandianlala@gmail.com
 * @version 1.0
 * @date 2020-10-19 20:14
 */
public class TestTransmittableThreadLocal {

    public static TransmittableThreadLocal<TraceContext> THREAD_LOCAL = new TransmittableThreadLocal<>();

    public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
            2, TimeUnit.MINUTES, new LinkedBlockingQueue<>());

    public static void main(String[] args) {
        TraceContext traceContext = new TraceContext();
        traceContext.setTraceId(UUID.randomUUID().toString());
        traceContext.setMethod("main");
        System.out.println("原始数据=\n" + JSON.toJSONString(traceContext, SerializerFeature.PrettyFormat));
        THREAD_LOCAL.set(traceContext);

        wrapTask();
        System.out.println("============================================================================================");
        wrapExecutor();

        threadPoolExecutor.shutdown();
    }

    public static void wrapTask() {

        // runnable
        Runnable runnable = getRunnable();
        TtlRunnable ttlRunnable = TtlRunnable.get(runnable);
        threadPoolExecutor.submit(ttlRunnable);

        // callable
        Callable<Long> callable = getCallable();
        TtlCallable<Long> ttlCallable = TtlCallable.get(callable);
        threadPoolExecutor.submit(ttlCallable);

    }

    public static void wrapExecutor() {
        ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor);
        Runnable runnable = getRunnable();
        Callable<Long> callable = getCallable();

        ttlExecutorService.submit(runnable);
        ttlExecutorService.submit(callable);

    }

    public static Runnable getRunnable() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("开始 runnable。");
                TraceContext traceContext1 = THREAD_LOCAL.get();
                if (Objects.nonNull(traceContext1)) {
                    System.out.println("线程池中数据=\n" + JSON.toJSONString(traceContext1, SerializerFeature.PrettyFormat));
                } else {
                    System.out.println("traceContext1 is null.");
                }
                System.out.println("结束 runnable。");
            }
        };
        return runnable;
    }

    public static Callable<Long> getCallable() {
        Callable<Long> callable = new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                System.out.println("开始 callable。");
                TraceContext traceContext1 = THREAD_LOCAL.get();
                if (Objects.nonNull(traceContext1)) {
                    System.out.println("线程池中数据=\n" + JSON.toJSONString(traceContext1, SerializerFeature.PrettyFormat));
                } else {
                    System.out.println("traceContext1 is null.");
                }
                System.out.println("结束 callable。");
                return 22L;
            }
        };
        return callable;
    }


}
public final void set(T value) {
        if (!disableIgnoreNullValueSemantics && null == value) {
            // may set null to remove value
            remove();
        } else {
            super.set(value);
            addThisToHolder();	// 进入
        }
    }


private void addThisToHolder() {
        if (!holder.get().containsKey(this)) {
        	// holder保存业务代码中的TransmittableThreadLocal对象。
            holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
        }
    }

代码地址:代码Github地址