作者:刘月财
本文次要介绍 seata-go 中 TCC 的设计思路、异样解决以及在实战中的应用。
Seata 是一款开源的分布式事务解决方案,致力于为现代化微服务架构下的分布式事务提供高性能和简略易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 等多种事务模式,帮忙用户解决不同场景下的业务问题。同时,Seata 还反对多语言编程,并且提供了繁难的 API 接口、丰盛的文档以及疾速上手的 samples 示例我的项目,也能疾速帮忙开发者入门并上手 Seata 的应用。
Seata-go 是 Seata 多语言生态中 golang 语言的实现计划,它致力于帮忙 golang 开发者也能应用 Seata 的能力来解决分布式事务场景的问题。 Seata-go 复用了 Seata TC 的能力,client 的性能和 Seata 保持一致。目前 Seata-go 曾经反对了 TCC 和 AT 模式,XA 模式正在测试中,预计会在 5 月份发版。Saga 模式正在设计和布局中,前面也会和 Seata 的 Saga 性能保持一致。
本文次要从以下几个角度,介绍 Seata-go 中的 TCC 模式的设计与应用:
- Seata-go TCC 实现原理
- Sata-go TCC 异样解决
- Seata-go 的瞻望
Seata-go TCC 实现原理
Seata-go 采纳了 getty 做 TCP 网络通信,齐全实现了 Seata 的通信协议。上层实现了配置核心和注册核心,也反对了很多的第三方框架的接入,比方 dubbo、grpc、gorm 等等,目前也正在踊跃和各个社区沟通,以反对更多框架的接入。Seata-go 繁难的零碎架构图如下:
先来简略回顾下 TCC 模式的含意。TCC 是分布式事务计划的一种实现,它采纳了二阶段提交协定,TCC 的全称是 Try-Confirm-Cancel,Try 是预留资源操作,Confirm 是提交操作,Cancel 是回滚操作。在 TCC 的一阶段中,先触发所有的子事务执行 Try 操作,如果所有的子事务的一阶段都执行胜利,那么会触发所有子事务二阶段执行 Confirm 操作,否则二阶段执行 Cancel 操作,以此来保障各个子事务状态的一致性。
TCC 是一种侵入式的分布式事务计划,Try、Confirm 和 Cancel 三个阶段的逻辑,都须要用户本人去实现。这样做意味着更多的代码量,以及对业务很大的入侵性;而长处是则比拟灵便,能由用户随便施展以解决更简单的分布式事务场景的问题。
在介绍 Seata-go 的 TCC 模式之前,先来回顾下 Seata 中的三个外围角色,即 TC、TM 和 RM。TC 是事务协调者,负责保护全局事务的状态,以及触发分支事务的提交和回滚动作;TM 是事务管理器,负责子事务的编排,以及全局事务的提交和回滚动作;RM 是资源管理器,治理分支事务处理的资源,比方 MySQL 数据库的操作等。
理解了这三个外围角色,就能够大抵的了解下 TCC 的事务流程,大抵分为以下几个步骤:
- TM 向 TC 发送申请,开启全局事务,TC 侧记录下全局事务的状态信息;
- TM 别离向所有的 RM 发送申请,RM 会向 TC 注册分支事务,而后执行 Try 阶段的逻辑;
- 如果当中某个 RM 给 TM 返回 Try 阶段执行失败,那 TM 就向 TC 发送“回滚全局事务”的申请。TC 收到后,就会向所有已执行 Try 的 RM 发送 Rollback 指令,触发 RM 执行 Cancel 逻辑;
- 如果所有的 RM 都给 TM 返回 Try 阶段执行胜利,那 TM 就向 TC 发送“提交全局事务”的申请。TC 收到后,就会向所有已执行 Try 的 RM 发送 Commit 指令,触发 RM 执行 Commit 逻辑。
至此,一个残缺的分布式事务就执行完了,以下是这个过程的流程图:
在 Seata-go 中,为了不便用户应用,提供了两种定义 TCC 服务办法,一种是实现 TwoPhaseInterface 接口,具体如下:
另一种是通过 tag 的形式来定义 TCC 服务,这种形式会绝对简单点,然而也更加的灵便:
第二种 tag 的计划,次要是为了满足一些非凡的场景,比如说,dubbo-go 的 server 和 client 是应用 tag 的形式来定义的,这个时候就须要应用 tag 的形式来定义 TCC 的服务。 个别状况举荐应用第一种继承接口的形式来做,比较简单。
在理论应用的时候,用户只须要做以下几件事件即可:
- 定义好本人的 TCC 服务,能够参考下面介绍的这两种形式之一都能够;
- 调用 TCC 的代理办法 NewTCCServiceProxy,将 TCC 服务的封装成代理;
- 编排好本人的子事务,传入到分布式事务的入口办法 WithGlobalTx 办法即可。
这里截图给大家看个例子,更具体的 samples 请参考 seata-go-samples 我的项目,地址为:https://github.com/seata/seata-go-samples
Seata-go TCC 异样解决
在理论应用 TCC 的时候,因为网络或是业务代码逻辑执行工夫等因素,可能会呈现以下的问题:
- 幂等: 在事务的一、二阶段,因为网络提早或是其余起因,RM 没有及时给 TC 或 TM 响应,导致 RM 被反复触发执行一、二阶段的逻辑,这个时候,须要思考业务的幂等;
- 空回滚: 因为网络提早或是其余起因,RM 在未收到 Try 申请的状况下,却收到了 Rollback 申请,造成空回滚的问题;
- 悬挂: 因为网络提早或是其余起因,RM 在未收到 Try 申请的状况下,收到了 Rollback 申请,解决完 Rollback 申请后,又收到了 Try 申请。这时全局事务已完结,会导致事务预留的资源始终无奈开释。
在 Seata-go 中,提供了两种解决方案,来帮忙用户解决这个问题。
第一种形式的原理和 Seata Java 的解决逻辑是一样的,都是借助 tcc_fence_log 事务状态表来做的:
用户须要在本人的业务数据库中,创立这个表,RM 在提交业务 SQL 的时候,同时会在这个表外面插入一条记录,这俩 SQL 是在一个本地事务中实现的。因为这个表中,“全局事务 ID+ 分支事务 ID”是一个联结主键,导致反复执行时会失败,这样就解决了 Try 阶段的幂等问题。在 Commit 和 Cancel 阶段时,会先查问这个表中分支事务的状态,而后才进行理论的逻辑,最初再更新状态。这样也能保障 Commit 和 Cancel 阶段的幂等性。
再来看看 Seata-go 是如何解决事务悬挂和空回滚的问题。如果一个 Rollbback 申请过去,RM 去查问 tcc_fence_log 表,发现没有记录(因为 RM 尚未收到 Try 申请),此时会往 tcc_fence_log 表插入一条记录,并标记状态为 suspend,而后间接退出,而不会去执行 Rollback 的逻辑,这样就防止了空回滚的问题。如果 RM 前面再收到 Try 申请,因为 tcc_fence_log 表曾经有一条记录,就会导致事务 SQL 无奈提交而失败(tcc_fence_log 会呈现主键抵触的问题),这样就防止了防悬挂的问题。
要实现这种形式,须要应用 Seata-go 提供的代理数据源,这些操作都会由代理数据源来实现,用户只须要开启开关,关注本人的业务 SQL 即可, 这个性能曾经实现,会在后续进行发版。
第二种形式,是通过用户手动的形式来实现的。原理和下面相似,然而 tcc_fence_log 的操作逻辑须要由用户本人实现,上面的截图形容了大抵的应用形式,详情能够参考这个 samples 代码:
https://github.com/seata/seata-go-samples/tree/main/tcc/fence
Seata-go 瞻望
Seata-go 社区近期与不少国内 go 语言微服务框架以及 ORM 框架背地的开发社区达成单干,比方 GORM 框架,曾经集成到了 Sample 中,后续会将更多的 ORM 框架集成在 Seata-go-Samples 我的项目中。与 MOSN 社区的单干也在推动中,可实现真正的基于 Seata 的 Transaction Mesh。
Seata-go 的 XA 模式会在 5 月份进行发版,届时 Seata-go 将反对 TCC、XA 和 AT 三种事务模式。Seata-go 后续的核心将会在 Saga 模式性能的开发上。
以后的 Saga 模式仅实现了服务编排的正向推动与反向 Rollback 能力,更进一步的服务编排则能够实现 DAG、定时工作、工作批量调度,笼罩工作流的所有流程,晋升用户在 Seata 这个平台上的应用体验。目前 Seata-go 依赖于 Seata Java 的 TC,依照这个工作打算,可能须要在将来的 Seata-go 版本中实现一个性能更弱小的 TC 调度。
Seata-go 社区目前正在疾速成长中,心愿有更多对开源感兴趣的小伙伴,退出到咱们社区来,一起助力 Seata-go 的成长!