分布式事务支持以 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 框架
定义接口。
在 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); }
通过 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
注解属性说明如下表所示。
参数 | 说明 |
| 必选项。该属性指定业务类型,由用户根据自己业务场景自定义,长度不超过 30 字符。 |
| 该属性指定事务超时时间,默认为 30 秒,您可以根据需要自定义,单位为秒。 超时会自动触发当前事务回滚。 |
| 该属性指定分布式事务的隔离级别,默认是为 |
发起方类配置 Bean
要让 Spring 框架扫描发起方的注解,需要让 Spring 框架感知到发起方的 Bean,以上述“YourClass”为例:
<bean id="yourClassBean" class="com.xxx.xxx.YourClass"/>
更多信息
TCC 参与者实现规范
实现两阶段接口:二阶段入参只包含事务数据,参与者处理业务数据之前需要根据事务数据关联到具体的业务数据。
控制业务幂等性:参与者需要支持同一笔事务的重复提交和重复回滚,保证一笔事务能且只能成功一次。
数据可见性控制:当一笔分布式事务正在处理中,此时如果有查询,则需要兼容未处理完的中间数据的可见性。一般通过文案展示告诉用户中间数据的存在,例如告诉用户当前冻结的金额有多少。
隔离性控制:对于状态类数据,需要提供隔离性控制来允许不同事务操作同一个业务资源。例如账户余额,不同事务操作的金额是隔离的。
允许空回滚,拒绝空提交:回滚请求处理时,如果对应的具体业务数据为空,则返回成功。提交请求处理时,如果对应的具体业务数据为空,则返回失败。