分布式事务

事务

关于数据库事务的详细说明可见文档,事务四大特性ACID。

  • Atomicity(原子性)

指事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
通俗的说,就是所有操作要么全部成功,要么全部失败回滚。

  • Consistency(一致性)

是指事务执行前后,数据从一个状态到另一个状态必须是一致的。
比如A向B转账(A、B的总金额就是一个一致性状态),不可能出现A扣了钱,B却没收到的情况发生。

  • Isolation(隔离性)

是指当多个用户并发访问数据库时。
比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

  • Durablity(持久性)

指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

XA事务协议

XA是一个分布式事务协议,XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,MySQL5.7之后也支持XA分布式事务。而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

XA分布式事务协议的工作原理是:把各个微服务中的本地资源交给一个统一的事务管理器管理,事务管理器可以看做是事务协调者的角色(coordinator),各个本地资源管理器可以看做是事务参与者的角色(partcipant)。各个事务参与者之间不能直接通讯,而是通过事务协调者间接通讯,通俗来说,服务A怎么知道服务B是否执行成功?就是由事务协调者转告各个事务参与者了。

分布式和关系型数据库的区别_解决方案

分布式和关系型数据库的区别_事务_02

以上就是XA分布式事务执行流程,加入了全局的事务管理器作为协调者,在接收到发起带事务的业务方法后,发送prepare到各个事务参与者,各个事务参与者接收到prepare后,开启本地事务being,并执行本地业务流程,如果流程正常运行,则返回ready结果给事务协调者,告知准备就绪了,这时,如果各个事务参与者返回的结果都是ready,那么事务协调者就会再次发送一个全局事务提交global_commit的消息到各个事务参与者,最后,各个事务参与者受到global_commit后,提交本地事务commit。

总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在并发量很高的情况下,会带来性能瓶颈。

TCC事务

TCC是Try-Confirm-Cancel的简称。其核心思想是:每个需要开启分布式事务的业务方法,都要注册一个与其对应的检测、确认和撤销的操作,如下:

  • Try阶段:主要是对业务系统做检测的操作,没有问题就调用确认操作,有问题则调用取消操作。
  • Confirm阶段:确认执行业务操作。
  • Cancel阶段:取消执行业务操作。

分布式和关系型数据库的区别_分布式事务_03

分布式和关系型数据库的区别_解决方案_04

通过以上的流程,我们可以发现,TCC的分布式事务处理与XA的分布式事务处理流程是非常相似的, 调用try接口检查业务是否有异常的操作,类似于XA的prepare预提交,如果接口返回正常,则调用confirm确认执行业务,操作数据。

那如果其中有一个事务参与者在调用了try接口检测后,返回了异常给事务协调者,但是之前很可能已经有其他事务参与者调用了confirm接口,执行业务流程操作了数据,那这时,事务协调者就需要调用事务参与者的cancel接口,撤销之前修改的数据,达到类似回滚的效果。

不过XA是在跨库的DB层面,而TCC是应用层面,需要通过业务逻辑来实现分布式事务。TCC的实现方式优势在于:没有项目XA协议那样,把分布的资源统一管理,这就使得分布的资源不会被加锁,从而提高整体的吞吐量,所以这种分布式事务的解决方案,在性能和吞吐量要求高的应用使用的还是比较多的。而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。同时,confirm和cancel接口还要考虑幂等性的问题,因为confirm和cancel有可能会被多次调用。

可靠消息事务

可靠消息事务全称叫做可靠消息事务最终一致性,它通常没有像前面两种分布式事务解决方案那样有回滚或撤销数据的操作,而更多的是强调事务的补偿和重试。这里的可靠消息指的是消息队列中间件,消息队列其中一个特点就是消息可靠性,而该解决方案最终能达到事务一致,依靠的核心就是消息队列。先粗略的看看它执行流程:

分布式和关系型数据库的区别_数据库_05

从以上的执行流程可以发现,各个事务参与者都是相对独立的,不管在执行业务方法的过程中是否有异常,整体的业务流程都要先跑完,这个是该解决方案的前提。然后在调用事务参与者的业务方法的同时,往消息队列发送事务相关的消息,这样的话,出现异常的事务参与者再从消息队列中获取消息,重新执行本地的业务,达到补偿和重试的效果,整个事务中,不管中间有哪些参与者出错,但是最终还是事务一致的。

这种解决方案是所有解决方案中最柔性的,并且灵活度非常高,可以根据自己具体的业务场景做改变,同时,对比TCC来说,性能和吞吐量更高,并且对应用的侵入性更低。性能的提高体现在:没有了业务检测的环节,跟原本一样,该怎么调用远程方法就怎么调用,只是增加了一个往MQ发送消息的操作,但是该操作是异步的,而且MQ也是具备高吞吐量的特性。而侵入性更低体现在:MQ是中间件,只需要通过网络来调用即可,我们的业务方法并不会由于分布式事务解决方案的加入而有太多的改造,加入的代码更多是以外围扩展,或者组件的方式加入。

可靠消息事务最终一致性的解决方案优点很明显,但缺点也不是没有的,首先该解决方案设计过于复杂,组件很多,需要考虑的情况繁杂,实现起来比较困难。其次,虽然它是最柔性,最灵活和性能最高的,但是事务的原子性和一致性是最弱的,因为这种解决方案是以大家都不出错为前提的,如果其中有一个出错,自己通过补偿机制重新执行本地事务,但重试的过程本身就是不确定性的,比如说:A转钱给B,A账户扣钱了,但是B账户加钱时出错,在该机制下,A账户不会回滚,而是让B账户重新尝试加钱,那这就产生时间上的延迟了,很可能等了很久,都没见B账户把钱加上去,或者不断重试都是失败的,最终导致整个事务不一致,需要人工处理。

参考