X/Open 组织提出了分布式事务处理的规范 DTP 模型(Distributed Transaction Processing),该模型中主要定义了三个基本组件,分别是git
通常,咱们称 TM 为事务的协调者,而称 RM 为事务的参与者。TM 与 RM 之间的通讯接口,则由 XA 规范来约定。github
在 DTP 模型的基础上,才引出了二阶段提交协议来处理分布式事务。redis
二阶段提交协议可以正确运转,须要具有如下前提条件:算法
实际上,条件2和3所要求的,现今绝大多数关系型数据库都能知足。数据库
当协调者节点从全部参与者节点得到的相应消息都为"赞成"时:微信
以下图所示:网络
若是任一参与者节点在第一阶段返回的响应消息为"终止",或者 协调者节点在第一阶段的询问超时以前没法获取全部参与者节点的响应消息时:并发
以下图所示:运维
二阶段提交协议除了协议自己具备的局限性以外,若是咱们把如下状况也考虑在内:异步
那么二阶段提交协议其实是存在不少问题的
协议自己的缺陷是指,在协议正常运行的状况下,不管全局事务最终是被提交仍是被回滚,依然存在的问题,而暂不考虑参与者或者协调者宕机,或者脑裂的状况。
参与者的本地事务开启后,直到它接收到协调者的 commit 或 rollback 命令后,它才会提交或回滚本地事务,而且释放因为事务的存在而锁定的资源。不幸的是,一个参与者收到协调者的 commit 或者 rollback 的前提是:协调者收到了全部参与者在一阶段的回复。
若是说,协调者一阶段询问多个参与者采用的是顺序询问的方式,那么一个参与者最快也要等到协调者询问完全部其它的参与者后才会被通知提交或回滚,在协调者未询问完成以前,这个参与者将保持占用相关的事务资源。
即便,协调者一阶段询问多个参与者采用的是并发询问的方式,那么一个参与者等待收到协调者的提交或者回滚通知的时间,将取决于在一阶段过程当中,响应协调者最慢的那个参与者的响应时间。
不管是哪种状况,参与者都将在整个一阶段持续的时间里,占用住相关的资源,参与者事务的处理时间增长。若此时在参与者身上有其它事务正在进行,那么其它事务有可能由于与这个延迟的事务有冲突,而被阻塞,这些被阻塞的事务,进而会引发其它事务的阻塞。
总而言之,总体事务的平均耗时增长了,总体事务的吞吐量也下降了。这会使得整个应用系统的延迟变高,吞吐量下降,可扩展性下降(当参与者变多的时候,延迟可能更严重)。
总的来讲,二阶段提交协议,不是一个高效的协议,会带来性能上的损失。
全局事务的隔离性与单机事务的隔离性是不一样的。
当咱们在单机事务中提到不容许脏读时,那么意味着在事务未提交以前,它对数据形成的影响不该该对其它事务可见。
当咱们在全局事务中提到不容许脏读时,意味着,在全局事务未提交以前,它对数据形成的影响不该该对其它事务可见。
在二阶段提交协议中,当在第二阶段全部的参与者都成功执行 commit 或者 rollback 以后,全局事务才算结束。但第二阶段存在这样的中间状态:即部分参与者已执行 commit 或者 rollback,而其它参与者还未执行 commit 或者 rollback。此刻,已经执行 commit 或者 rollback 的参与者,它对它本地数据的影响,对其它全局事务是可见的,即存在脏读的风险。对于这种状况,二阶段协议并无任何机制来保证全局事务的隔离性,没法作到“读已提交”这样的隔离级别。
若是在第一阶段,协调者发生了宕机,那么由于全部参与者没法再接收到协调者第二阶段的 commit 或者 rollback 命令,因此他们会阻塞下去,本地事务没法结束,
若是协调者在第二阶段发生了宕机,那么可能存在部分参与者接收到了 commit/rollback 命令,而部分没有,所以这部分没有接收到命令的参与者也会一直阻塞下去。
协调者宕机属于单点问题,能够经过另选一个协调者的方式来解决,但这只能保证后续的全局事务正常运行。而由于以前协调者宕机而形成的参与者阻塞则没法避免。若是这个新选择的协调者也宕机了,那么同样会带来阻塞的问题。
若是在第一阶段,某个参与者发生了宕机,那么会致使协调者一直等待这个参与者的响应,进而致使其它参与者也进入阻塞状态,全局事务没法结束。
若是在第二阶段,协调者发起 commit 操做时,某个参与者发生了宕机,那么全局事务已经执行了 commit 的参与者的数据已经落盘,而宕机的参与者可能还没落盘,当参与者恢复过来的时候,就会产生全局数据不一致的问题。
当网络闪断发生在第一阶段时,可能会有部分参与者进入阻塞状态,全局事务没法结束。
当发生在第二阶段时,可能发生部分参与者执行了 commit 而部分参与者未执行 commit,从而致使全局数据不一致的问题。
在二阶段提交中,当协调者宕机的时候,不管是在第一阶段仍是在第二阶段发生宕机,参与者都会由于等待协调者的命令而进入阻塞状态,从而致使全局事务没法继续进行。所以,若是在参与者中引入超时机制,即,当指定时间过去以后,参与者自行提交或者回滚。可是,参与者应该进行提交仍是回滚呢?悲观的作法是,统一都回滚。但事情每每没那么简单。
当第一阶段,协调者宕机时,那么全部被阻塞的参与者选择超时后回滚事务是最明智的作法,由于还未进入第二阶段,因此参与者都不会接收到提交或者回滚的请求,当前这个事务是没法继续进行提交的,由于参与者不知道其它参与者的执行状况,因此统一回滚,结束分布式事务。
在二阶段提交协议中的第二阶段,当协调者宕机后,因为参与者没法知道协调者在宕机前给其余参与者发了什么命令,进入了第二阶段,全局事务要么提交要么回滚,参与者若是引入超时机制,那么它应该在超时以后提交仍是回滚呢,彷佛怎么样都不是正确的作法。执行回滚,太保守,执行提交,太激进。
若是在二阶段提交协议中,在第一阶段和第二阶段中间再引入一个阶段,若是全局事务度过了中间这个阶段,那么在第三阶段,参与者就能够认为此刻进行提交的成功率会更大。但这难道不是治标不治本吗,当进入第三阶段,全局事务须要进行回滚时候,若是协调者宕机,那么参与者超时以后自行进行提交事务,就会形成全局事务的数据不一致。
再考虑参与者宕机的状况下,协调者应该在超时以后,对全局事务进行回滚。
总结起来,三阶段提交主要在二阶段提交的基础上,为了解决参与者和协调者宕机的问题,而引入了超时机制,并由于超时机制,附带引入中间这一层。
而且,三阶段提交并无解决二阶段提交的存在的脑裂的问题。
总而言之,二阶段和三阶段提交都没法完美地解决分布式事务的问题。关于三阶段提交更详细的算法和步骤,能够参考个人另一篇文章《分布式事务概览》
fescar 是阿里最近开源的一个关于分布式事务的处理组件,它的商业版是阿里云上的 GTS。
在其官方wiki上,咱们能够看到,它对XA 二阶段提交思考与改进。
在咱们上面提到的参与者中,这个参与者每每是数据库自己,在 DTP 模型中,每每称之为 RM,即资源管理器。fescar 的二阶段提交模型,也是在 DTP 模型的基础上构建。
fescar 2PC 与 XA 2PC 的第一个不一样是,fescar 把 RM 这一层的逻辑放在了 SDK 层面,而传统的 XA 2PC,RM的逻辑其实就在数据库自己。fescar 这样作的好处是,把提交与回滚的逻辑放在了 SDK 层,从而没必要要求底层的数据库必须对 XA 协议进行支持。对于业务来讲,业务层也不须要为本地事务和分布式事务两类不一样场景来适配两套不一样的数据库驱动。
基于咱们先前对 XA 2PC 讨论,XA 2PC 存在参与者宕机的状况,而 fescar 的 2PC 模型中,参与者其实是 SDK。参与者宕机这个问题之因此在 XA 2PC 中是个大问题,主要是由于 XA 中,分支事务是有状态的,即它是跟会话绑定在一块儿的,没法跨链接跨会话对一个分支事务进行操做,所以在 XA 2PC 中参与者一旦宕机,分支事务就已经没法再进行恢复。
fescar 2PC 中,参与者其实是SDK,而SDK是能够作高可用设计的。而且,在其第一阶段,分支事务实际上已是被提交了的,后续的全局上的提交和回滚,其实是操做数据的镜像,全局事务的提交会异步清理 undo_log,回滚则会利用保存好的数据镜像,进行恢复。fescar 的 2PC 中,其实是利用了 TCC 规范的无状态的理念。由于全局事务的执行、提交和回滚这几个操做间不依赖于公共状态,好比说数据库链接。因此参与者其实是能够成为无状态的集群的。
也就是说,在 fescar 2PC 中,协调者若是发现参与者宕机或者超时,那么它能够委托其余的参与者去作。
fescar 2PC 的第一阶段中,利用 SDK 中的 JDBC 数据源代理经过对业务 SQL 的解析,把业务数据在更新先后的数据镜像组织成回滚日志,利用本地事务 的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个 本地事务 中提交。
这就意味着在阻塞在第一阶段事后就会结束,减小了对数据库数据和资源的锁定时间,明显效率会变动高。根据 fescar 官方的说法,一个正常运行的业务,大几率是 90% 以上的事务最终应该是成功提交的,所以能够在第一阶段就将本地事务提交呢,这样 90% 以上的状况下,能够省去第二阶段持锁的时间,总体提升效率。
fescar 的这个设计,直接优化了 XA 2PC 协议自己的性能缺陷。
XA 2PC 中存在协调者宕机的状况,而 fescar 的总体组织上,是分为 server 层和 SDK 层的,server 层做为事务的协调者,fescar 的话术中称协调者为 TC(Transaction Coordinator ),称 SDK 为 TM(Transaction Manager)。截止至这篇文章发表前,fescar 的server层高可用还未实现,依据其官方的蓝图,它可能会采用集群内自行协商的方案,也可能直接借鉴高可用KV系统。自行实现集群内高可用方案,可能须要引进一套分布式一致性协议,例如raft,我认为这是最理想的方式。而直接利用高可用KV系统,例如 redis cluster,则会显得系统太臃肿,但实现成本低。
XA 2PC 是没有机制去支持全局事务隔离级别的,fescar 是提供全局事务的隔离性的,它把全局锁保存在了 server 层。全局事务隔离级别若是过高,性能会有很大的损耗。目前的隔离界别默认是读未提交,若是须要读已提交或者更高的级别,就会涉及到全局锁,则意味着事务的并发性会受影响。应用层业务层应该选择合适的事务隔离级别。
不管是 XA 仍是 fescar,都未解决上述提到的脑裂的问题。脑裂的问题主要影响了全局事务的最后的提交和回滚阶段。
没有完美的分布式事务解决方案,即便是 fescar 或者 GTS,它们也必然须要人工介入。但脑裂问题是小几率事件,而且不是致命性错误,能够先经过重试的方法来解决,若是不行,能够收集必要的事务信息,由运维介入,以自动或者非自动的方式,恢复全局事务。
维基百科:二阶段提交
《2PC之踵?是时候升级二阶段提交协议了》 by Tim Yang
《分布式事务概览》by beanlam
fescar wiki