**业务背景:**在sass(租户)系统中,需要传递租户信息(tenant_id) 使用TransmittableThreadLocal传递租户信息。
**问题:**使用了@Async租户传递(tennat_id) 出现的问题,和用户的tenant_id对不上。
排查问题需要的前置知识(别人的博客写的好,直接引用)
1.sass系统实现方案:https://blog.csdn.net/johntsu2006/article/details/100591615
2.ThreadLocal的使用:https://www.cnblogs.com/hama1993/p/10382523.html
3.InheritableThreadLocal的使用:https://www.cnblogs.com/hama1993/p/10400265.html
4.TransmittableThreadLocal:https://www.cnblogs.com/hama1993/p/10409740.html
说明:在此直接引用他人的博客,这几篇博客帮助我解决了问题,在此提出感谢。
正文
1.首先问题出现了@Async注解上的方法上,这里我们业务上没有对@Async线程池进行配置,觉得默认线程池SimpleAsyncTaskExecutor(来一个任务创建一个线程,不会复现线程)。
2.我通过log.info() @Async 方法中的线程ID发现是线程是复用的,那说明不是采用SimpleAsyncTaskExecutor。
3.查看说明 @EnableAsync注释文档
发现其实没有另外配置并不是优先使用SimpleAsyncTaskExecutor 线程池,先去看spring 是否存在 org.springframework.core.task.TaskExecutor bean 或者 名称为"taskExecutor"线程池。找不到才默认使用SimpleAsyncTaskExecutor。我才看自己业务当中,引入jar宝就存在org.springframework.core.task.TaskExecutor bean。
4.说明SimpleAsyncTaskExecutor并不是很好的解决方案,对线程不进行复用,对系统的性能是一种浪费,还有可能造成OOM。
5.解决方案:自定义@Async线程池使用TtlExecutors包装
@Slf4j
public class AsyncExecutorConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 配置核心线程数
executor.setCorePoolSize(10);
// 配置最大线程数
executor.setMaxPoolSize(50);
// 配置队列大小
executor.setQueueCapacity(99999);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("async-service-");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
//TtlExecutors使用这个包装,这个在前置知识第四点的博客中可以明白其原理
return TtlExecutors.getTtlExecutorService(executor.getThreadPoolExecutor());
}
}
6.找到配置的@Async配置的线程池,springboot 中的spring-boot-autoconfigure包会自动配置的一个@Async线程池。源代码如下:
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {
/**
* Bean name of the application {@link TaskExecutor}.
*/
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
@Bean
@ConditionalOnMissingBean
public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
ObjectProvider<TaskDecorator> taskDecorator) {
TaskExecutionProperties.Pool pool = properties.getPool();
TaskExecutorBuilder builder = new TaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
builder = builder.taskDecorator(taskDecorator.getIfUnique());
return builder;
}
@Lazy
@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
}
而ThreadPoolTaskExecutor的继承于AsyncListenableTaskExecutor,AsyncListenableTaskExecutor继承了AsyncTaskExecutor,AsyncTaskExecutor继承TaskExecutor。
@EnableAsync 注释文档说明:
By default, Spring will be searching for an associated thread pool definition: either a unique org.springframework.core.task.TaskExecutor bean in the context, or an java.util.concurrent.Executor bean named “taskExecutor” otherwise. If neither of the two is resolvable, a org.springframework.core.task.SimpleAsyncTaskExecutor will be used to process async method invocations. Besides, annotated methods having a void return type cannot transmit any exception back to the caller. By default, such uncaught exceptions are only logged.
总结:出现这个问题是我对@Async和TransmittableThreadLocal 了解很少情况下盲目使用在项目中,才踩到坑的。