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

问题描述:

这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);

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

相关推荐
m0_5719575843 分钟前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟4 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity5 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天5 小时前
java的threadlocal为何内存泄漏
java
caridle6 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^6 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋36 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx