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 的官方介绍:https://SEATA.io/zh-cn/docs/dev/mode/tcc-mode.html
蚂蚁金服相关开发者分享:https://www.bilibili.com/video/BV1pK411s7MS?from=search&seid=10347207598851791940
对于 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。
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 的集成。
业务一致性核心接口
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云社区,由恒生电子搭建的金融科技专业社区平台,分享实用技术干货、资源数据、金融科技行业趋势,拥抱所有金融开发者。
扫描下方小程序二维码,加入我们!
版权声明: 本文为 InfoQ 作者【恒生LIGHT云社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/74f331c99d1d2c16c63d35e6c】。文章转载请联系作者。
评论