微服务中使用阿里开源的TTL,优雅的实现身份信息的线程间复用一

love374
发布于 2022-7-13 15:18
浏览
0收藏

 

大家好,我是不才陈某~

 

前面在介绍分布式链路追踪时讲过异步调用会丢失链路信息,最终的解决方案是使用对应的包装类重新包装一下,如下:

  • RunnableWrapper
  • CallableWrapper
  • SupplierWrapper
    还有openFeign异步请求丢失上文的问题,这些问题追根究底都是ThreadLocal惹得祸。

 

由于ThreadLocal只能保存当前线程的信息,不能实现父子线程的继承。

 

说到这,很多人想到了InheritableThreadLocal,确实InheritableThreadLocal能够实现父子线程间传递本地变量,但是.....

 

但是你的程序如果采用线程池,则存在着线程复用的情况,这时就不一定能够实现父子线程间传递了,因为在线程在线程池中的存在不是每次使用都会进行创建,InheritableThreadlocal是在线程初始化时intertableThreadLocals=true才会进行拷贝传递。

 

所以若本次使用的子线程是已经被池化的线程,从线程池中取出线下进行使用,是没有经过初始化的过程,也就不会进行父子线程的本地变量拷贝。

 

由于在日常应用场景中,绝大多数都是会采用线程池的方式进行资源的有效管理。

 

今天就来聊一聊阿里的ThansmittableThreadLocal是如何解决线程池中父子线程本地变量传递。

 

B站链接:https://b23.tv/RI06iZl

下方是本篇文章视频教程,讲解更加详细! 

 

“帅气的人都点赞了~

 

InheritableThreadLocal 的问题

在介绍ThansmittableThreadLocal之前先来看一下InheritableThreadLocal 在线程池中的问题,如下代码:

@Test
public void test() throws Exception {
    //单一线程池
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    //InheritableThreadLocal存储
    InheritableThreadLocal<String> username = new InheritableThreadLocal<>();
    for (int i = 0; i < 10; i++) {
    username.set("公众号:码猿技术专栏—"+i);
    Thread.sleep(3000);
    CompletableFuture.runAsync(()-> System.out.println(username.get()),executorService);
   }
}

 

上述代码中创建了一个单一线程池,循环异步调用,打印一下username,由于核心线程数是1,势必存在线程的复用。

 

打印信息如下:

公众号:码猿技术专栏—0
公众号:码猿技术专栏—0
公众号:码猿技术专栏—0
公众号:码猿技术专栏—0
公众号:码猿技术专栏—0
公众号:码猿技术专栏—0
公众号:码猿技术专栏—0
公众号:码猿技术专栏—0
公众号:码猿技术专栏—0
公众号:码猿技术专栏—0

看到了吗?这里并没有实现父子线程间的变量传递,这也就是InheritableThreadLocal 的局限性。

 

TransmittableThreadLocal 使用
TransmittableThreadLocal(TTL):在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。

整个TransmittableThreadLocal库的核心功能(用户API与框架/中间件的集成API、线程池ExecutorService/ForkJoinPool/TimerTask及其线程工厂的Wrapper)。

需求场景:

  1. 分布式跟踪系统 或 全链路压测(即链路打标)
  2. 日志收集记录系统上下文
    “官网地址:https://github.com/alibaba/transmittable-thread-local


下面就以上面的例子改造成TransmittableThreadLocal试一下效果。

首选需要引入对应的依赖,如下:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
</dependency

改造后的代码如下:

@Test
public void test() throws Exception {
    //单一线程池
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    //需要使用TtlExecutors对线程池包装一下
    executorService=TtlExecutors.getTtlExecutorService(executorService);
    //TransmittableThreadLocal创建
    TransmittableThreadLocal<String> username = new TransmittableThreadLocal<>();
    for (int i = 0; i < 10; i++) {
    username.set("公众号:码猿技术专栏—"+i);
    Thread.sleep(3000);
    CompletableFuture.runAsync(()-> System.out.println(username.get()),executorService);
  }
}

 

需要注意的是需要使用TtlExecutors对线程池进行包装,代码如下:

executorService=TtlExecutors.getTtlExecutorService(executorService);

运行效果如下:

公众号:码猿技术专栏—0
公众号:码猿技术专栏—1
公众号:码猿技术专栏—2
公众号:码猿技术专栏—3
公众号:码猿技术专栏—4
公众号:码猿技术专栏—5
公众号:码猿技术专栏—6
公众号:码猿技术专栏—7
公众号:码猿技术专栏—8
公众号:码猿技术专栏—9

 

可以看到已经能够实现了线程池中的父子线程的数据传递。

 

在每次调用任务的时,都会将当前的主线程的TTL数据copy到子线程里面,执行完成后,再清除掉。同时子线程里面的修改回到主线程时其实并没有生效。这样可以保证每次任务执行的时候都是互不干涉。

 

文章转自公众号:码猿技术专栏

标签
已于2022-7-13 15:18:56修改
收藏
回复
举报
回复
    相关推荐