什么是分布式事务?分布式事务的实现方式

知梧 1143 2023-06-19

本文关于什么是分布式事务?分布式事务的实现方式。

什么是分布式事务
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库数据一致性

分布式事务的产生的原因
数据库分库分表
当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,具体分库分表的原理在此不做解释,以后有空详细说,简单的说就是原来的一个数据库变成了多个数据库。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务。

应用SOA化
所谓的SOA化,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。

以上两种情况表象不同,但是本质相同,都是因为要操作的数据库变多了!

事务的ACID特性
原子性(A)
所谓的原子性就是说,在整个事务中的所有操作,要么全部完成,要么全部不做,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。

一致性(C)
事务的执行必须保证系统的一致性,就拿转账为例,A有500元,B有300元,如果在一个事务里A成功转给B50元,那么不管并发多少,不管发生什么,只要事务执行成功了,那么最后A账户一定是450元,B账户一定是350元。

隔离性(I)
所谓的隔离性就是说,事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。

持久性(D)
所谓的持久性,就是说一单事务完成了,那么事务对数据所做的变更就完全保存在了数据库中,即使发生停电,系统宕机也是如此。

分布式事务的应用场景
支付
最经典的场景就是支付了,一笔支付,是对买家账户进行扣款,同时对卖家账户进行加钱,这些操作必须在一个事务里执行,要么全部成功,要么全部失败。而对于买家账户属于买家中心,对应的是买家数据库,而卖家账户属于卖家中心,对应的是卖家数据库,对不同数据库的操作必然需要引入分布式事务。

在线下单
买家在电商平台下单,往往会涉及到两个动作,一个是扣库存,第二个是更新订单状态,库存和订单一般属于不同的数据库,需要使用分布式事务保证数据一致性。

常见的分布式事务解决方案
基于XA协议的两阶段提交
XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如***、***这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:

image.png

 image.png

总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。

消息事务+最终一致性
所谓的消息事务就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性

A系统向消息中间件发送一条预备消息
消息中间件保存预备消息并返回成功
A执行本地事务
A发送提交消息给消息中间件

通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:

步骤一出错,则整个事务失败,不会执行A的本地操作
步骤二出错,则整个事务失败,不会执行A的本地操作
步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息
步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务
基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。

虽然上面的方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。

TCC编程模式
所谓的TCC编程模式,也是两阶段提交的一个变种。TCC提供了一个编程框架,将整个业务逻辑分为三块:Try、Confirm和Cancel三个操作。以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,则进入Cancel阶段,会去恢复库存。总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。

分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。不控制就是不引入分布式事务,部分控制就是各种变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。作为技术人员,一定不能忘了技术是为业务服务的,不要为了技术而技术,针对不同业务进行技术选型也是一种很重要的能力。


分布式事务的实现方式

二阶段提交(2PC)

二阶段提交(2PC)是分布式事务中最强大的事务类型之一,二阶段提交就是分两个阶段提交,第一阶段询问各个事务数据源是否准备好,第二阶段才是真正将数据提交给事务数据源。为了保证该事务可以满足ACID,就要引入一个协调者(Cooradinator)。其他的节点被成为参与者(Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。

工作流程

(一)第一阶段

(1)协调者会问所有的参与者结点,是否可以执行提交操作。

(2)各个参与者开始事务执行的准备工作:如:资源上锁,预留资源。

(3)参与者响应协调者,如果事务的准备工作成功,则回应“可以提交”,否则回应“拒绝提交”。

(二)第二阶段

(1)如果所有的参与者都回应“可以提交”,那么,协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,协调者收集各结点的“完成”回应后结束这个Global Transaction。

(2)如果有一个参与者回应“拒绝提交”,那么,协调者向所有的参与者发送“回滚操作”,并释放所有资源,然后回应“回滚完成”,协调者收集各结点的“回滚”回应后,取消这个Global Transaction。

缺陷

(1)同步阻塞:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

(2)单点故障:由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)。

(3)数据不一致:在二阶段提交的第二阶段中,当协调者向参与者发送Commit请求之后,发生了局部网络异常或者在发送Commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了Commit请求。而在这部分参与者接到Commit请求之后就会执行Commit操作。但是其他部分未接到Commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。

(4)二阶段提交无法解决的问题:协调者再发出Commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

三阶段提交(3PC)

相比2PC改变

(1)引入超时机制:同时在协调者和参与者中都引入超时机制。

(2)在第一阶段和第二阶段中插入一个准备阶段:保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

工作流程

(一)CanCommit阶段

3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送Commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

(1)协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。

(2)参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No。

(二)PreCommit阶段

协调者根据参与者的反应情况来决定是否可以进行事务的PreCommit操作。根据响应情况,有以下两种可能。

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

(1)协调者向参与者发送PreCommit请求,并进入Prepared阶段。

(2)参与者接收到PreCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中。

(3)如果参与者成功的执行了事务操作,则返回Ack响应,同时开始等待最终指令。

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

(1)协调者向所有参与者发送Abort请求。

(2)参与者收到来自协调者的Abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

(三)doCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况。

执行提交

(1)协调接收到参与者发送的Ack响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送DoCommit请求。

(2)参与者接收到DoCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。

(3)事务提交完之后,向协调者发送Ack响应。

(4)协调者接收到所有参与者的Ack响应之后,完成事务。

中断事务

(1)协调者向所有参与者发Abort请求。

(2)参与者接收到Abort请求之后,利用其在阶段二记录的Undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。

(3)参与者完成事务回滚之后,向协调者发送Ack消息。

(4)协调者接收到参与者反馈的Ack消息之后,执行事务的中断。

解决问题

(1)单点故障问题。

(2)减少阻塞。

提示:一旦参与者无法及时收到来自协调者的信息之后,会默认执行Commit,而不会一直持有事务资源并处于阻塞状态

缺陷

(1)一致性问题:由于网络原因,协调者发送的Abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了Commit操作。这样就和其他接到Abort命令并执行回滚的参与者之间存在数据不一致的情况 。

TCC(Try Confirm Cancel)

概念

一个完整的TCC业务由一个主业务服务和若干个从业务服务组成,主业务服务发起并完成整个业务活动,TCC模式要求从服务提供三个接口:Try、Confirm、Cancel。

(1)Try:完成所有业务检查,预留必须业务资源。

(2)Confirm:真正执行业务,不作任何业务检查;只使用Try阶段预留的业务资源;Confirm操作满足幂等性。

(3)Cancel:释放Try阶段预留的业务资源;Cancel操作满足幂等性。

工作流程

(1)第一阶段:主业务服务分别调用所有从业务的Try操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的Try操作都调用成功或者某个从业务服务的Try操作失败,进入第二阶段。

(2)第二阶段:活动管理器根据第一阶段的执行结果来执行Confirm或Cancel操作。如果第一阶段所有Try操作都成功,则活动管理器调用所有从业务活动的Confirm操作。否则调用所有从业务服务的Cancel操作。

与2PC比较

(1)位于业务服务层而非资源层。

(2)没有单独的准备(Prepare)阶段,Try操作兼备资源操作与准备能力。

(3)Try操作可以灵活选择业务资源的锁定粒度。

(4)开发成本较高。

(5)XA是资源层面的分布式事务,强一致性,在而阶段提交的整个过程中,一直会持有资源的锁。 TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。

缺陷

(1)Canfirm和Cancel的幂等性很难保证。

(2)这种方式缺点比较多,通常在复杂场景下是不推荐使用的,除非是非常简单的场景,非常容易提供回滚Cancel,而且依赖的服务也非常少的情况。

(3)这种实现方式会造成代码量庞大,耦合性高。而且非常有局限性,因为有很多的业务是无法很简单的实现回滚的,如果串行的服务很多,回滚的成本实在太高。

本地消息表

image.png

基本思路

(1)消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。

(2)消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

(3)生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

优缺点

(1)优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。

(2)缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

MQ事务消息(比如RocketMQ)

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

image.png

基本思路

(1)首先,发送一个事务消息,这个时候,RocketMQ将消息状态标记为Prepared,注意此时这条消息消费者是无法消费到的。

(2)接着,执行业务代码逻辑,可能是一个本地数据库事务操作。

(3)最后,确认发送消息,这个时候,RocketMQ将消息状态标记为可消费,这个时候消费者,才能真正的保证消费到这条数据。

如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事务消息,如果发现了Prepared消息,它会向消息发送端(生产者)确认。RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

优缺点

(1)优点:实现了最终一致性,不需要依赖本地数据库事务。

(2)缺点:实现难度大,主流MQ不支持,没有.NET客户端,RocketMQ事务消息部分代码也未开源。

MQ非事务消息(加独立消息服务、或者本地事务表)

另外一种实现,并不是所有的MQ都支持事务消息。也就是消息一旦发送到消息队列中,消费者立马就可以消费到。此时可以使用独立消息服务、或者本地事务表。

基本思路

(1)将消息先发送到一个我们自己编写的一个“独立消息服务”应用中,刚开始处于Prepare状态。

(2)业务逻辑处理成功后,确认发送消息,这个时候“独立消息服务”才会真正的把消息发送给消息队列。

(3)消费者消费成功后,Ack时,除了对消息队列进行Ack(图中没有画出),对于独立消息服务也要进行Ack,“独立消息服务”一般是把这条消息删除。而定时扫描Prepare状态的消息,向消息发送端(生产者)确认的工作也由独立消息服务来完成。

对于“本地事务表”,其实和“独立消息服务”的作用类似,只不过“独立消息服务”是需要独立部署的,而“本地事务表”是将“独立消息服务”的功能内嵌到应用中。

柔性事务:最大努力通知

最大努力通知型( Best-effort delivery)是最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果 不影响主动方的处理结果。典型的使用场景:如银行通知、商户通知等。最大努力通知型的实现方案,一般符合以下特点:

(1) 不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)。

(2)定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息。

Sagas长事务

在Sagas事务模型中,一个长事务是由一个预先定义好执行顺序的子事务集合和他们对应的补偿子事务集合组成的。典型的一个完整的交易由T1、T2、…、Tn等多个业务活动组成,每个业务活动可以是本地操作、或者是远程操作,所有的业务活动在Sagas事务下要么全部成功,要么全部回滚,不存在中间状态。

image.png

实现机制

(1)每个业务活动都是一个原子操作。

(2)每个业务活动均提供正反操作。

(3)任何一个业务活动发生错误,按照执行的反顺序,实时执行反操作,进行事务回滚。

(4)回滚失败情况下,需要记录待冲正事务日志,通过重试策略进行重试。

(5)冲正重试依然失败的场景,提供定时冲正服务器,对回滚失败的业务进行定时冲正。

(6)定时冲正依然失败的业务,等待人工干预。

优缺点

(1)优点:Sagas长事务模型支持对数据一致性要求比较高的场景比较适用,由于采用了补偿的机制,每个原子操作都是先执行任务,避免了长时间的资源锁定,能做到实时释放资源,性能相对有保障。

(2)缺点:Sagas长事务方式如果由业务去实现,复杂度与难度并存。


以上就是小编为大家整理的关于什么是分布式事务?分布式事务的实现方式的相关内容。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:分布式存储是什么?分布式存储原理是什么?
下一篇:数据库迁移 存储过程、包,触发器,脚本和应用程序,数据库的迁移
相关文章