并发条件下-数据源切换失效问题及解决方案

问题描述:

这impl文件上已经注明了@DS("tms"),但是在时间执行过程中还是使用的别的数据源。

dart 复制代码
public Map<String, List<String>> queryMonitoringMetricsOnSevenDays(QueryResourceTo projectInfo) {
    if(projectInfo.getServiceNameList()==null ||projectInfo.getServiceNameList().isEmpty()){
        return null;
    }
    // 创建三个 CompletableFuture 任务
    CompletableFuture<Map<Object, Object>> slowInterfaceFuture = CompletableFuture.supplyAsync(
            () -> tradeLogMapper.querySlowInterfaceNumOnSevenDays(projectInfo.getServiceNameList()), taskExecutor);
    CompletableFuture<Map<Object, Object>> slowSqlFuture = CompletableFuture.supplyAsync(
            () -> sqlLogMapper.querySlowSqlNumOnSevenDays(projectInfo.getServiceNameList()), taskExecutor);
    CompletableFuture<Map<Object, Object>> errorInfoFuture = CompletableFuture.supplyAsync(
            () -> errorLogMapper.queryErrorNumOnSevenDays(projectInfo.getServiceNameList()), taskExecutor);
    // 在需要打印数据源的地方添加以下代码
    DataSource dataSource = applicationContext.getBean(DataSource.class);
    System.out.println("当前使用的数据源为:" + dataSource);

    // 等待所有任务完成
    CompletableFuture.allOf(slowInterfaceFuture).join();

    try {
        // 获取三个任务的结果
        Map<Object, Object> slowInterface = slowInterfaceFuture.get();
        Map<Object, Object> slowSql = slowSqlFuture.get();
        Map<Object, Object> errorInfo = errorInfoFuture.get();
        HashMap<String, List<String>> objectObjectHashMap = new HashMap<>();

        // 在这里处理获取到的结果
        // ...

        return null;
    } catch (InterruptedException | ExecutionException e) {
        // 处理异常
        e.printStackTrace();
        return null;
    }
}

在并发环境下,使用指定的数据源可能会出现问题,这通常与以下几个原因有关:

  1. 数据源上下文的线程本地变量:很多数据源切换机制依赖于线程本地变量(ThreadLocal)来存储当前的数据源信息。在并发环境下,线程池中的线程复用可能导致数据源上下文信息丢失或不一致。

  2. CompletableFuture的线程池问题CompletableFuture.supplyAsync 默认使用的是 ForkJoinPool.commonPool,这个线程池中的线程是全局共享的,因此无法保证每个线程都使用正确的数据源上下文。

  3. 异步任务的AOP切面问题@DS 注解依赖 AOP 切面来切换数据源,而异步任务中的切面可能不会被正确触发,导致数据源没有切换。

解决方法

  1. 使用自定义的线程池:通过自定义线程池并配置正确的数据源上下文,可以确保异步任务中数据源的正确切换。
  2. 在任务中手动设置数据源:在每个异步任务中显式地设置数据源。

示例代码

下面是一些可能的解决方案:

1. 自定义线程池

创建一个自定义的线程池,并确保线程池中的线程可以正确传递数据源上下文。

java 复制代码
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(100);
    executor.setQueueCapacity(50);
    executor.setThreadNamePrefix("async-");
    executor.setTaskDecorator(new ContextCopyingDecorator());
    executor.initialize();
    return executor;
}

自定义 TaskDecorator 用于复制数据源上下文:

java 复制代码
public class ContextCopyingDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        Map<String, Object> context = ContextHolder.getContext(); // 假设ContextHolder用于获取当前线程的数据源上下文
        return () -> {
            ContextHolder.setContext(context); // 设置数据源上下文
            try {
                runnable.run();
            } finally {
                ContextHolder.clearContext(); // 清除上下文,避免泄露
            }
        };
    }
}

完整代码

完整示例

以下是完整的代码示例:

java

typescript 复制代码
// ContextHolder.java
public class ContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setContext(String context) {
        contextHolder.set(context);
    }

    public static String getContext() {
        return contextHolder.get();
    }

    public static void clearContext() {
        contextHolder.remove();
    }
}

// ContextCopyingDecorator.java
public class ContextCopyingDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        String context = ContextHolder.getContext(); // 获取当前线程的数据源上下文
        return () -> {
            ContextHolder.setContext(context); // 设置数据源上下文
            try {
                runnable.run();
            } finally {
                ContextHolder.clearContext(); // 清除上下文,避免泄露
            }
        };
    }
}

// Configuration class to create a custom thread pool
@Configuration
public class ThreadPoolConfig {
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("async-");
        executor.setTaskDecorator(new ContextCopyingDecorator());
        executor.initialize();
        return executor;
    }
}

// In your service class where you run asynchronous tasks
public class MonitoringService {
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    @Autowired
    private TradeLogMapper tradeLogMapper;

    @Autowired
    private SqlLogMapper sqlLogMapper;

    @Autowired
    private ErrorLogMapper errorLogMapper;

    public Map<String, List<String>> queryMonitoringMetricsOnSevenDays(QueryResourceTo projectInfo) {
        if (projectInfo.getServiceNameList() == null || projectInfo.getServiceNameList().isEmpty()) {
            return null;
        }

        CompletableFuture<Map<Object, Object>> slowInterfaceFuture = CompletableFuture.supplyAsync(() -> {
            ContextHolder.setContext("tms");
            try {
                return tradeLogMapper.querySlowInterfaceNumOnSevenDays(projectInfo.getServiceNameList());
            } finally {
                ContextHolder.clearContext();
            }
        }, taskExecutor);

        CompletableFuture<Map<Object, Object>> slowSqlFuture = CompletableFuture.supplyAsync(() -> {
            ContextHolder.setContext("tms");
            try {
                return sqlLogMapper.querySlowSqlNumOnSevenDays(projectInfo.getServiceNameList());
            } finally {
                ContextHolder.clearContext();
            }
        }, taskExecutor);

        CompletableFuture<Map<Object, Object>> errorInfoFuture = CompletableFuture.supplyAsync(() -> {
            ContextHolder.setContext("tms");
            try {
                return errorLogMapper.queryErrorNumOnSevenDays(projectInfo.getServiceNameList());
            } finally {
                ContextHolder.clearContext();
            }
        }, taskExecutor);

        CompletableFuture.allOf(slowInterfaceFuture, slowSqlFuture, errorInfoFuture).join();

        try {
            Map<Object, Object> slowInterface = slowInterfaceFuture.get();
            Map<Object, Object> slowSql = slowSqlFuture.get();
            Map<Object, Object> errorInfo = errorInfoFuture.get();
            HashMap<String, List<String>> result = new HashMap<>();

            // 在这里处理获取到的结果
            // ...

            return result;
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return null;
        }
    }
}

通过以上配置和代码,你可以在并发环境下正确使用指定的数据源。希望这些步骤能够解决你的问题。

2. 在任务中手动设置数据源

在异步任务中手动切换数据源:

java 复制代码
CompletableFuture<Map<Object, Object>> slowInterfaceFuture = CompletableFuture.supplyAsync(() -> {
    DynamicDataSourceContextHolder.push("tms");
    try {
        return tradeLogMapper.querySlowInterfaceNumOnSevenDays(projectInfo.getServiceNameList());
    } finally {
        DynamicDataSourceContextHolder.poll();
    }
}, taskExecutor);

CompletableFuture<Map<Object, Object>> slowSqlFuture = CompletableFuture.supplyAsync(() -> {
    DynamicDataSourceContextHolder.push("tms");
    try {
        return sqlLogMapper.querySlowSqlNumOnSevenDays(projectInfo.getServiceNameList());
    } finally {
        DynamicDataSourceContextHolder.poll();
    }
}, taskExecutor);

CompletableFuture<Map<Object, Object>> errorInfoFuture = CompletableFuture.supplyAsync(() -> {
    DynamicDataSourceContextHolder.push("tms");
    try {
        return errorLogMapper.queryErrorNumOnSevenDays(projectInfo.getServiceNameList());
    } finally {
        DynamicDataSourceContextHolder.poll();
    }
}, taskExecutor);

通过以上方式,可以确保在并发环境下也能正确使用指定的数据源。你可以选择其中一种方式来解决你的问题,具体选择取决于你的项目结构和上下文管理方式。

相关推荐
稚辉君.MCA_P8_Java13 小时前
Gemini永久会员 Java实现的暴力递归版本
java·数据结构·算法
许商13 小时前
【stm32】【printf】
java·前端·stm32
JIngJaneIL14 小时前
智慧物业|物业管理|基于SprinBoot+vue的智慧物业管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·论文·智慧物业管理系统
ANYOLY14 小时前
Redis 面试题库
java·redis·面试
懒惰蜗牛14 小时前
Day63 | Java IO之NIO三件套--选择器(下)
java·nio·选择器·selector·半包粘包·tcp缓冲区
JavaGuide14 小时前
美团2026届后端一二面(附详细参考答案)
java·后端
打工人你好14 小时前
如何设计更安全的 VIP 权限体系
java·jvm·安全
L.EscaRC14 小时前
Spring IOC核心原理与运用
java·spring·ioc
摇滚侠14 小时前
2025最新 SpringCloud 教程,Nacos-总结,笔记19
java·笔记·spring cloud
在逃热干面14 小时前
(笔记)获取终端输出保存到文件
java·笔记·spring