分布式事务

两阶段提交(2PC):

概念:

将事务的提交过程分为资源准备和资源提交两个阶段,由事务协调者来协调所有事务参与者,如果准备阶段所有事务参与者都预留资源成功,则进行第二阶段的资源提交,否则事务协调者回滚资源。

第一阶段(准备阶段):

  1. 协调者向所有参与者发送prepare请求(包含事务内容),询问是否可以提交事务,并等待答复。

  2. 各参与者执行本地事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)

  3. 如参与者执行成功则给协调者反馈同意,否则反馈中止表示事务不可以执行。

第二阶段(提交阶段):

协调者收到所有参与者准备的结果后,根据准备结果通知各个参与者进行事务提交或者事务回滚。

事务提交:当第一阶段所有参与者都反馈同意时,协调者发起提交事务的请求,当所有参与者都回复事务提交成功,则意味着完成事务,具体流程如下:

  • 协调者向所有参与者发出commit请求。

  • 参与者收到commit请求后正式执行事务提交操作,并释放在整个事务期间内占用的资源。

  • 参与者完成事务提交后,向协调者发送ACK消息。

  • 协调者收到所有参与者节点反馈的ACK消息后,完成分布式事务。

事务回滚:在第一阶段,若任意一个参与者返回终止信号,或者协调者等待超时,那么这个分布式事务就将回滚,具体流程如下:

  • 协调者向所有参与者发出rollback请求

  • 参与者利用阶段一写入的undo信息执行回滚,并释放在整个事务期间内占用的资源。

  • 参与者在完成事务回滚之后,向协调者发送回滚完成的ACK消息

  • 协调者收到所有参与者反馈的ACK消息后,取消事务。

缺点:

  • 性能很差:每个参与者从准备阶段就开始了事务,一直到所有参与者都完成事务提交,期间整个事务占用的资源都不会释放,参与者一直处于同步阻塞状态。

  • 数据一致性问题:在第二阶段(提交阶段)中,若只有部分参与者收到了commit请求(协调者部分发送失败或部分参与者接收失败),那么就会出现数据不一致的情况。

  • 可靠性差:在第二阶段(提交阶段)中,若协调者发生故障导致部分commit请求未发出,则未收到commit请求的参与者将一直处于阻塞的状态直到连接超时。

三阶段提交(3PC):

概念:

将事务的提交过程分为准备、预提交、提交三个阶段,与两阶段提交相比,3PC把2PC的准备阶段拆分为两个阶段,并且在第二阶段和第三阶段都超时机制。

第一阶段(准备阶段):

  • 协调者向所有参与者发出包含事务内容的canCommit请求,询问是否可以提交事务,并等待所有参与者答复。

  • 参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈yes并进入预提交阶段;否则反馈no表示事务不可执行需要中断。

第二阶段(预提交阶段):

事务执行:若协调者收到了所有参与者的反馈,并且都为yes

  • 协调者向所有参与者发送PreCommit请求。

  • 各参与者收到PreCommit请求后,执行本地事务操作,将undo和redo信息记入事务日志中(但不提交事务)

  • 如参与者执行成功则给协调者反馈同意,进入第三阶段,否则反馈中止表示事务不可以执行。

事务回滚:若存在参与者给协调者反馈no

  • 协调者向所有参与者发送擦callback请求。

  • 参与者收到callback请求后,或者参与者在等待超时之后,仍未收到协调者的请求,参与者执行事务中断。

第三阶段(提交阶段):

事务提交:若协调者收到了所有参与者的反馈,并且都为ack

  • 协调者向所有参与者发送doCommit请求。

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

  • 参与者完成事务提交后,向协调者发送ACK消息。

  • 协调者收到所有参与者节点反馈的ACK消息后,完成分布式事务。

事务回滚:若协调者收到了任意一个参与者的反馈未no,或者协调者等待超时后还未收到所有参与者的反馈。

  • 协调者向所有参与者发送callback请求。

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

  • 协调者收到所有参与者反馈的ACK消息后,取消事务。

引用超时机制:进入第三阶段后,若协调者出现故障或网络出现问题,都会导致部分参与者无法收到协调者的doCommit请求或callback请求。此时,参与者等待超时之后,自动执行事务的提交。

缺点:

  • 数据一致性问题:在第三阶段中,若协调者向参与者发送callback请求之后,参与者因网络问题没有收到该callback请求,此时参与者将在等待超时后自动提交事务,造成数据不一致。

  • 性能差:虽然引入超时机制,性能和可靠性上比2PC好一些,但整体还是同步阻塞的方式。

2PC与3PC的对比:

  • 3PC比2PC阻塞的时间短,在进入第三阶段后,参与者若没收到的协调者的通知,则在超时时间后自动提交事务。

  • 2PC和3PC都无法保证数据绝对的一致性。


TCC:

TCC是应用层的两阶段提交协议,对代码的侵入性强,针对所有的操作,都需要额外再实现对应的确认(try)和补偿(cancel)逻辑。

try

  • 在try阶段,协调者分别调用多个参与者,只有所有参与者都返回成功的时候,才会进行confirm,否则就会进行cancel。

  • 也就是说try阶段是一个预备类的操作,锁定某个资源、冻结部分数据,是在真正的扣除之前进行一个预占、冻结的动作。

confirm

  • confirm就进行真正的数据变更,将冻结、预占的数据进行真正的扣除。

  • confirm若部分或全部失败,那么进行有限次数重试(超时会重试,如果有明确错误则直接失败),重试后成功就正常结束。重试后失败,则需要告警,由兜底策略介入处理。

cancel

某个try调用失败后,将之前try成功的参与者服务进行cancel调用。若cancel部分或全部失败,那么使用和confirm一样的重试机制进行重试。

异常处理:

try阶段发生错误/异常/超时了需要进行cancel回滚,在这里需要考虑两种特殊情况:

1)cancel需要支持空回滚:参与者没有收到try,但是收到了cancel,这个时候被调用方不需要做任何处理,也就是空回滚。

2)try需要支持对悬挂(先收到cancel,后收到try)进行处理:参与者先收到了cancel,后收到了try,参与者需要对先收到的cancel请求进行空回滚,并对后面的try进行识别,识别出已经进行过cancel处理,故不再处理该次try调用。

方案:通过事务记录表(全局事务表、分支事务表)来识别当前事务的情况:比如事务记录表记录已执行过cancel操作,那么后面再收到try操作就不需要做任何处理。

事务记录表:

  • 全局事务表:记录分布式事务的内容和执行情况。

  • 分支事务表:记录各个参与者的事务内容和执行情况。

  • 数据日志表:记录数据的变化过程。后续可根据该日志表来追溯问题或进行数据修复。

优点:

  • 吞吐量较高:占用资源锁的粒度小,参与者的资源只有在自身事务执行提交(confirm或cancel)时被短暂占用,并不会因其它参与者的状态导致自身资源被占。

  • 数据最终一致:confirm和cancel操作都支持幂等,通过重试和兜底策略来保证数据的一致性。

缺点:

  • 对所有的服务参与者都要求实现tcc的3个接口。通常需要所有服务参与者使用相同的tcc框架。微服务架构下,服务间的调用关系错综复杂,要求所有场景下各链路涉及的服务都接入统一的tcc框架,并不是一件容易的事情。

一次提交类业务服务如何使用tcc

当业务场景没有“锁定、冻结”等操作时(即属于一次提交类业务服务),如何使用tcc框架来实现分布式事务?

将业务的真正操作放到try操作里面,confirm操作不做任何处理,如果其它分支事务有异常则再调用cancel操作进行回滚。这其实就是saga模式中的统一协调者方案。

案例:

一个参与者故障,导致另一个参与者回滚时间较长,期间发生的并发访问导致数据不一致。

场景:手机银行开户,因账户服务(响应慢)故障,因此出现了对客户信息的并发访问,导致客户数据不一致从而带来业务异常。

整体流程:

  • 1.创建客户:手机银行app调用核心用户系统接口创建客户

    • 核心用户系统在创建客户流程(有分布式事务):1.1用户系统创建客户、1.2调用账户系统接口同步客户信息、1.3调用路由服务建立客户路由规则。

  • 2.查询客户信息:手机银行调用核心用户系统接口查询客户是否存在

  • 3.更新客户信息:若第2步查询到客户信息,则手机银行调用核心用户系统接口更新客户信息

    • 核心用户系统更新客户信息(1.1待回滚的客户信息):3.1更新本地的客户信息,3.2调用账户系统接口将客户信息同步到账户中心的客户副本表。

  • 4.创建账户:手机银行调用核心账户系统接口创建账户。

数据不一致原因:

  • 创建客户和更新客户信息出现了并发调用,创建客户的交易回滚,更新客户的交易成功导致数据不一致。

异常原因分析:

  • 1.3调用路由服务失败,故触发分布式事务回滚。1.2事务分支回滚耗时较长,1.2回滚操作结束后1.1事务分支进行回滚,因此1.1事务分支从提交到回滚的周期比较长(10秒)。在1.1回滚完成前,客户的数据再次被其它交易(查询客户信息交易、更新客户信息交易)使用,对该客户的信息的访问出现了并发,导致核心用户系统没有该客户信息,但是核心账户系统有该客户的信息,造成数据不一致。。

不一致带来的业务影响:

  • 创建账户:核心账户系统会去自己的客户副本表中查询客户信息,查询到则允许创建账户。最终导致用户系统无客户信息,但是账户系统却给该客户开了账户。

思考:

confirm和cancel操作的重试超时时间的设置:时间太短会导致重试效果不佳,时间太长则会导致分布式事务耗时增加,业务上如果没有并发控制,容易产生并发导致数据不一致。

常见tcc框架:Seata


saga:

概念:

将长事务拆分为多个本地短事务并依次提交,如果所有短事务都执行成功,则分布式事务提交。如果某个参与者执行本地事务失败,则由协调者根据相反的顺序依次调用补偿操作,回滚已提交的参与者。

saga有设计方案:

命令驱动:指定一个协调者,这个协调者来负责各个短事务段的执行顺序。

  • 优点:参与者之间关系简单,各个参与者都无需关心参与者之间的调用顺序,不会导致参与者服务循环依赖,参与者服务程序开发简单。

  • 缺点:协调者处理逻辑容易变得庞大复杂,导致难以维护。

事件驱动:每个服务产生自己的事件并监听自己关心的服务发出来的事件,根据其它服务的事件内容来决定自己的操作。(主业务服务发送第一个发消息来触发整个链路的启动)

  • 优点:服务间实现了解耦,不会因某个参与者故障导致事务立即失败,容错性更高。

  • 缺点:服务之前可能存在循环依赖的风险。当涉及的步骤较多时,服务间的关系混乱,难以追踪联调测试。

本身缺点及解决方案:

缺点及解决方法:

  • 隔离性问题:在需要回滚的场景下,在回滚操作完成前,用户读取的数据是已提交的脏数据。

  • 解决:在业务逻辑层来实现并发的控制,在回滚操作完成前禁止去读取(使用)已提交的脏数据。兜底策略:定时任务进行对账,对明细数据和总数据进行准实时的比对,有异常时程序自动修复或人工介入手动修复。

优点:

  • 可以选择事件驱动,有很高的吞吐量。

  • 可以保证数据最终一致。

使用场景:

长事务的解决方案,使用于长业务链路的场景。如果服务参与者中,有一个服务无法提供TCC模式下的3个接口,那么就需要采用saga的方式来解决分布式事务的问题。


本地消息表

概念:

事务主动发起方需要额外新建事务消息表,在本地事务中完成业务处理和记录事务消息,并通过消息中间件将事务消息发送出去,事务被动方基于消息中间件消费事务消息表中的事务。

本地消息表的执行流程:

  1. 事务主动方在同一个本地事务中处理业务和写消息表操作,并且通过mq将事务内容发送出去。

  2. 事务被动方监听mq消息,业务处理完成后通过mq发送处理的结果。

  3. 事务主动方接收mq消息,更新消息表的状态为已处理。

  4. 最大努力通知(兜底策略):

    1. 事务主动方:轮询事务消息表,将处理失败或状态异常的消息重新发送。同时需要提供本地消息表的查询接口,供事务被动方查询。

    2. 事务被动方:事务被动方未收到消息时,事务被动方在某个业务操作前或通过定时任务主动调用事务主动方的消息校对接口查询业务消息并消费。

优点:

轻量级方案,容易实现。

消息数据的可靠性是在应用层面通过代码逻辑来保证,而不是依赖消息中间件,降低了对消息中间件可靠性的依赖。

缺点:

与具体的业务场景绑定,耦合性强,不具备通用性。(真正需要分布式事务的场景并不多,从这个角度看就不算是缺点了)


MQ事务消息:

基于MQ的分布式事务方案本质上是对本地消息表的封装,整体流程与本地消息表一致,唯一不同的就是将本地消息表存在了MQ内部,而不是业务数据库中

RocketMQ事务消息

在事务主动方服务正常,没有发生故障的情况下,发消息流程如下:

  • 步骤①:发送方向 MQ Server(MQ服务方)发送 half 消息

  • 步骤②:MQ Server 将消息持久化成功之后,向发送方 ack 确认消息已经发送成功

  • 步骤③:发送方开始执行本地事务逻辑

  • 步骤④:发送方根据本地事务执行结果向 MQ Server 提交二次确认(commit 或是 rollback)。

  • 最终步骤:MQ Server 如果收到的是 commit 操作,则将半消息标记为可投递,MQ订阅方最终将收到该消息;若收到的是 rollback 操作则删除 half 半消息,订阅方将不会接受该消息

异常情况:

在断网或者应用重启等异常情况下,图中的步骤④提交的二次确认超时未到达 MQ Server,此时的处理逻辑如下:

  • 步骤⑤:MQ Server 对该消息发起消息回查

  • 步骤⑥:发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果

  • 步骤⑦:发送方根据检查得到的本地事务的最终状态再次提交二次确认。

  • 最终步骤:MQ Server基于 commit/rollback 对消息进行投递或者删除。

优点:

相比本地消息表方案,MQ 事务方案优点是:

  • 消息数据独立存储 ,降低业务系统与消息系统之间的耦合

  • 吞吐量大于使用本地消息表方案

缺点:

  • 一次消息发送需要两次网络请求(half 消息 + commit/rollback 消息) 。

  • 业务处理服务需要实现消息状态回查接口


总结:

  • 2PC和3PC:都是以集中式的方式实现分布式事物,这种方法都存在两个共同的缺点,一个是同步执行性能差,一个是没有解决数据不一致的问题。分布式消息确保事物最终一致性的方案就出现了。

  • TCC:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。

  • 本地消息表/MQ 事务:适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。

  • Saga 事务:由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。同时saga也要求补偿的逻辑容易实现。

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值