seata分布式事务TCC模式介绍及推荐实践
作者:ptti通过前面的文章《seata入门介绍与seata-service部署与验证》,我们对seata已经有一个大体的认识,并且也了解到seata分布式事务AT模式,今天我们介绍SEATA分布式事务框架中TCC事务框架。打算从原理、实际原型演示、推荐的应用场景、注意事项等这几个维度去介绍TCC分布式事务。
来源:恒生LIGHT云社区
首先介绍一下TCC,关于TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。
TCC作为一种与平台无关的抽象的开放的方案,有众多厂商有自己的实践。已知比较流行的有开源框架有ByteTCC, SEATA,恒生电子也有自己的TCC实现。了解SEATA TCC原理推荐阅读以下两个资料:
SEATA的官方介绍:https://SEATA.io/zh-cn/docs/d...
蚂蚁金服相关开发者分享:https://www.bilibili.com/vide...
对于TCC的解释:
- Try阶段:尝试执行,完成所有业务检查(一致性),预留必须业务资源(准隔离性)
- Confirm阶段:确认执行真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源,Confirm操作满足幂等性。要求具备幂等设计,Confirm失败后需要进行重试。
- Cancel阶段:取消执行,释放Try阶段预留的业务资源
Cancel操作满足幂等性Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。
- 基于Confirm 与 Cancel任务执行必然成功的假设下,TCC仍然是一个2PC协议。
- 在分布式环境下,它没有提供一个全局的锁的机制去控制资源竞争,仅是约定一个异常时处理回滚操作的流程。
本文涉及demo 基于Springcloud + Feign。采用Spring Boot工程,整合Spring Cloud Feign,业务数据库采用的Mysql8.0。
1、业务DB初始化,使用相关脚本构建业务db。
文章图片
2、maven project部署,包含三个业务模块:account用户中心,order订单中心,storage库存中心。
文章图片
order是整体业务的入口。
文章图片
关键依赖
- 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
文章图片
3、整体逻辑如下图:
文章图片
4、初始化数据信息
账户余额表
文章图片
订单数据表
文章图片
库存信息表
文章图片
场景演示1:演示一次正常下单成功过程
文章图片
请求返回正常
文章图片
数据库符合预期
文章图片
文章图片
文章图片
场景演示2:演示一次库存不足下单失败过程
文章图片
注:撤销订单的过程可能是异步完成;
核心代码:
文章图片
结果演示
文章图片
数据验证,订单并未生成
文章图片
以上,演示完毕,当然可以设计更多场景进行验证,这里不再展开。
关于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中获取,写入到请求中。
下面这个项目值得参考https://github.com/caohdgege/seata-async一下,他一定程度上解决了多线程场景的问题,但是尚未验证。因时间有限,这里并未进一步确认该方案是否100%准确无误。
4、TCC无默认数据隔离性。TCC通过约定了一个异常回滚的调用流程,仅仅是解决了分布式环境下事务的原子性问题。无论是分布式环境下,还是单机环境下的数据隔离性问题,完全需要业务同学自己考虑。在本Demo中可以看到Try or Confirm方法大多会被@Transactional 关键字修饰;单库的ACID性还需要依赖DB本身提供。
5、二阶段(Confirm与Cancel)可能会异步处理。TCC方案比较适合高并发的业务场景。基于confirm 与 rollback操作必然成功的假设前提,二阶段异步处理会极大提升高峰期业务的性能。比如本demo中的示例,客户落单后可以先只是冻结账户金额,并不直接划拨款项,等到业务高峰期结束,再约定一个时间(可能时某个定时任务)统一划拨,从而达到削峰的效果。
到此seata分布式事务TCC模式原理和使用分享完成。
想向技术大佬们多多取经?开发中遇到的问题何处探讨?如何获取金融科技海量资源?
恒生LIGHT云社区,由恒生电子搭建的金融科技专业社区平台,分享实用技术干货、资源数据、金融科技行业趋势,拥抱所有金融开发者。
扫描下方小程序二维码,加入我们!
文章图片
推荐阅读
- 深入浅出谈一下有关分布式消息技术(Kafka)
- mysql中视图事务索引与权限管理
- KubeDL HostNetwork(加速分布式训练通信效率)
- spring事务管理_01:事务管理框架+声明式事务
- 2018-03-11|2018-03-11 存储过程
- 实操Redission|实操Redission 分布式服务
- 分布式|《Python3网络爬虫开发实战(第二版)》内容介绍
- SpringBoot解决Shiro导致依赖注入的bean事务失效问题
- 2021年行业社群孵化的分布式商业趋势
- 啥是负载均衡、高并发、分布式、集群()