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

问题描述:

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

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

相关推荐
&白帝&1 小时前
JAVA JDK7时间相关类
java·开发语言·python
2301_818732061 小时前
用layui表单,前端页面的样式正常显示,但是表格内无数据显示(数据库连接和获取数据无问题)——已经解决
java·前端·javascript·前端框架·layui·intellij idea
狄加山6751 小时前
系统编程(线程互斥)
java·开发语言
星迹日1 小时前
数据结构:二叉树—面试题(二)
java·数据结构·笔记·二叉树·面试题
组合缺一1 小时前
solon-flow 你好世界!
java·solon·oneflow
HHhha.1 小时前
JVM深入学习(二)
java·jvm
叩叮ING2 小时前
正则表达式中常见的贪婪词
java·服务器·正则表达式
组合缺一2 小时前
Solon Cloud Gateway 开发:熟悉 Completable 响应式接口
java·gateway·reactor·solon
组合缺一2 小时前
Solon Cloud Gateway 开发:Route 的配置与注册方式
java·gateway·reactor·solon
栗豆包3 小时前
w179基于Java Web的流浪宠物管理系统的设计与实现
java·开发语言·spring boot·后端·spring·宠物