spring 使用多线程,保证事务一致性

1、背景

最近接受到接口优化的任务,查看代码逻辑后发现在批量处理数据耗时长,想到使用多线程处理批量数据,又要保持原来的事务一致性。从网上检索出很多方法,经过尝试记录了2中方法。

2、实现方法

(1)、方法一

@Component
@Slf4j
public class MultiThreadingTransactionManager {
    /**
     * 数据源事务管理器
     */
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    private ThreadPoolTaskExecutor executorService;
    private long timeout = 120;

    public boolean execute(List<Runnable> runnableList,List<String> factorySchema) {
        /**
         * 用于判断子线程业务是否处理完成
         * 处理完成时threadCountDownLatch的值为0
         */
       CountDownLatch mainCountDownLatch = new CountDownLatch(1);
        /**
         * 用于等待子线程全部完成后,子线程统一进行提交和回滚
         * 进行提交和回滚时mainCountDownLatch的值为0
         */
        CountDownLatch threadCountDownLatch =  new CountDownLatch(runnableList.size());;
        /**
         * 是否提交事务,默认是true,当子线程有异常发生时,设置为false,回滚事务
         */
        AtomicBoolean isSubmit = new AtomicBoolean(true);
        for(int i =0;i<runnableList.size();i++){
            int finalI=i;
            executorService.execute(() ->{
                log.info("子线程: [" + Thread.currentThread().getName() + "]");
                // 判断别的子线程是否已经出现错误,错误别的线程已经出现错误,那么所有的都要回滚,这个子线程就没有必要执行了
                if (!isSubmit.get()) {
                    log.info("整个事务中有子线程执行失败需要回滚, 子线程: [" + Thread.currentThread().getName() + "] 终止执行");
                    // 计数器减1,代表该子线程执行完毕
                    threadCountDownLatch.countDown();
                    return;
                }
                SchemaContextHolder.setSchema(factorySchema.get(finalI));
                // 开启事务
                DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
                TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(defaultTransactionDefinition);
                try {
                    // 执行业务逻辑
                    runnableList.get(finalI).run();
                } catch (Exception exception) {
                    // 发生异常需要进行回滚,设置isSubmit为false
                    isSubmit.set(false);
                    log.info("子线程: [" + Thread.currentThread().getName() + "]执行业务发生异常,异常为: " + exception.getMessage());
                    throw new ServiceException(exception.getMessage());
                } finally {
                    // 计数器减1,代表该子线程执行完毕
                    threadCountDownLatch.countDown();
                }
                try {
                    // 等待主线程执行
                    mainCountDownLatch.await();
                } catch (Exception exception) {
                    log.info("子线程: [" + Thread.currentThread().getName() + "]等待提交或回滚异常,异常为: " + exception.getMessage());
                    throw new ServiceException(exception.getMessage());
                }
                try {
                    // 提交
                    if (isSubmit.get()) {
                        dataSourceTransactionManager.commit(transactionStatus);
                        log.info("子线程: [" + Thread.currentThread().getName() + "]进行事务提交");
                    } else {
                        dataSourceTransactionManager.rollback(transactionStatus);
                        log.info("子线程: [" + Thread.currentThread().getName() + "]进行事务回滚");
                    }
                } catch (Exception exception) {
                    log.info("子线程: [" + Thread.currentThread().getName() + "]进行事务提交或回滚出现异常,异常为:" + exception.getMessage());
                    throw new ServiceException(exception.getMessage());
                }
            });
        }
        // 等待子线程全部执行完毕
        try {
            // 若计数器变为零了,则返回 true
            boolean isFinish = threadCountDownLatch.await(timeout, TimeUnit.SECONDS);
            if (!isFinish) {
                // 如果还有为执行完成的就回滚
                isSubmit.set(false);
                log.info("存在子线程在预期时间内未执行完毕,任务将全部回滚");
            }
        } catch (Exception exception) {
            log.info("主线程发生异常,异常为: " + exception.getMessage());
            throw new ServiceException(exception.getMessage());
        } finally {
            // 计数器减1,代表该主线程执行完毕
            mainCountDownLatch.countDown();
        }
        // 返回结果,是否执行成功,事务提交即为执行成功,事务回滚即为执行失败
        return isSubmit.get();
    }
}

(2)、方法二

@Component
public class MultiThreadingTransactionManager {
    private final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private ThreadPoolTaskExecutor executorService;
    @Autowired
    DataSource multipleDataSource;

    /**
     * 生成一个事务管理器
     */
    private DataSourceTransactionManager genTransactionManager() {
        return new DataSourceTransactionManager(multipleDataSource);
    }


    /**
     * 执行的是无返回值的任务
     *
     * @param tasks      异步执行的任务列表
     * @param factorySchemas 异步执行任务需要用到的线程池,考虑到线程池需要隔离,这里强制要求传
     */
    public boolean execute(List<Runnable> tasks, List<String> factorySchemas) {
        try {
            // 参数有效性校验
            if (tasks == null || tasks.size() == 0) {
                return false;
            }
            DataSourceTransactionManager transactionManager = genTransactionManager();

            // 用于标记是否存在线程发生了异常
            AtomicBoolean ex = new AtomicBoolean();
            // 混合事务定义信息map,用于存储 TransactionStatus & TransactionResource
            Map<Integer, Map<TransactionStatus, TransactionResource>>  multiplyTransactionDefinition = new ConcurrentHashMap<>();
            // 开辟多线程,将CompletableFuture存储到taskFutureList,方便后面的管理
            List<CompletableFuture<?>> taskFutureList = new ArrayList<>(tasks.size());
            for(int i=0;i<tasks.size();i++){
                int finalI = i;
                taskFutureList.add(CompletableFuture.runAsync(() -> {
                            try {
                                // 1.填充值(事务状态 & 事务资源)到混合事务定义map中
                                multiplyTransactionDefinition.put(
                                        tasks.get(finalI).hashCode(),
                                        new HashMap<TransactionStatus, TransactionResource>(1){
                                            {
                                                put(openNewTransaction(transactionManager,factorySchemas.get(finalI)), TransactionResource.copyTransactionResource());
                                            }
                                        }
                                );
                                // 2.异步任务执行
                                tasks.get(finalI).run();
                            } catch (Throwable throwable) {
                                // 打印异常
                                throwable.printStackTrace();
                                // 异常标记
                                ex.set(Boolean.TRUE);
                                // 其他任务还没执行的不需要执行了
                                taskFutureList.forEach(completableFuture -> completableFuture.cancel(true));
                            }
                        }, executorService)
                );
            }

            try {
                // 阻塞直到所有任务全部执行结束---如果有任务被取消,这里会抛出异常,需要捕获
                CompletableFuture.allOf(taskFutureList.toArray(new CompletableFuture[]{})).get();
            } catch (Exception ignored) {}

            // 遍历所有的task做事务的提交或回滚
            for (int i = 0; i < tasks.size(); i++) {
                // 根据线程task的hashCode获取对应线程事务的状态和资源
                Map<TransactionStatus, TransactionResource> transactionStatusAndResource = multiplyTransactionDefinition.get(tasks.get(i).hashCode());
                // 根据事务状态和事务资源回滚事务
                for (Map.Entry<TransactionStatus, TransactionResource> entry : transactionStatusAndResource.entrySet()) {
                    // 载入事务资源
                    entry.getValue().loadTransactionResource();
                    // 发生了异常则进行回滚操作,否则提交
                    if(ex.get()) {
                        // 回滚
                        transactionManager.rollback(entry.getKey());
                        log.warn("已完成第 {} 个事务的回滚...", (i + 1));
                    } else {
                        // 提交
                        transactionManager.commit(entry.getKey());
                        entry.getKey().isNewTransaction();
                        log.info("已完成第 {} 个事务的提交...", (i + 1));
                    }
                        // 卸载事务资源
                     entry.getValue().unLoadTransactionResource();
                }
            }
            // 返回多线程执行结果(true表示成功,false表示失败)
            return !ex.get();
        } catch (TransactionException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 开启一个新事务
     * @param transactionManager 事务管理器
     */
    private TransactionStatus openNewTransaction(DataSourceTransactionManager transactionManager,String factorySchema) {
        SchemaContextHolder.setSchema(factorySchema);

        // JdbcTransactionManager根据TransactionDefinition信息来进行一些连接属性的设置(包括隔离级别和传播行为等)
        DefaultTransactionDefinition transactionDef = new DefaultTransactionDefinition();
        // 开启一个新事务---此时autocommit已经被设置为了false,并且当前没有事务,这里创建的是一个新事务
        return transactionManager.getTransaction(transactionDef);
    }




    /**
     * 保存当前事务资源,用于线程间的事务资源COPY操作
     */
    @Builder
    private static class TransactionResource {
        // 保存当前事务关联的资源--默认只会在新建事务的时候保存当前获取到的DataSource和当前事务对应Connection的映射关系--当然这里Connection被包装为了ConnectionHolder
        // 事务结束后默认会移除集合中的DataSource作为key关联的资源记录
        private final Map<Object, Object> resources;

        // 下面五个属性会在事务结束后被自动清理,无需我们手动清理
        // 事务监听者--在事务执行到某个阶段的过程中,会去回调监听者对应的回调接口(典型观察者模式的应用)---默认为空集合
        private Set<TransactionSynchronization> synchronizations;
        // 存放当前事务名字
        private final String currentTransactionName;
        // 存放当前事务是否是只读事务
        private final Boolean currentTransactionReadOnly;
        // 存放当前事务的隔离级别
        private final Integer currentTransactionIsolationLevel;
        // 存放当前事务是否处于激活状态
        private final Boolean actualTransactionActive;

        public TransactionResource(Map<Object, Object> resources, Set<TransactionSynchronization> synchronizations, String currentTransactionName, Boolean currentTransactionReadOnly, Integer currentTransactionIsolationLevel, Boolean actualTransactionActive) {
            this.resources = resources;
            this.synchronizations = synchronizations;
            this.currentTransactionName = currentTransactionName;
            this.currentTransactionReadOnly = currentTransactionReadOnly;
            this.currentTransactionIsolationLevel = currentTransactionIsolationLevel;
            this.actualTransactionActive = actualTransactionActive;
        }

        /**
         * 复制事务资源
         */
        public static TransactionResource copyTransactionResource() {
            final TransactionResource build = TransactionResource.builder()
                    //返回不可变集合
                    .resources(new HashMap<>())
                    //如果需要注册事务监听者,这里记得修改
                    .synchronizations(new LinkedHashSet<>())
                    .currentTransactionName(TransactionSynchronizationManager.getCurrentTransactionName())
                    .currentTransactionReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly())
                    .currentTransactionIsolationLevel(TransactionSynchronizationManager.getCurrentTransactionIsolationLevel())
                    .actualTransactionActive(TransactionSynchronizationManager.isActualTransactionActive())
                    .build();
            build.equipData(TransactionSynchronizationManager.getResourceMap());
            return build;
        }
        public void equipData(Map<Object,Object> from) {
            this.resources.putAll(from);
        }


        /**
         * 载入事务所需资源
         */
        public void loadTransactionResource() {
             resources.forEach((key,item)->{
                 System.out.println(((DataSource)key));
                         try{
                 TransactionSynchronizationManager.bindResource(key,item);
                         }catch (Exception e){

                         }
             });
            //如果是新事务才进行初始化
            if(!TransactionSynchronizationManager.isActualTransactionActive()) {
                TransactionSynchronizationManager.initSynchronization();
            }
            TransactionSynchronizationManager.setActualTransactionActive(actualTransactionActive);
            TransactionSynchronizationManager.setCurrentTransactionName(currentTransactionName);
            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(currentTransactionIsolationLevel);
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(currentTransactionReadOnly);
        }

        /**
         * 卸载事务资源
         */
        public void unLoadTransactionResource() {
            // 事务结束后默认会移除集合中的DataSource作为key关联的资源记录
            // DataSource如果重复移除,unbindResource时会因为不存在此key关联的事务资源而报错
            resources.keySet().forEach(key -> {
                if (!(key instanceof DataSource)) {
                    if(TransactionSynchronizationManager.getResource(key)!=null){
                        TransactionSynchronizationManager.unbindResource(key);
                    };
                }
            });
        }
    }
}

(3)、方法一测试类

@RestController
@RequestMapping("test")
public class TestController {
    @Autowired
    TestService testService;
    @Autowired
    MultiThreadingTransactionManager multiThreadingTransactionManager;
    @RequestMapping("test")
    public String test(){
        List<TestBean> list = new ArrayList<>();
        list.add(new TestBean("2",1));
        list.add(new TestBean("3",2));
        List<Runnable> runnableList = new ArrayList<>();
        list.forEach(testBean -> runnableList.add(() -> {
                testService.insert(testBean);
        }));
        boolean isSuccess = multiThreadingTransactionManager.execute(runnableList,"db9771");
        System.out.println(isSuccess);

        return "ok";
    };
}

(4)、方法二测试类

    @Override
    @Transactional
    public void test(String id, Integer num) {
        String sql1 ="INSERT INTO test (id, num) VALUES (%s, %d)";
        List<Runnable> runnables=new ArrayList<>();
        runnables.add(()->{
            jdbcTemplate.execute(String.format(sql1,"1",2));
        });
        runnables.add(()->{
            jdbcTemplate.execute(String.format(sql1,"3",4));
        });
        multiThreadingTransactionManager.execute(runnables, Arrays.asList("db6640","db6641"));
    }

3、总结

【方法一】中所有子线程在各自线程内开启事务,执行业务逻辑后,判断是否抛错,一旦抛错,会把全局AtomicBoolean置为false。所有子线程完业务代码会等待主线程,全部子线程执行业务结束后,主线程等待结束,判断AtomicBoolean是什么状态,一旦false,所有子线程回滚,否则提交。
【方法二】中每个子线程创建事务,并将事务状态和事务资源存入map中,在子线程创建的事务复制一份,放入到集合中,主线程中遍历map,将复制的事务资源载入事务资源统一提交或回滚。如果像方法二中测试类一样,添加声明式事务@Transactional注解,会造成No value for key [com.course.datasource.DynamicDatasourceConfig$$EnhancerBySpringCGLIB$$3b6ae611@796613b7] bound to thread [http-nio-8081-exec-1]错误,原因是主线程开启了事务,子线程事务绑定的动态数据源和主线程一致,在遍历map提交事务后,会清理事务资源,主线程声明式事务又会清理一次,导致错误发生,所以方法二不能和声明式事务一起使用。
【区别】方法一,事务开启回滚提交在子线程中进行,通过原子的布尔型状态判断回滚或提交。
方法二,事务开启式子线程中,提交或回滚在主线程中。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值