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

问题描述:

这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·开发语言·算法
文丑颜不良啊1 天前
LeetCode876~900题目解析
java·leetcode
aloha_7891 天前
Java 基础、集合、异常与 JUnit 5 速查
java·junit·sqlserver
知守观1 天前
设计模式实战:从真实老项目中看标准模板方法与工厂的落地
java·spring·设计模式
独隅1 天前
Claude API 实战教程:从入门到完整项目
android·java·数据库·claude·claude code
Wang's Blog1 天前
Java 项目实战: 交友项目 - 圈子功能实现:总结
java·开发语言·交友
SL_staff1 天前
JVS私有化文档系统上线避坑指南:5大运维与业务协同断点及检测加固实践
java·开源·全栈
FinelyYang1 天前
考试系统简答题 AI 阅卷:覆盖度驱动、异步评分与人机协同落地实践
java·大数据·人工智能
hhhjhl1 天前
Yup 跨端三方库仓颉适配全流程
java·开发语言
用户3126874877201 天前
Java 对象内存布局:压缩指针、对齐填充与 JOL 实测
java