当前位置: 首页 > 科技观察

那些用Go实现的分布式事务框架之二

时间:2023-03-19 19:10:49 科技观察

本文转载自微信公众号《RememberGo》,作者吴钦库里。转载请联系RememberGo公众号。在上一篇文章的开头,我们主要介绍了Go实现的分布式事务框架seata-golang。一个对标seata的go语言实现,当然版本还是比java版本差很多。这次我们要介绍的是另一个go实现的分布式事务:dtm。首先看一下dtm的整体架构(源码官网)。我们再看看之前的seata架构图。从结构的角度来看,差异还不错。seata中的TC反对dam的TM。RM两边意思相同。seata中的TM对标dtm交易SDK。功能相同:第一阶段启动一个全局事务,执行每个RM分支事务。第二阶段根据RM第一阶段的执行结果决定调用TC(seata)|TM(dtm)commit或rollback。架构方面,个人感觉只是模块名称和图片不同而已。当然,在实现细节上还是有很大差异的。先简单介绍一下DTM的各个模块。TMTM层在代码中没有具体的主体结构,都是从函数之前的调用开始的。启动TM其实是启动了两个服务,http和grpc。http路由和gRPC接口既然提供了两个服务入口,自然有一个共同的部分来处理核心业务。TM对数据的存储管理不依赖接口,而是依赖common.DB结构。根据配置文件中DB.driver的值,底层数据库为mysql或postgres。再看DB结构,那么本质上不管是哪种数据库在底层,都是直接依赖gorm来操作数据。接下来,TM如何通知各个RM进行commit或者rollback呢?举一个TCC模式的例子。TCC的两个阶段。第一阶段:尝试。Try执行,调用每个RM自定义的try行为,预留必要的业务资源。Phase2:Confirm(在Phase1中,本次交易涉及的所有尝试行为均成功)。调用每个分支事务的Confirm方法真正执行业务,只使用try阶段预留的资源。Phase2:Cancel(任何在Phase1中涉及这个事务的try行为都会失败)。调用每个分支事务的Cancel方法释放第一阶段try预留的资源。从上面我们可以知道,在TCC模式下,TM在第二阶段通知每个分支事务Confirm或者Cancel。在将每个RM事务分支注册到TM时,TM最终会为分布式事务的每个参与者(RM)生成两条分支信息。就这样,没错,就是直接存储对应的RM资源操作地址。当TM收到commit或rollback命令后,在处理完自己的逻辑(通常是修改Gloable状态)后,需要开始处理每一个注册的分支事务。说白了就是需要调用每个分支事务对应的操作接口。这里的t.getProcessor()需要根据当前事务的类型(TCC、SAGA、XA)获取对应的处理器进行逻辑处理。当然,每个事务处理器只需要实现接口即可。实际调用RM资源服务地址时,分为http和grpc,由开发者自行决定。在v1.6之前的版本中,grpc请求很简单,粗暴的解析地址方法,然后连接。现在为了支持一些使用了gRPCResolver机制的微服务框架的接入,做了一个抽象。对[1]感兴趣的可以看看,这里就不介绍了。SDK对于SDK,各个交易模式是独立的,本质上没有关联。比如我们下面开始一个TCC分布式事务。这个分布式事务由两个服务组成,分别称为+30和-30服务。从上面的调用,我们还是可以还原出整体流程的。调用TM获取分布式id调用TccGlobalTransaction函数启动分布式事务。调用TMprepare(这一步只是为了检查第一步生成的分布式事务状态是否在prepare中,这里没看懂,此时执行分支还没有注册,不应该只有全局状态存在吗在初始化状态?)上一步没有问题是执行传入的闭包函数,即在CallBranch函数中向TM注册参与事务的TM分支。注册完成后,第一阶段开始调用各分支的try服务。各分支try服务调用完成后,根据第一阶段的结果决定通知TM是提交还是中止。另外,分布式事务中的一些常见问题:如空补偿、rehang等。一般情况下,业务需要自己处理这种场景,避免出现不可描述的错误。dtm中提供了相应的子事务屏障方案。其核心,其实就是利用了数据库独有的索引机制。当然,您必须为每个RM资源添加一个新表。上面说了dtm的TM角色本质上对应的是seata中的TC,只是它们的处理方式不同。dtm中的TM会根据注册时各个分支保存的地址决定通过http或者rpc调用各个RM操作,TM直接向RM发起请求。在seata-go的实现中,TC不参与直接调用RM。记得上一篇文章提到了一个双向流式RPC接口(BranchCommunicate)。TC通过该接口将相应的分支处理信息传递给RM管理器。然后RM管理器根据事务类型选择对应的事务管理器进行处理,最后调用对应事务类型管理器的BranchCommit方法。下面是一个TCC事务类型管理器的处理。对应的事务RM管理器如何通知和处理每个RM资源。原理是我在上一篇文章中提到的作者实现的一种全局事务代理模式,本质上是利用go的反射实现的。有兴趣的可以自己去看源码,也可以看看作者对全局事务代理实现的介绍[2]。小结本文主要介绍dtm实现的一些细节。从这两篇文章中,我们大致可以看出一些实现上的差异,更多的细节还得自己去发现。最后,我想再问几个问题。你在日常开发中都在哪些场景下使用分布式事务?用的是哪个框架还是自研的?或者你如何解决分布式环境中的一致性问题?相关https://zhuanlan.zhihu.com/p/351391359https://dtm.pub/protocol/support.html