你好,我是看山。

transmittable-thread-local 是阿里开源一个线程池复用场景下,处理异步执行时上下文传递数据问题的解决方案。可以从官方文档https://github.com/alibaba/transmittable-thread-local获取更多信息。

本文主要是变更 transmittable-thread-local 使用方式时出现的一个异常。

异常现场

看异常之前,先简单说下项目大概情况。

项目是 Java 栈,使用了 SpringBoot+MyBatis 的框架结构,构建工具是 Maven。因为项目中使用了比较多的多线程逻辑,所以引入了 transmittable-thread-local,解决上下文传递数据问题。后来做项目升级,接入公司的监控系统,启动时增加了启动参数-javaagent:/path/to/transmittable-thread-local-2.12.1.jar,通过零侵入的方式解决多线程上下文传值问题。

于是,有些逻辑出错了。

我们看看异常栈(日志做了删改,隐藏项目信息):

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor' available: expected single matching bean but found 3: executor1,executor2,executor3at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1200)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:420)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)……at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)……

异常日志很清楚,就是通过AbstractApplicationContext.getBean获取 Bean 的时候,因为存在多个同类型的ThreadPoolTaskExecutor,Spring 容器不知道返回哪个 Bean,就抛出了NoUniqueBeanDefinitionException异常。

排查问题

我们再来看看调用代码:

public static void doSth(Object subtag, Object extra, long time) {ApplicationContextContainer.getBean(ThreadPoolTaskExecutor.class).execute(() -> {// 一些业务代码});
}@Component
public class ApplicationContextContainer implements ApplicationContextAware {private static ApplicationContext applicationContext;public static <T> T getBean(Class<T> clazz) {return applicationContext.getBean(clazz);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ApplicationContextContainer.applicationContext = applicationContext;}
}

可以看出来,applicationContext.getBean时只传入了 class 类型,没有指明 Bean 的名字。推测是项目中定义了多个ThreadPoolTaskExecutor类型的 Bean,名字分别是 executor1、executor2、executor3(名字改过了,大家写代码时尽量使用见名知意的起名方式)。

@Configuration
public class ExecutorConfig {@Bean(value = "executor1")public Executor executor1() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();// 一些初始化方法taskExecutor.initialize();return taskExecutor;}@Bean(value = "executor2")public Executor executor2() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();// 一些初始化方法taskExecutor.initialize();return TtlExecutors.getTtlExecutor(taskExecutor);}@Bean(value = "executor3")public Executor executor3() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();// 一些初始化方法taskExecutor.initialize();return TtlExecutors.getTtlExecutor(taskExecutor);}
}

从上面的代码可以发现,确实有 executor1、executor2、executor3 三个Executor,executor1 是ThreadPoolTaskExecutor类型的,executor2 和 executor3 是经过TtlExecutors.getTtlExecutor包装的ThreadPoolTaskExecutor

我们来看看TtlExecutors.getTtlExecutor方法:

public static Executor getTtlExecutor(@Nullable Executor executor) {if (TtlAgent.isTtlAgentLoaded() || null == executor || executor instanceof TtlEnhanced) {return executor;}return new ExecutorTtlWrapper(executor, true);
}

根据错误反推,经过TtlExecutors.getTtlExecutor之后返回的还是ThreadPoolTaskExecutor类型。也就是上面代码走了if语句,直接返回了输入参数。

但是,这里就碰到了两个开发十大未解之谜中的两个:

  1. 代码没改,之前好好地,怎么就报错了;
  2. 本地好使,为什么放在服务器上就报错了。

定位问题

首先,我们需要知道,代码的终点不是玄学。我们现在用的计算机还不会撒谎,只要报错了,就一定是有问题。

我们仔细看看TtlExecutors.getTtlExecutor方法中的if判断:

  • TtlAgent.isTtlAgentLoaded():这个是判断 ttlAgentLoaded 标识,这个后文再说;
  • null == executor:输入参数为 null,显然不符合;
  • executor instanceof TtlEnhanced:输入参数是TtlEnhanced类型,输入的是ThreadPoolTaskExecutor类型,不符合。

所以,重点看看 ttlAgentLoaded 标识:

public static boolean isTtlAgentLoaded() {return ttlAgentLoaded;
}

从全局找到修改ttlAgentLoaded的地方是:

public final class TtlAgent {public static void premain(final String agentArgs, @NonNull final Instrumentation inst) {kvs = splitCommaColonStringToKV(agentArgs);Logger.setLoggerImplType(getLogImplTypeFromAgentArgs(kvs));final Logger logger = Logger.getLogger(TtlAgent.class);try {logger.info("[TtlAgent.premain] begin, agentArgs: " + agentArgs + ", Instrumentation: " + inst);final boolean disableInheritableForThreadPool = isDisableInheritableForThreadPool();// 省略非相关代码ttlAgentLoaded = true;} catch (Exception e) {String msg = "Fail to load TtlAgent , cause: " + e.toString();logger.log(Level.SEVERE, msg, e);throw new IllegalStateException(msg, e);}}// 省略非相关代码
}

有一定 javaagent 知识的应该知道,premain方法是 java 启动时,加载 javaagent 后执行的方法。

这就吻合了。

报错之前的服务器代码,ExecutorConfig类中定义的 executor1 是ThreadPoolTaskExecutor类型,executor2 和 executor3 是ExecutorTtlWrapper类型,使用applicationContext.getBean(clazz)能够得到名字是 executor1 的 Bean。

然后使用-javaagent:/path/to/transmittable-thread-local-2.12.1.jar方式实现零侵入的transmittable-thread-local注入能力。ExecutorConfig类中定义的 executor2 和 executor3 是ThreadPoolTaskExecutor类型,使用applicationContext.getBean(clazz)就会查到三个ThreadPoolTaskExecutor类型的 Bean,Spring 容器没有办法判断返回哪一个,于是抛出了NoUniqueBeanDefinitionException异常。

本地启动是加上-javaagent:/path/to/transmittable-thread-local-2.12.1.jar命令,问题复现。

解决问题

解决上面的报错比较简单,就是使用applicationContext.getBean(beanName, clazz)方法,通过输入指定的 Bean 的名字和类型,获取确定 Bean,代码修改为:

public static void doSth(Object subtag, Object extra, long time) {ApplicationContextContainer.getBean("executor1", ThreadPoolTaskExecutor.class).execute(() -> {// 一些业务代码});
}

流水线发版回归测试,问题解决。

青山不改,绿水长流,我们下次见。


你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。

个人主页:https://www.howardliu.cn
个人博文:小心 transmittable-thread-local 的这个坑
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:小心 transmittable-thread-local 的这个坑

小心 transmittable-thread-local 的这个坑相关推荐

  1. TLS(Thread Local Storage)问题demo

      C++11中的thread_local是C++存储期的一种,属于线程存储期.存储期定义C++程序中变量/函数的范围(可见性)和生命周期.C++程序中可用的存储期包括auto.register.st ...

  2. TLAB(Thread Local Allocation Buffer)

    TLAB是虚拟机在堆内存的eden划分出来的一块专用空间,是线程专属的.在虚拟机的TLAB功能启动的情况下,在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用,这样每个线程都单独 ...

  3. JVM线程本地分配缓冲区(Thread Local Allocation Buffer)TLAB详解

    最近在看java性能相关方面的书籍.然后在GC调优相关的部分出现了,线程本地分配缓冲区的名词,对于它的调优级为重要,所以就梳理一下这个到底是什么?为什么他对于JVM性能如此重要. 什么是JVM线程本地 ...

  4. 全链路跟踪之线程上下文Thread Local实战(完整源码)

    写在开头: 我是「猿码天地」,一个热爱技术.热爱编程的IT猿.技术是开源的,知识是共享的! 写博客是对自己学习的总结和记录,如果您对Java.分布式.微服务.中间件.Spring Boot.Sprin ...

  5. thread local storage

    有时会需要这种模式,一个全局变量,需要在程序的任何地方都可以使用它,但是当这个变量出现在不同线程时,就要求系统将这个变量拷贝到各个线程中,这样的话,每个线程内部也可以随时访问本线程的全局变量,但是线程 ...

  6. thread local性能 c++_MySQL 5.7 amp; MySQL 8.0 性能对比

    (给数据分析与开发加星标,提升数据技能) 来源:jiaxin_12 https://www.cnblogs.com/YangJiaXin/p/11234591.html 背景 测试mysql5.7和m ...

  7. java线程本地存储_[并发并行]_[C/C++]_[使用线程本地存储Thread Local Storage(TLS)-win32和pthread比较]...

    场景: 1.  需要统计某个线程的对象上创建的个数. 2. 当创建的堆空间需要根据线程需要创建和结束时销毁时. 3. 因为范围是线程只能看到自己的存储数据,所以不需要临界区或互斥量来维护自己的堆内存. ...

  8. 学界 !李飞飞高徒Andrej Karpathy提醒你,小心搭建神经网络的六个坑

    摘要: 继Ian Goodfellow的推特小课堂之后,特斯拉的人工智能研究负责人.李飞飞斯坦福高徒Andrej Karpathy也在twitter上分享了他对神经网络的一些研究技巧. 继Ian Go ...

  9. 机器学习、AI那么火,千万小心别掉进前人的坑

    向AI转型的程序员都关注了这个号

  10. Netty中的那些坑

    Netty中的那些坑(上篇) 最近开发了一个纯异步的redis客户端,算是比较深入的使用了一把netty.在使用过程中一边优化,一边解决各种坑.儿这些坑大部分基本上是Netty4对Netty3的改进部 ...

最新文章

  1. Vbox在Linux 5上安装Oracle 11gR2 RAC
  2. 【100题】第五十九题 用C++编写不能被继承的类
  3. Fatal error in launcher: Unable to create process using ‘“d:\python3.6\python.exe“ “D:\python3.6\Sc
  4. OpenFOAM流固耦合问题-FsiFoam(foam-extend-4.0)运行tutorials的bug修复
  5. php 创建目录_使用SMB绕过PHP远程文件包含限制
  6. 【C基础】指针/指针运算/二级指针/函数指针
  7. H3C MSTP实验
  8. js中递归调用返回值为undefined问题
  9. 38. 重定向与负载均衡
  10. [推荐]13款js编辑器大全
  11. ppapi,npapi
  12. isis宣告网络_ISIS协议及其配置
  13. Deepsort工作原理分析
  14. 微信小程序被投诉怎么办?小妙招教给你
  15. 【软件之道】Word模板的制作及使用
  16. Python3.6-Flask:制作一个语音对话问答机器人系统(网页版)
  17. Pytorch 3D卷积
  18. postgrepSQL
  19. 微服务商城系统(十三)订单、支付流程分析
  20. 二十一世纪大学英语读写教程(第三册)学习笔记(原文)——1 - How I Got Smart(我是如何变聪明的)

热门文章

  1. 如何在SQL SERVER的windows身份验证添加一个SQL Server身份验证方式
  2. 外贸中一些单词的缩写
  3. 系统时间与服务器时间同步出错,Win7电脑时间同步出错是怎么回事?系统时间同步失败如何解决?...
  4. 微博 php7,PHP_迁移PHP版本到PHP7,今天看到微博上说phpng也就是ph - phpStudy
  5. “AI四小龙”神话破灭?依图终止IPO,云从大裁员,旷视巨亏不止
  6. 使用Xcode创建第一个App
  7. 单片机简易开发板怎么设计,我来告诉你
  8. python判断用户名密码是否正确_Python 判断输入的用户名和密码是否正确
  9. linux没有网卡配置文件,linux找不到网卡配置文件解决办法
  10. 数据库——MySQL——完整性约束