Go开源说第十七期 分布式事务DTM
大家好,很高兴到“Go开源说”跟大家分享DTM,我是叶东富,DTM作者,github:https://github.com/yedf
内容提纲
本次分享分为以下四个部分:
DTM是什么
产生的背景
可以解决什么问题
发展现状与未来
DTM是什么
我们按照开源项目的习惯,以一个类似Quick Start的例子来说明:
一个例子
业务场景:A跨行转给B 30元,A、B分属不同银行
需求要点:
需要保证A-30和B+30,都成功,或都失败
中间任何一个地方发生故障,不能影响最终的数据一致性
因数据保存在多个数据库实例,无法通过本地事务解决
解决方案DTM:A Distributed Transaction Manager
req := &gin.H{"amount": 30} // 微服务的载荷
// DtmServer为DTM服务的地址
saga := dtmcli.NewSaga(DtmServer, dtmcli.MustGenGid(DtmServer)).
// 添加一个TransOut的子事务,正向操作为url: qsBusi+"/TransOut", 逆向操作为url: qsBusi+"/TransOutCompensate"
Add(qsBusi+"/TransOut", qsBusi+"/TransOutCompensate", req).
// 添加一个TransIn的子事务,正向操作为url: qsBusi+"/TransIn", 逆向操作为url: qsBusi+"/TransInCompensate"
Add(qsBusi+"/TransIn", qsBusi+"/TransInCompensate", req)
// 提交saga事务,dtm会完成所有的子事务/回滚所有的子事务
err := saga.Submit()
在这个例子中:
定义了一个Saga事务
Saga事务中的第一个子事务TransOut负责扣减A的余额
Saga事务中的第一个子事务TransIn负责增加B的余额
完成了Saga事务定义之后,将事务提交给DTM
DTM将按顺序完成各项子事务
如果中间出现临时的网络错误,DTM会负责重试
如果业务返回失败,DTM会调用子事务的补偿操作,保证最终数据一致性
跨数据源、跨服务的数据一致性
DTM是跨数据源、跨服务的数据一致性解决方案,他又可以分为以下三类:跨数据库、跨服务、混合
易混淆的分布式数据库事务
现在涌现出构建在分布式系统上的数据库--NewSQL,能够随着分布式系统的动态扩容而大幅提高系统的服务能力,例如谷歌的Spanner、国产的TiDB。NewSQL的事务是在分布式系统上构建的,也被称之为分布式事务,但是与我们在前面介绍的分布式事务在解决问题类型不同,所使用的核心技术也不同。
NewSQL事务主要解决的是数据库扩展到多Host下的ACID,聚焦在数据库系统层面,而DTM聚焦在应用层和多数据源之间的数据一致性。
产生的背景
随着我们公司业务的发展,交易系统越来越复杂,交易并发也越来越高。原先一个交易是放在一个本地事务里完成,由数据库来保证ACID。这种架构,一方面要求把所有订单交易的操作在一个服务,导致无法微服务化,这样就会把所有订单涉及的子系统耦合进订单中,复杂度很高;另一方面,订单修改库存到订单提交的这段时间里,库存是被锁定的,购买同一个商品的所有订单会因为扣库存的行锁导致串行,并发度低。
我们系统采用的语言栈是Node,调研之后,决定之后采用Go作为主栈。如果把交易系统全部用Go改写之后再上线,周期十分漫长,对业务非常不友好。我们需要做渐进式的改造,需要分布式事务支持一部分服务Go编写,一部分服务Node编写,然后把相关服务组合成一个全局的分布式事务。
当前可选方案
我们调研了当时市面上的方案,最成熟、应用最广泛的是阿里开源的Seata,它是Java语言开发的,如果我们选用这个方案的话,需要把订单相关的系统,全部改造成Java,工作量巨大,预计要20人月以上。还有其他方案,例如hmily、tcc-transaction、ByteTCC,这些都是Java的方案,工作量都接近。
其他语言的分布式事务方案,没有看到成熟可用的,Go、PHP、Python、Node等语言搜索了一个遍,都未发现好的方案。
加上前面我们希望要一个渐进式的方案,需要支持多语言的子服务组合成一个全局事务,那么市面上的方案就更不可能了。综合评估下来,我们决定自己开发
DTM架构
我们开发的DTM架构如下:
图中左上角的Go客户端SDK等,都是轻量级的SDK,大约几十到几百行代码,新增一门语言支持非常容易。DTM服务器采用云原生友好的无状态服务,可以非常方便的动态扩容、做高可用,数据存储在数据库,和现在绝大多数公司的存储架构一致。
我们支持了类似如下的多语言,能够采用各种语言编写子服务,最终组合成一个全局事务:
可以解决什么问题
DTM提供了跨数据源分布式事务的一站式解决方案,支持的事务模式包括:可靠消息、事务消息、TCC、SAGA、XA。而在这些事务模式中,有一个难点问题是如果中间发生异常,导致子事务乱序要怎么解决。
异常
典型的异常分为三类,:
空补偿:可能出现回滚请求在正向操作前到达,此时需忽略
悬挂:可能出现回滚后,再收到正向操作,此时需忽略
幂等:可能出现重复请求,需要允许操作重试,不影响结果
通俗一点说,转账这类的分布式事务,可能会出现服务先收到撤销转账的请求,然后再收到转账的请求。下面是一个时序图,很好的演示了异常的情况:
其中:
5 Cancel在Try之前被调用,是空补偿
7 Cancel和5的重复,是重复请求
8 Try在Cancel之后执行,是悬挂操作
目前没有看到哪个项目在这方面有成熟好用的解决方案,包括Seata,都要求业务层面直接处理这类问题。业务直接处理这类问题,面临大量逻辑判断,复杂易出错,需要花费大量的时间进行调试测试,而且由于悬挂这样的问题难以模拟,可能要线上跑一段时间后才会出现。业务对于这样的情况只能表示很无奈,很头疼。
子事务屏障
DTM首创了子事务屏障,系统性的解决了上述的异常问题,效果示意图如下:
所有这些请求,到了子事务屏障后:不正常的请求,会被过滤;正常请求,通过屏障。开发者使用子事务屏障之后,前面所说的各种异常全部被妥善处理,业务开发人员只需要关注实际的业务逻辑,负担大大降低。
子事务屏障提供了方法BranchBarrier.Call,方法的原型为:
func (bb *BranchBarrier) Call(db *sql.DB, busiCall BusiFunc) error
业务开发人员,在busiCall里面编写自己的相关逻辑,调用BranchBarrier.Call。BranchBarrier.Call保证,在空回滚、悬挂等场景下,busiCall不会被调用;在业务被重复调用时,有幂等控制,保证只被提交一次。
子事务屏障会管理TCC、SAGA、事务消息等,也可以扩展到其他领域
有了子事务屏障的帮助,分布式事务的使用门槛大大降低,原先需要架构师才能胜任的工作,现在只需要普通开发人员就可以完成。未来DTM有可能成为推进微服务化的核心组件。
说完了所有事务模式共同的问题和解决方案后,我们下面看一下DTM对各个事务模式的支持:
可靠消息、事务消息
这两种事务模式保证消息至少被成功消费一次(或者说相关服务的执行最少成功一次),保证多个微服务被原子执行,它适合不需要回滚的业务场景。例如:
点击按钮,领取优惠券+会员,领优惠券+会员不需要回滚,这种情况适合可靠消息
注册成功后,领取优惠券+会员,这种情况适合事务消息。
第二种情况和第一种对比,差别主要是只有注册成功,才领取,如果注册失败,不可以领取,这种情况适合事务消息
DTM的支持如下:
如果是事务消息,那么在本地事务提交之前调用Prepare,提交之后调用Commit。如果没有本地事务,其实就是可靠消息,忽略Prepare调用即可。
XA
XA是标准二阶段提交,一阶段Prepare,二阶段Commit/Rollback。一阶段的所有Prepare成功,则Commit,否则Rollback。从修改数据开始,到Commit/Rollback结束,锁定数据长,并发度较低。
这种事务模式适合并发不高的订单转账等各项业务。
DTM的支持如下:
TCC
TCC的每个子事务有两个阶段:一阶段Try;二阶段Confirm/Cancel;Try中进行资源预留,例如冻结资金;一阶段如果全部成功,则Confirm,例如进行余额扣减,解冻资金;一阶段如果有一个子事务出现失败,则Cancel,例如解冻资金;
TCC模式不长期锁数据,并发高。常用于支付、订单类业务的拆分。
DTM的支持如下:
SAGA
SAGA每个子事务有正向分支和补偿分支,它在正向分支中直接修改数据,出错则补偿所有修改过的数据。
SAGA比TCC少一个分支,一致性比TCC弱。适用于积分换礼品等不涉及资金业务;也适用于对接较多第三方的长事务。
DTM的支持如下:
未支持的模式AT
AT为SEATA和部分其他的框架提供的事务模式,各方面特性类似于XA,性能比XA高,但低于其他模式。
这种模式的使用需要小心避免脏回滚,假如T1修改Row1,然后Row1被T2直接修改,接下来T1回滚,此时出现脏回滚,需要手动处理。Seata给出避免这种脏回滚的办法是,所有要修改Row1的访问,都必须使用Seata中的Transaction注解,这个要求在多团队协作,新旧系统兼容上难以保证。
因为脏回滚以及类似XA的特性,DTM支持了XA,并未支持AT模式,预计近期也不会支持。
DTM支持的其他场景
DTM还有一些其他典型的适用场景,例如:多个应用间的对账系统、发布文章后更新各类统计信息等
单服务多数据源
DTM对单服务多数据源的支持非常优雅,只需要将每个数据源的修改用一个子事务屏障包装起来即可。如图所示:
发展现状与未来
已完成的特性
从生产系统提炼而来,已接入三家
中英文文档
HTTP/gRPC支持
测试覆盖95%以上
Promethues监控
完善的CI/CD
支持的语言包括Go、Python、PHP、Node、Java、C#等
完整的线上部署文档,含直接部署、Docker、K8S
行业影响
干货推广文《分布式事务最经典的其中解决方案》被大量的自媒体自发转载(不是主动投稿),大约有几十个公众号,多个自媒体平台(包括脉脉,微博等)。该文章两个月时间就排到谷歌搜索“分布式事务"结果第二。
DTM在Github上面的star增长迅速,受到了很大的关注
下图是DTM和Seata在重要特性上的对比:
可以看到DTM在各项重要特性上不输于,甚至优于第一名,未来的发展潜力很大,他的目标是成为云原生架构的核心组件
结束
我们的项目地址是:https://github.com/yedf/dtm
文档是:https://dtm.pub
大家还可以通过扫码进群,一起交流~