分布式服务
分布式服务框架是大型互联网架构的基础组件之一,目标是能让各个业务服务化,并且在服务化框架的管理下能够实现基本的 RPC 功能,以及必要的服务发现、服务治理、熔断、限流等高级功能。Dubbo 是阿里开源的比较知名的分布式服务化框架。
服务化的好处显而易见,能让各个系统解耦,便于系统拆分,服务之间的调用能很好的被管理起来,无状态的服务可以部署多个实例,提升系统的处理能力。但是系统拆分过后,会有一个明显的问题要解决,即分布式事务的问题。比如一次请求需要对两个服务分别执行一次写操作,当一个服务的写操作失败后,另一个服务的已经持久化的写操作如何有效地进行回滚,是一个很关键的问题,回滚不到位的话,就会产生脏数据。
分布式事务的基本原理本质上都是两阶段提交协议(2PC),TCC (try-confirm-cancel)其实也是一种 2PC,只不过 TCC 规定了在服务层面实现的具体细节,即参与分布式事务的服务方和调用方至少要实现三个方法:try 方法、confirm 方法、cancel 方法。
服务调用方( Consumer )在 try 阶段调用提供方( Provider )的 try 方法,真整个 try 阶段一旦成功,接下来就相继调用各个服务参与者的 confirm 方法,而 try 阶段一旦失败,就立即相继调用各个服务参与者的 cancel 方法,保证整个事务的最终一致性。而一旦进入 confirm 或者 cancel 阶段,就会一直 confirm 或者 cancel 下去,系统应该提供恢复机制,即使在 confirm 阶段出错时,也能够保证立即或者定时 confirm,直到全部的服务 confirm 成功。
TCC-Transaction 是一种开源的 TCC 型事务的实现,源代码在 Github 可以找到,内部提供一个基于 dubbo 的例子。事实上,TCC-Transaction 并不依赖具体的 RPC 框架,用 dubbo 只是为演示例子的方便。
Dubbo-sample 整体流程
TCC-Transaction 的源码用 maven 管理,其中有一个 tcc-transaction-dubbo-sample 的子模块,跑 demo 之前需要提前执行在 src/main/dbscripts 中的数据库脚本。dubbo-sample 模拟的是一个经典的订单购物扣款的例子,其中包含的服务有:
- order 服务:负责发起下单操作
- capital 服务:管理用户的资金账户,对外提供资金扣款的操作
- redpacket 服务 : 管理用户的红包账户,对外提供红包扣款的操作
整个流程是这样的:order 服务创建订单,然后调用 capital 服务请求扣除资金账户金额,再调用 redpacket 服务请求扣除红包账户余额,当资金账户和红包账户余额在 try 阶段都成功后,order 服务开始执行 confirm 方法把订单置为成功,同时调用 capital 和 redpakcet 服务把转账订单置为成功。
Zookeeper 与 Dubbo
dubbo 的启动需要一个注册中心,源码中的例子中使用 zookeeper 作为注册中心,所以在启动例子之前需要先下载并启动 zookeeper . zookeeper 可以在 Apache 官网下载下来,解压之后将 confg 下的 zoo_sample.cfg 重命名为 zoo.cfg 后(即使用默认配置),通过启动脚本 bin/zkServer.sh start
启动 zookeeper 即可。由于 zookeeper 默认监听 2181 端口,要保证此端口不被其他程序占用。
之后需要在 IDE 中配置三个Tomcat,分别启动 tcc-transaction-dubbo-capital、tcc-transaction-dubbo-redpacket、tcc-transaction-dubbo-order .
启动后可按提示模拟支付请求,比如如下界面,可输入具体金额然后执行相应的支付流程:
源码导读
在 order 的 controller 层接受页面上用户发起的下单和支付请求,发起 TCC 事务的代码为makePayment
paymentService.makePayment(order, redPacketPayAmount, order.getTotalAmount().subtract(redPacketPayAmount));
在 makePayment 方法内部是 TCC 的实现逻辑,包括 try、confirm、cancel 方法,并通过 @Compensable 注解的形式告诉 TCC-Transaction 框架如何找到这三个方法:
@Compensable(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment")
@Transactional
public void makePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
System.out.println("order try make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
order.pay(redPacketPayAmount, capitalPayAmount);
orderRepository.updateOrder(order);
String result = capitalTradeOrderService.record(buildCapitalTradeOrderDto(order));
String result2 = redPacketTradeOrderService.record(buildRedPacketTradeOrderDto(order));
}
同理,在 capital 和 redpacket 服务中出现 @Compensable 注解的地方同样实现了 try、confirm、cancel方法。通过断点调试的方法,可以观察出三个服务的执行顺序。
总结
通过 tcc-transaction 中的例子,可以初步体验 TCC 型事务的实现逻辑,作为使用方需要通过 tcc-transaction 提供的注解来标记相应的 try、confirm、cancel 方法。