TCC 模式接入

分布式事务支持以 TCC 模式接入 SOFARPC、Dubbo 和 Spring Cloud 远程服务框架。本文将介绍如何定义接口、实现接口,并针对远程服务框架进行发布服务。

定义接口

TCC 参与者需要实现三个方法,分别是一阶段 Try 方法、二阶段 Confirm 方法以及二阶段 Cancel 方法。示例如下:

  • 基于 SOFARPC 和 Dubbo 框架

    在 TCC 参与者的接口中需要先加上@TwoPhaseBusinessAction注解(需引入 com.alipay.dtx.client.core.spi.TwoPhaseBusinessAction包),并声明这个三个方法,如下所示:

    public interface TccAction {
        @TwoPhaseBusinessAction(name = "yourTccActionName", commitMethod = "confirm", rollbackMethod = "cancel")
        public boolean try(
        BusinessActionContext businessActionContext, int a, int b);
    
        public boolean confirm(BusinessActionContext businessActionContext);
    
        public boolean cancel(BusinessActionContext businessActionContext);
    }

    @TwoPhaseBusinessAction注解属性说明:

    • name:TCC 参与者的名称,可自定义,但必须全局唯一。

    • commitMethod:指定二阶段 Confirm 方法的名称,可自定义。

    • rollbackMethod:指定二阶段 Cancel 方法的名称,可自定义。

  • 基于 Spring Cloud 框架

    1. 定义接口。

      在 Controller 中定义 Try、Cancel、Confirm。

      //使用RestRemoting声明参与者是REST接口
      @RestRemoting
      public interface AccountTccAction {
      
          /**
           * try 尝试
           *
           * BusinessActionContext 上下文对象,用来在两个阶段之间传递数据
           * BusinessActionContextParameter 注解的参数数据会被存入 BusinessActionContext
           * TwoPhaseBusinessAction 注解中commitMethod、rollbackMethod 属性有默认值,可以不写
           *
           * @param businessActionContext
           * @param userId
           * @param money
           * @return
           */
          @TwoPhaseBusinessAction(name = "accountTccAction", appName = "account")
          boolean prepareDecreaseMoney(BusinessActionContext businessActionContext,
                                       @BusinessActionContextParameter(paramName = "userId")Long userId,
                                       @BusinessActionContextParameter(paramName = "money")BigDecimal money);
      
          /**
           * commit 提交
           * @param businessActionContext
           * @return
           */
          boolean commit(BusinessActionContext businessActionContext);
      
          /**
           * cancel 撤销
           * @param businessActionContext
           * @return
           */
          boolean rollback(BusinessActionContext businessActionContext);
      }
      
    2. 通过 Controller 暴露服务。

      Controller 需要实现参与者 REST 接口。

      @RestController
      @RequestMapping("account")
      public class AccountController implements AccountTccAction{
          @Autowired
          private final AccountTccService accountTccService;
      
          /**
           * 账户扣钱
           * @param userId
           * @param money
           * @return
           */
          @PostMapping(value = "decrease-money")
          @ResponseBody
          public boolean decreaseMoney(@RequestBody BusinessActionContext businessActionContext, Long userId, BigDecimal money){
              accountTccService.prepareDecreaseMoney(businessActionContext, userId,money);
               return true;
          }
      
          /**
           * commit 提交
           * @param businessActionContext
           * @return
           */
          @PostMapping(value = "commit")
          @ResponseBody
          public boolean commit(@RequestBody BusinessActionContext businessActionContext){
              return accountTccService.commit(businessActionContext);
          }
      
          /**
           * cancel 撤销
           * @param businessActionContext
           * @return
           */
          @PostMapping(value = "rollback")
          @ResponseBody
          public boolean rollback(@RequestBody BusinessActionContext businessActionContext){
              return accountTccService.rollback(businessActionContext);
          }
      }

TCC 方法参数说明

  • Try:第一个参数类型必须是 com.alipay.dtx.client.core.api.domain.BusinessActionContext,后续参数的个数和类型可以自定义。

  • Confirm:有且仅有一个参数,参数类型必须是 com.alipay.dtx.client.core.api.domain.BusinessActionContext,后续为相应的参数名(businessActionContext)。

  • Cancel:有且仅有一个参数,参数类型必须是 com.alipay.dtx.client.core.api.domain.BusinessActionContext,后续为相应的参数名(businessActionContext)。

返回类型说明:

  • 二阶段的 Confirm 和 Cancel 方法的返回类型必须为 boolean 类型。

  • 一阶段的 Try 方法可以为 boolean 类型,也可以自定义返回值。

实现接口

TCC 接口实现支持用户自定义,实现类框架如下:

  • 基于 SOFARPC 和 Dubbo 框架

    public class TccActionImpl implements TccAction {
        @Override
        public boolean try(
        BusinessActionContext businessActionContext, int a, int b){
    
        }
    
        @Override
        public boolean confirm(BusinessActionContext businessActionContext) {
    
        }
    
        @Override
        public boolean cancel(BusinessActionContext businessActionContext) {
    
        }
    }
  • 基于 Spring Cloud 框架

    根据参与方定义的 Try、Cancel、Confirm,定义发起方调用的 Feign API。Try 方法需要加上@TwoPhaseBusinessAction注解(需引入com.alipay.dtx.client.core.spi.TwoPhaseBusinessAction包)。

    //使用FeignClient声明需要调用的参与方
    @FeignClient(name = "account")
    public interface AccountTccAction {
    
        /**
         * try 尝试
         *
         * BusinessActionContext 上下文对象,用来在两个阶段之间传递数据
         * BusinessActionContextParameter 注解的参数数据会被存入 BusinessActionContext
         * TwoPhaseBusinessAction 注解中commitMethod、rollbackMethod 属性有默认值,可以不写
         *
         * @param businessActionContext
         * @param userId
         * @param money
         * @return
         */
        @TwoPhaseBusinessAction(name = "accountTccAction", appName = "account")
        @PostMapping(value = "/account/decrease-money")
        boolean prepareDecreaseMoney(@RequestBody BusinessActionContext businessActionContext,
                                     @BusinessActionContextParameter(paramName = "userId")Long userId,
                                     @BusinessActionContextParameter(paramName = "money")BigDecimal money);
    
        /**
         * commit 提交
         * @param businessActionContext
         * @return
         */
        @PostMapping(value = "/account/commit")
        boolean commit(@RequestBody BusinessActionContext businessActionContext);
    
        /**
         * cancel 撤销
         * @param businessActionContext
         * @return
         */
        @PostMapping(value = "/account/rollback")
        boolean rollback(@RequestBody BusinessActionContext businessActionContext);
    }

各接口作用:

  • Try:初步操作。完成所有业务检查,预留必须的业务资源。

  • Confirm:确认操作。真正执行的业务逻辑,不作任何业务检查,只使用 Try 阶段预留的业务资源。因此,只要 Try 操作成功,Confirm 必定能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务能且只能成功一次。

  • Cancel:取消操作。释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性。

关于 TCC 参与者的实现规范,查看 更多信息

发布服务

在完成接口定义与实现后,参与者还需要发布服务,才能被分布式事务的发起方远程调用,从而保证分布式场景下的数据一致性。

  • 基于 SOFARPC 框架

    <bean id="tccActionImpl" class="com.xxx.xxx.TccActionImpl"/>
    <sofa:service ref="tccActionImpl" interface="com.xxx.xxx.TccAction">
         <sofa:binding.tr/>
    </sofa:service>
  • 基于 Dubbo 框架

    <bean id="tccActionImpl" class="com.xxx.xxx.TccActionImpl"/>
    <dubbo:service ref="tccActionImpl" interface="com.xxx.xxx.TccAction"/>
  • 基于 Spring Cloud 框架

    发起方支持通过@DtxTransaction注解或者调用 API 开启事务。

    • 注解方式

      //注入参与者 Feign 客户端
      private OrderTccAction orderTccAction;
      private AccountTccAction accountTccAction;
      private StorageTccAction storageTccAction;
      
      @DtxTransaction(bizType = "createOrderByTcc")
      @Override
      public void createOrder(OrderDO orderDO) {
          String orderNo=this.generateOrderNo();
          //创建订单
          orderTccAction.prepareCreateOrder(null,
                  orderNo,
                  orderDO.getUserId(),
                  orderDO.getProductId(),
                  orderDO.getAmount(),
                  orderDO.getMoney());
          //扣余额
          accountTccAction.prepareDecreaseMoney(null, orderDO.getUserId(),orderDO.getMoney());
          //扣库存
          storageTccAction.prepareDecreaseStorage(null, orderDO.getProductId(),orderDO.getAmount());
      }
    • 调用 API 的方式

      private DtxService dtxService;
      //注入参与者 Feign 客户端
      private OrderTccAction orderTccAction;
      private AccountTccAction accountTccAction;
      private StorageTccAction storageTccAction;
      @Resource(name = "dtxLocalTransactionTemplate")
      private TransactionTemplate dtxLocalTransactionTemplate;
      
      @Override
      public void createOrder(OrderDO orderDO) {
          try {
              dtxLocalTransactionTemplate.execute(status -> {
                  
                  String txId = dtxService.start("createOrderByTcc", UUIDGenUtil.genUUId() + "_FAILED");
                  String orderNo=this.generateOrderNo();
                  //创建订单
                  orderTccAction.prepareCreateOrder(null,
                          orderNo,
                          orderDO.getUserId(),
                          orderDO.getProductId(),
                          orderDO.getAmount(),
                          orderDO.getMoney());
                  //扣余额
                  accountTccAction.prepareDecreaseMoney(null, orderDO.getUserId(),orderDO.getMoney());
                  //扣库存
                  storageTccAction.prepareDecreaseStorage(null, orderDO.getProductId(),orderDO.getAmount());
                  retrun true;
              });
          
          } catch (Throwable t) {
          }
      }

发起方配置

对于发起方的应用来说,如果参与者与发起方分别属于不同的应用,则发起方需要订阅参与者 RPC 服务。

跨服务 TCC 参与者服务订阅

  • 基于 SOFARPC 发布的服务订阅

    <sofa:reference id="tccAction" interface="com.xxx.xxx.TccAction">
    <sofa:binding.tr/>
    </sofa:reference>
  • 基于 Dubbo 发布的服务订阅

    <dubbo:reference id="tccAction" interface="com.xxx.xxx.TccAction"/>

开启分布式事务

  • 为需要开启分布式事务的接口增加分布式事务注解 @DtxTransaction(com.alipay.sofa.dtx.client.aop.annotation.DtxTransaction),表明此方法内部需要开启分布式事务。

注意:注解需放在方法实现之前。

  • 方法内部可以执行下列事务操作(无数量与先后顺序的限制):

    • 调用本地 TCC 参与者的第一阶段 Try 方法调用。

    • 调用基于 SOFARPC 或 Dubbo 发布的跨应用 TCC 参与者的第一阶段 Try 方法。

业务方法正常返回则分布式事务提交,业务方法抛出异常则分布式事务回滚。

示例如下:

public class YourClass{
 @DtxTransaction(bizType="yourbizType")
 public void yourMethod(yourParams){
 try{
 // 调用 TCC 参与者一阶段try方法,try方法第一个参数值传'null';
 TCC.try();
 // 方法正常返回,事务提交
 }catch(Throwable t){
 // 出现异常,事务回滚
 throw t;
 }
 }
}
重要

该方法所在类需要配置成 Spring Bean 才能被分布式事务扫描器识别。

@DtxTransaction 注解属性说明如下表所示。

参数

说明

bizType

必选项。该属性指定业务类型,由用户根据自己业务场景自定义,长度不超过 30 字符。

timeout

该属性指定事务超时时间,默认为 30 秒,您可以根据需要自定义,单位为秒。 超时会自动触发当前事务回滚。

isolationLevel

该属性指定分布式事务的隔离级别,默认是为 READ_UNCOMMITTED(读未提交)。目前只支持 READ_UNCOMMITTED(读未提交)和 READ_COMMITTED(读已提交)两种隔离级别,可按需配置。

发起方类配置 Bean

要让 Spring 框架扫描发起方的注解,需要让 Spring 框架感知到发起方的 Bean,以上述“YourClass”为例:

<bean id="yourClassBean" class="com.xxx.xxx.YourClass"/>

更多信息

TCC 参与者实现规范

  • 实现两阶段接口:二阶段入参只包含事务数据,参与者处理业务数据之前需要根据事务数据关联到具体的业务数据。

  • 控制业务幂等性:参与者需要支持同一笔事务的重复提交和重复回滚,保证一笔事务能且只能成功一次。

  • 数据可见性控制:当一笔分布式事务正在处理中,此时如果有查询,则需要兼容未处理完的中间数据的可见性。一般通过文案展示告诉用户中间数据的存在,例如告诉用户当前冻结的金额有多少。

  • 隔离性控制:对于状态类数据,需要提供隔离性控制来允许不同事务操作同一个业务资源。例如账户余额,不同事务操作的金额是隔离的。

  • 允许空回滚,拒绝空提交:回滚请求处理时,如果对应的具体业务数据为空,则返回成功。提交请求处理时,如果对应的具体业务数据为空,则返回失败。