面试官:Spring在多线程环境下如何确保事务一致性

问题在现

我先把问题抛出来,大家就明白本文目的在于解决什么样的业务痛点了:

public void removeAuthorityModuleSeq(Integer authorityModuleId, IAuthorityService iAuthorityService, IRoleAuthorityService iRoleAuthorityService) {
   
    //1.查询出当前资源模块下所有资源,查询出来后进行删除
    deleteAuthoritiesOfCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService);
    //2.查询出当前资源模块下所有子模块,递归查询,当删除完所有子模块下的资源后,再删除所有子模块,最终删除当前资源模块
    deleteSonAuthorityModuleUnderCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService);
    //3.删除当前资源模块
    removeById(authorityModuleId);
}

如果我希望将步骤1和步骤2并行执行,然后确保步骤1和步骤2执行成功后,再执行步骤3,等到步骤3执行完毕后,再提交全部事务,这个需求该如何实现呢?

如何解决异步执行

上面需求第一点是: 如何让任务异步并行执行,如何实现二元依赖呢?

说到异步执行,很多小伙伴首先想到Spring中提供的@Async注解,但是Spring提供的异步执行任务能力并不足以解决我们当前的需求。

@Async注解原理简单来说,就是扫描IOC中的bean,给方法上标注有@Async注解的bean进行代理,代理的核心是添加一个MethodInterceptor即AsyncExecutionInterceptor,该方法拦截器负责将方法真正的执行包装为任务,放入线程池中执行。

下面我们先使用CompletableFuture来完成我们第一步需求:

public void removeAuthorityModuleSeq(Integer authorityModuleId, IAuthorityService iAuthorityService, IRoleAuthorityService iRoleAuthorityService) {
   
    CompletableFuture.runAsync(()->{
   
        //两个并行执行的任务
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() ->
                deleteAuthoritiesOfCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService),executor);
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() ->
                deleteSonAuthorityModuleUnderCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService), executor);
        //等待两个并行任务执行完后,再执行最后一个步骤
        CompletableFuture.allOf(future1,future2).thenRun(()->removeById(authorityModuleId)); 
    },executor);
}

多线程环境下如何确保事务一致性

我们已经完成了任务的异步执行化,那么又如何确保多线程环境下的事务一致性问题呢?

public void removeAuthorityModuleSeq(Integer authorityModuleId, IAuthorityService iAuthorityService, IRoleAuthorityService iRoleAuthorityService) {
   
    CompletableFuture.runAsync(()->{
   
        //两个并行执行的任务
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() ->
                deleteAuthoritiesOfCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService),executor);
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() ->
                deleteSonAuthorityModuleUnderCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService), executor);
        //等待两个并行任务执行完后,再执行最后一个步骤
        CompletableFuture.allOf(future1,future2).thenRun(()->removeById(authorityModuleId));
    },executor);
}

在Spring环境下说到事务控制,大家第一反应就想到使用@Transactional注解解决问题,但是这里显然行不通,为什么行不通呢?

我还是简单的对Spring事务实现原理进行一番概括:

事务王国回顾

事务管理大体分为三个流程:事务创建 ,事务执行,事务结束

事务创建涉及到一些属性的配置,如:

  • 事务的隔离级别
  • 事务的传播行为
  • 事务的超时时间
  • 是否为只读事务

由于涉及属性颇多,并且后期还有可能进行扩展,因此必须通过一个类来封装这些属性,在Spring中对应TransactionDefinition。

有了事务相关属性定义后,我们就可以利用TransactionDefinition来创建一个事务了,在Spring中局部事务由PlatformTransactionManager负责管理,创建事务也是由PlatformTransactionManager负责提供:

TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
   throws TransactionException;

如果我们希望追踪事务的状态,例如: 事务已完成,事务回滚等,那么就需要一个事务状态类贯穿当前事务的执行流程,在Spring中由TransactionStatus负责完成。

对于常见的数据源而言,通常需要记录的事务状态有如下几点:

  • 当前事务是否是新事务
  • 当前事务是否结束
  • 当前事务是否需要回滚(通过标记来判断,因此我也可以在业务流程中手动设置标记为true,来让事务在没有发生异常的情况下进行回滚)
  • 当前事务是否设置了回滚点(savePoint)

事务的执行过程就是具体业务代码的执行流程,这里就不多说了。

事务的结束分为两种情况:需要进行事务回滚或者事务正常提交,如果是事务回滚,还需要判断TransactionStatus 中的savePoint是否被设置了。

事务实现方式回顾

Spring中常见的事务实现方式有两种: 编程式和声明式。

声明式事务就是使用我们常见的@Transactional注解完成的,声明式事务优点就在于让事务代码与业务代码解耦,通过Spring中提供的声明式事务使用,我们也可以发觉我们只需要编写业务代码即可,而事务的管理基本不需要我们操心,Spring就像使用了魔法一样,帮我们自动完成了。

之所以那么神奇,本质还是依靠Spring框架提供的Bean生命周期相关回调接口和AOP结合完成的,简述如下:

  • 通过自动代理创建器依次尝试为每个放入容器中的bean尝试进行代理
  • 尝试进行代理的过程对于事务管理来说,就是利用事务管理涉及到的增强器advisor,即TransactionAttributeSourceAdvisor
  • 判断当前增强器是否能够应用与当前bean上,怎么判断呢?—> advisor内部的pointCut喽 !
  • 如果能够应用,那么好,为当前bean创建代理对象返回,并且往代理对象内部添加一个TransactionInterceptor拦截器。
  • 此时我们再从容器中获取,拿到的就是代理对象了,当我们调用代理对象的方法时,首先要经过代理对象内部拦截器链的处理,处理完后,最终才会调用被代理对象的方法。(这里其实就是责任链模式的应用)

对于被事务增强器TransactionAttributeSourceAdvisor代理的bean而言,代理对象内部会存在一个TransactionInterceptor,该拦截器内部构造了一个事务执行的模板流程:

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
   final InvocationCallback invocation) throws Throwable {
   
  //TransactionAttributeSource内部保存着当前类某个方法对应的TransactionAttribute---事务属性源
  //可以看做是一个存放TransactionAttribute与method方法映射的池子
  TransactionAttributeSource tas = getTransactionAttributeSource();
  //获取当前事务方法对应的TransactionAttribute
  final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
  //定位TransactionManager
  final TransactionManager tm = determineTransactionManager(txAttr);
        .....
        //类型转换为局部事务管理器
  PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
  final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

  if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
   
   //TransactionManager根据TransactionAttribute创建事务后返回
   //TransactionInfo封装了当前事务的信息--包括TransactionStatus
   TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

   Object retVal;
   try {
   
    //继续执行过滤器链---过滤链最终会调用目标方法
    //因此可以理解为这里是调用目标方法
    retVal = invocation.proceedWithInvocation();
   }
   catch (Throwable ex) {
   
    //目标方法抛出异常则进行判断是否需要回滚
    completeTransactionAfterThrowing(txInfo, ex
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyufeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值