【随笔记】Spring Propagation 分布式事务

 

简单说,一个事务是多个操作的集合,要么这些操作全部生效,要么全部都没有生效,就是一个通用流程;单体应用事务不算复杂,分布式中由于流程长,流转网元多,事务控制难度大。

  1. Spring 中 propagation 七种配置属性:

    • required:支持当前事务,如果当前没有事务,就新建一个事务,常见配置选择,也是默认配置;

    @Transactional
    public void service(){
     serviceA();
     serviceB();
    }
    @Transactional
    serviceA();
    @Transactional
    serviceB();
    • supports:支持当前事务,如果当前没有事务,就以非事务方式执行;

    serviceA被调用执行的时候,当前是没有事务的,所以service方法中会抛出异常导致serviceA回滚;

      public void service(){
           serviceA();
           throw new RunTimeException();
      }
      
      @Transactional(propagation=Propagation.SUPPORTS)
      serviceA();

    同上,serviceA在执行的时候还是没有事务,这时候又出现一种情况,就是使用数据库的底层数据源defaultAutoCommit=true,那么SQL 1 生效,SQL 2不生效,SQL 1不会回滚;如果defaultAutoCommit=false,两个SQL 失效,SQL 1不会提交;另外service有@Transaction标签,那么serviceA与调用者sercie公用事务,不在依赖数据库的提交方式,这时出现SQL 失效,会全部回滚;

      public void service(){
           serviceA();
      }
      
      @Transactional(propagation=Propagation.SUPPORTS)
      serviceA(){
          do sql 1
          1/0;
          do sql 2
      }
    • mandatory:支持当前事务,如果当前没有事务,就抛出异常;

      public void service(){
           serviceB();
           serviceA();
      }
      serviceB(){
          do sql
      }
      @Transactional(propagation=Propagation.MANDATORY)
      serviceA(){
          do sql 
      }

    这种情况执行 service会抛出异常,如果defaultAutoCommit=true,则serviceB是不会回滚的,defaultAutoCommit=false,则serviceB执行无效。

    • requires_new:新建事务,如果当前存在事务,把当前事务挂起;

    如果当前存在事务,先把当前事务相关内容封装到一个实体,然后重新创建一个新事务,接受这个实体为参数,用于事务的恢复。更直白的说法就是暂停当前事务(当前无事务则不需要),创建一个新事务。 针对这种情况,两个事务没有依赖关系,可以实现新事务回滚了,但外部事务继续执行。

      @Transactional
      public void service(){
          serviceB();
          try{
              serviceA();
          }catch(Exception e){
          }
      }
      serviceB(){
          do sql
      }
      @Transactional(propagation=Propagation.REQUIRES_NEW)
      serviceA(){
          do sql 1
          1/0; 
          do sql 2
      }

    当调用service接口时,由于serviceA使用的是REQUIRES_NEW,它会创建一个新的事务,但由于serviceA抛出了运行时异常,导致serviceA整个被回滚了,而在service方法中,捕获了异常,所以serviceB是正常提交的。如果需要serviceB一同回滚,不需要使用try catch serviceA。

    • not_supported:以非事务方式执行,如果当前存在事务,就把当前事务挂起;

    说明:如果当前存在事务,挂起当前事务,然后新的方法在没有事务的环境中执行,没有spring事务的环境下,SQL 的提交完全依赖于 defaultAutoCommit属性值 。

      @Transactional
      public void service(){
           serviceB();
           serviceA();
      }
      serviceB(){
          do sql
      }
      @Transactional(propagation=Propagation.NOT_SUPPORTED)
      serviceA(){
          do sql 1
          1/0;
          do sql 2
      }

    当调用service方法的时候,执行到serviceA方法中的1/0代码时,抛出了异常,由于serviceA处于无事务环境下,所以 SQL 1是否生效取决于defaultAutoCommit的值,当defaultAutoCommit=true时,SQL 1是生效的,但是service由于抛出了异常,所以serviceB会被回滚。

    • never:以非事务方式执行,如果当前存在事务,则抛出异常;

    说明: 如果当前存在事务,则抛出异常,否则在无事务环境上执行代码.

      public void service(){
          serviceB();
          serviceA();
      }
      serviceB(){
          do sql
      }
      @Transactional(propagation=Propagation.NEVER)
      serviceA(){
          do sql 1
          1/0;
          do sql 2
      }

    调用service后,若defaultAutoCommit=true,则serviceB方法及serviceA中的SQL 1都会生效。

    • nested:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务;

    如果当前存在事务,则使用 SavePoint 技术把当前事务状态进行保存,然后底层共用一个连接,当NESTED内部出错的时候,自行回滚到 SavePoint这个状态,只要外部捕获到了异常,就可以继续进行外部的事务提交,而不会受到内嵌业务的干扰,但是,如果外部事务抛出了异常,整个大事务都会回滚。 首先需要在Spring 配置事务管理器,并且nestedTransactionAllowed=true。

      <bean id="dataTransactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="dataDataSource" />
              <property name="nestedTransactionAllowed" value="true" />
          </bean>

    然后在执行sercice;

      @Transactional
      public void service(){
          serviceA();
          try{
              serviceB();
          }catch(Exception e){
          }
      }
      serviceA(){
          do sql
      }
      @Transactional(propagation=Propagation.NESTED)
      serviceB(){
          do sql1
          1/0;
          do sql2
      }

    serviceB是一个内嵌的业务,内部抛出了运行时异常,所以serviceB整个被回滚了,由于service捕获了异常,所以serviceA是可以正常提交的。 或者下面的情况:

      @Transactional
      public void service(){
           serviceA();
           serviceB();
           1/0;
      }
      @Transactional(propagation=Propagation.NESTED)
      serviceA(){
          do sql
      }
      serviceB(){
          do sql
      }

    由于service抛出了异常,所以会导致整个service方法被回滚。(这就是跟PROPAGATION_REQUIRES_NEW不一样的地方了,NESTED方式下的内嵌业务会受到外部事务的异常而回滚。)

  2. Feign 分布式事务

基于Redis的一种补偿方案,分布式事务通过txlcn实现,分布式规则Spring 的 propagation。这种方式对代码入侵小,TX-LCN在里面只是事务的搬运工作。

  • LCN模式

    事务搬运工,通过代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。当本地事务提交回滚或者关闭代理连接时将会执行假操作,该代理的连接由LCN连接池管理。这种方式也是使用最频繁的方式。

    模式有几种特点:

    • 模式对代码的入侵量极地,只在方法上增加Spring的Propagation注解即可;

    • 仅限本地存在连接对象且可以通过连接对象控制事务的模块生效;

    • 事务提交与回滚由本地事务方控制,对于数据的一致性有很高的保障;

    • 缺陷是由于代理的连接需要跟随事务发起方一同释放,增加了连接占用时间,但相对于便利性和可靠性,可以接受。

  • TCC模式

    TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),特性在于它不依赖资源管理器(RM)对XA的支持,而是通过对业务提供的业务逻辑来调度实现分布式事务。主要就是分三步:Try:尝试执行事务、Congirm:确认执行事务、Cancel:取消执行事务。

    模式的特点:

    • 代码嵌入性高,每个需要进行事务管理的方法都需要增加三种步骤;

    • 对有没有本地事务控制的情况,都可以支持;

    • 一致性控制需要开发者去控制,支持使用性广泛,就是研发成本高。

  • TXC模式

    TXC模式的来源于淘宝,实现原理就是在执行SQL前,先查询一下SQL的影响数据,然后保存执行的SQL信息和创建一个锁。当需要回滚的时候,就把之前保存的数据回滚到数据库,目前锁使用的是Redis分布式锁控制。

    模式的特点:

    • 对代码入侵性低;

    • 仅限于SQL方式;

    • 每次执行都要先执行SQL保存影响数据,消耗量大;

  • 事务管理中心

    • 通过txlcn-tm包实现:引入对应版本依赖包;

    • 启动类增加@EnableTransactionManagerServer注解,增加对应的事务管理数据库地址;数据库用来保持组id,任务id之类的数据;

  • 服务实例(调用方)

    • 引入txlcn-tc包及txlcn-txmsg-netty包;

    • 在启动类增加@EnableDistributedTransaction注解;

    • 重点是在调用的业务层方法上增加@LcnTransaction注解,该注解不能写在controller方法上面,这样会造成分组不一致,导致事务失效;

  • 服务实例(被调用方)

    • 引入txlcn-tc包及txlcn-txmsg-netty包;

    • 在启动类增加@EnableDistributedTransaction注解;

    • 被调用的服务业务层方法上增加@TxcTransaction(propagation = DTXPropagation.REQUIRED)完成配置;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值