seata分布式事务TCC模式介绍及推荐实践

作者:ptti

来源:恒生LIGHT云社区

通过前面的文章《seata入门介绍与seata-service部署与验证》,我们对seata已经有一个大体的认识,并且也了解到seata分布式事务AT模式,今天我们介绍SEATA分布式事务框架中TCC事务框架。打算从原理、实际原型演示、推荐的应用场景、注意事项等这几个维度去介绍TCC分布式事务。

首先介绍一下TCC,关于TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。

TCC作为一种与平台无关的抽象的开放的方案,有众多厂商有自己的实践。已知比较流行的有开源框架有ByteTCC, SEATA,恒生电子也有自己的TCC实现。了解SEATA TCC原理推荐阅读以下两个资料:

SEATA的官方介绍:SEATA.io/zh-cn/docs/…

蚂蚁金服相关开发者分享:www.bilibili.com/video/BV1pK…

对于TCC的解释:

  • Try阶段:尝试执行,完成所有业务检查(一致性),预留必须业务资源(准隔离性)
  • Confirm阶段:确认执行真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源,Confirm操作满足幂等性。要求具备幂等设计,Confirm失败后需要进行重试。
  • Cancel阶段:取消执行,释放Try阶段预留的业务资源 Cancel操作满足幂等性Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。

对TCC的理解概括如下:

  • 基于Confirm 与 Cancel任务执行必然成功的假设下,TCC仍然是一个2PC协议。
  • 在分布式环境下,它没有提供一个全局的锁的机制去控制资源竞争,仅是约定一个异常时处理回滚操作的流程。

原型演示:

本文涉及demo 基于Springcloud + Feign。采用Spring Boot工程,整合Spring Cloud Feign,业务数据库采用的Mysql8.0。

1、业务DB初始化,使用相关脚本构建业务db。初始化业务db.png

2、maven project部署,包含三个业务模块:account用户中心,order订单中心,storage库存中心。

模块.png

order是整体业务的入口。

order.png

关键依赖

  • spring-cloud-alibaba-SEATA-x.x.x.RELEASE-sources.jar SEATA与 Spring Cloud 原生组件(Feign, Hystrix …)适配与集成。
  • SEATA-all-x.x.x.jar SEATA 的核心业务逻辑。
  • SEATA-spring-boot-starter.x.x.x.jar 与spring boot的集成。

业务一致性核心接口

OrderTccAction.java
AccountTccAction.java
StorageTccAction.java
复制代码

核心接口.png

3、整体逻辑如下图:

整体逻辑.png

4、初始化数据信息

账户余额表

账户余额.png

订单数据表

订单表.png

库存信息表

库存表.png

场景演示1:演示一次正常下单成功过程

成功下单.png

请求返回正常

正常返回.png

数据库符合预期

z1.pngz2.pngz3.png

场景演示2:演示一次库存不足下单失败过程

库存不足.png

注:撤销订单的过程可能是异步完成;

核心代码:

核心代码.png

结果演示

失败结果.png

数据验证,订单并未生成

失败数据.png

以上,演示完毕,当然可以设计更多场景进行验证,这里不再展开。

关于seata的TCC模式的一些注意事项与推荐实践

1、关于空回归,悬挂和幂等问题,目前TCC模式下,业务同学必须自己处理这3个问题。如何设计事务查询表去解决这3个问题是具有一定的技术难度的。例如采用了一个全局的静态的ConcurrentHashMap ResultHolder.map 作为事务查询表解决了幂等问题,供参考。需要到网上查找更多相关资料学习。当然SEATA目前已经实现了一个通用的事务查询表功能,框架有能力自己去解决这3个问题。

2、二阶段的动作必须成功:一阶段成功后,二阶段的Confirm与Cancel 动作必须成功是TCC模式的约定与前提。开发者需要根据自己实际的业务场景,去判断相关的业务逻辑是放在Try中还是放在Confirm中。 在示例代码中存在一个比较典型的业务划分的方法,即在Try中操作frozen 字段锁定账户金额/产品库存,在Confirm中操作used字段去正式扣款/扣库存。如果没有frozen字段,那么扣款/扣库存的动作应该放置于Try方法中而不是Confirm方法中。

3、默认不支持并发多线程。

基于SEATA的源码分析与实际代码验证;TCC模式本身对多线程并发执行支持有限。

如果开辟其他线程执行任务调用(无论是本地调用,还是RPC/HTTP远程调用),不会加入本地全局事务。

基于此问题,已经在社区有相关实践经验的开发者确认,通过自己获取xId加入到相关线程的ThreadLocalMap中就可以把当前线程执行的任务join到全局事务中;

处理了本地调用场景的前提下,理论上远程调用的场景不需要再做额外处理,因为框架会在RPC/Http发起之前拦截请求,将相关的全局事务信息从Thread中获取,写入到请求中。

下面这个项目值得参考github.com/caohdgege/s…一下,他一定程度上解决了多线程场景的问题,但是尚未验证。因时间有限,这里并未进一步确认该方案是否100%准确无误。

4、TCC无默认数据隔离性。TCC通过约定了一个异常回滚的调用流程,仅仅是解决了分布式环境下事务的原子性问题。无论是分布式环境下,还是单机环境下的数据隔离性问题,完全需要业务同学自己考虑。在本Demo中可以看到Try or Confirm方法大多会被@Transactional 关键字修饰;单库的ACID性还需要依赖DB本身提供。

5、二阶段(Confirm与Cancel)可能会异步处理。TCC方案比较适合高并发的业务场景。基于confirm 与 rollback操作必然成功的假设前提,二阶段异步处理会极大提升高峰期业务的性能。比如本demo中的示例,客户落单后可以先只是冻结账户金额,并不直接划拨款项,等到业务高峰期结束,再约定一个时间(可能时某个定时任务)统一划拨,从而达到削峰的效果。

到此seata分布式事务TCC模式原理和使用分享完成。


想向技术大佬们多多取经?开发中遇到的问题何处探讨?如何获取金融科技海量资源?

恒生LIGHT云社区,由恒生电子搭建的金融科技专业社区平台,分享实用技术干货、资源数据、金融科技行业趋势,拥抱所有金融开发者。

猜你喜欢

转载自juejin.im/post/7047783500220465188