本文主要介绍常见的分布式事务及其在.Net平台下的落地方案,参考了很多资料,主要来自DTM的官方文档、知乎
本文发布于我的个人博客站点:
文章链接
XA是由X/Open组织提出的分布式事务的规范,XA规范主要定义了(全局)事务管理器™和(局部)资源管理器(RM)之间的接口。本地的数据库如mysql在XA中扮演的是RM角色
XA一共分为两阶段:
第一阶段(prepare):即所有的参与者RM准备执行事务并锁住需要的资源。参与者ready时,向TM报告已准备就绪。
第二阶段 (commit/rollback):当事务管理者™确认所有参与者(RM)都ready后,向所有参与者发送commit命令。
目前主流的数据库基本都支持XA事务,包括mysql、oracle、sqlserver、postgre
流程图如下:

TCC分为三个阶段:
以跨银行转账为案例:
Try阶段
先把两个银行账户中的资金给它冻结住,不让操作了
Confirm阶段
执行实际的转账操作,A银行账户的资金扣减,B银行账户的资金增加
Cancel阶段
如果任何一个银行的操作执行失败,那么就需要回滚进行补偿
比如A银行账户如果已经扣减了,但是B银行账户资金增加失败了,那么就得把A银行账户资金给加回去
适用场景:
对一致性要求较高,常见于资金类的场景,可以使用TCC,自己编写大量的业务逻辑,自己判断一个事务中的各个环节是否ok,不ok就执行补偿/回滚代码
利用数据库+消息队列的方式实现异步分布式事务,流程如下:
A系统在本地一个事务里操作的同时,插入一条数据到消息表
接着A系统将这个消息发送到MQ
B系统接收到消息后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息
B系统执行成功后,就会更新自己本地消息表的状态以及A系统消息表的状态
如果B系统处理失败,那么就不会更新消息表状态,那么此时A系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到MQ中去,让B再处理
这个方案保证了最终一致性
哪怕B事务失败了,但是A会不断重发消息,直到B那边成功为止
Saga是这一篇数据库论文SAGAS提到的一个分布式事务方案。其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果各个本地事务成功完成那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作
例如我们要进行一个类似于银行跨行转账的业务,将A中的30元转给B,根据Saga事务的原理,我们将整个全局事务,切分为以下服务:
以下内容摘自DTM的文档
分布式系统最大的敌人可能就是NPC了,在这里它是Network Delay, Process Pause, Clock Drift的首字母缩写。我们先看看具体的NPC问题是什么:
分布式事务既然是分布式的系统,自然也有NPC问题。因为没有涉及时间戳,带来的困扰主要是NP。
Cancel执行时,Try未执行,事务分支的Cancel操作需要判断出Try未执行,这时需要忽略Cancel中的业务数据更新,直接返回
一般解决方案:针对该问题,在服务设计时,需要允许空补偿,即在没有找到要补偿的业务主键时,返回补偿成功,并将原业务主键记录下来,标记该业务流水已补偿成功
Try执行时,Cancel已执行完成,事务分支的Try操作需要判断出Cancel已执行,这时需要忽略Try中的业务数据更新,直接返回
一般解决方案:需要检查当前业务主键是否已经在空补偿记录下来的业务主键中存在,如果存在则要拒绝执行该笔服务,以免造成数据不一致
由于任何一个请求都可能出现网络异常,出现重复请求,所有的分布式事务分支操作,都需要保证幂等性(即多次请求和一次请求的结果是一致的,比如将余额修改为100,不管调用几次都是100,而将余额减100,多次调用结果会不一样,因此在设计时需要考虑到这个问题)
dtm中,首创了子事务屏障技术,使用该技术,能够非常便捷的解决异常问题,极大的降低了分布式事务的使用门槛:

子事务屏障技术的原理是,在本地数据库,建立分支操作状态表dtm_barrier,唯一键为全局事务id-分支id-分支操作(try|confirm|cancel)
在此机制下,解决了乱序相关的问题
这里说一下我的理解,在try阶段,如果try的op插入成功了,在cancel阶段先插入try,正常是失败的,如果插入成功的话,说明try阶段的时候插入失败了,也就是认为业务未执行,那么cancel中对应的补偿也就不需要调用了,也就是说通过在数据库中通过gid-branchid-op字段的来同时实现避免空补偿和防悬挂,同时gid-branchid-op是唯一的,因此也避免了重复请求的问题,接口的幂等性也不需要我们自己考虑了
官网:https://dtm.pub/
强烈推荐,支持多种事务模式,包括SAGA、TCC、二阶段消息
.Net的Demo:https://github.com/dtm-labs/dtmcli-csharp-sample 里面包含了SAGA、TCC和子事务屏障的示例代码,看完后再阅读一下dtm的.Net SDK和官方的文档,基本上就熟悉的差不多了
官网:https://github.com/dotnetcore/CAP
CAP 是一个基于 .NET Standard 的 C# 库,它是一种处理分布式事务的解决方案,同样具有 EventBus 的功能。
其底层通过数据库+消息队列的方式来保证分布式事务的可靠性,但是由于是异步的,对于补偿机制实现起来较为复杂,适合不需要补偿机制的场景(不断重试直到成功,重试达到一定次数后报警)
强一致性事务:
异步分布式事务(最终一致):
如果用DTM的话最好还是用子事务屏障,不需要自己考虑空悬挂、幂等、空补偿等问题,写起业务来十分的舒适,降低自己的心智负担
对于严格资金要求绝对不能错的场景,可以用TCC方案
如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案
你其实用任何一个分布式事务的这么一个方案,都会导致你那块儿代码会复杂10倍。很多情况下,系统A调用系统B、系统C、系统D,我们可能根本就不做分布式事务。如果调用报错会打印异常日志。
每个月也就那么几个bug,很多bug是功能性的,体验性的,真的是涉及到数据层面的一些bug,一个月就几个,两三个?如果你为了确保系统自动保证数据100%不能错,上了几十个分布式事务,代码太复杂;性能太差,系统吞吐量、性能大幅度下跌。
99%的分布式接口调用,不要做分布式事务,直接就是监控(发邮件、发短信)、记录日志(一旦出错,完整的日志)、事后快速的定位、排查和出解决方案、修复数据。
每个月,每隔几个月,都会对少量的因为代码bug,导致出错的数据,进行人工的修复数据,自己临时动手写个程序,可能要补一些数据,可能要删除一些数据,可能要修改一些字段的值。
比你做50个分布式事务,成本要来的低上百倍,低几十倍
trade off,权衡,要用分布式事务的时候,一定是有成本,代码会很复杂,开发很长时间,性能和吞吐量下跌,系统更加复杂更加脆弱反而更加容易出bug;好处,如果做好了,TCC、可靠消息最终一致性方案,一定可以100%保证你那块数据不会出错。
1%,0.1%,0.01%的业务,资金、交易、订单,我们会用分布式事务方案来保证,会员积分、优惠券、商品信息,其实不要这么搞了
以上内容摘自:小知在知乎的回答,参考文末链接3
以上内容及图片或部分内容摘自如下博客 or 回答
关于分布式事务这方面的内容,可以重点参考Dtm的官方手册,写的非常详细,地址:https://dtm.pub/guide/start.html,不仅包含了Dtm框架下各种分布式事务的实现方式,还有不同业务场景下的解决方案提供,非常方便学习
另外,大多数问题可以在dtm的github repository上查找issue,或者在CAP的github repository上查找issue,大多数问题都能在这里找到,或者进dtm的微信群交流