分布式事务的常见实现方式

版权声明:本文转载自敖丙原创文章 分布式事务篇
https://blog.csdn.net/qq_35190492/article/details/108000380

一、2PC 二阶段提交

2PC 引⼊⼀个事务协调者的⻆⾊来协调管理各参与者的提交和回滚,⼆阶段分别指的是准备和提交两个阶段。

1. 准备阶段又叫第一阶段的时候协调者会给所有参与者发送准备的命令,该命令就表示参与者是否除了提交以外的准备都做好了

2.协调者按发送的顺序发送准备请求后,同步等待参与者进行的回应。必须等所有参与者资源响应结束之后才会进入第二阶段(注意这里准备成功表示除了事务提交之外的所有操作都完成了,数据已经修改只差提交了)

3.提交阶段又叫第二阶段的时候,即所有的参与者都完成了资源响应并且准备成功的时候,此时协调者就向各个参与者发送提交请求完成事务

具体流程图如下所示

在这里插入图片描述

当然在这期间肯定会有请求失败的情况接下来分析一下各个阶段请求失败的情况

一阶段的时候提交失败意思就是在所有的参与者中有某个参与者准备失败了,这里协调者会向所以参与者发送回滚请求
在这里插入图片描述
二阶段提交如果出现问题的话分为两种

第⼀种是第⼆阶段执⾏的是回滚事务操作,该操作如果提交失败的话,就会一直重试,直到所有参与者都回滚了,不然那些在第⼀阶段准备成功的参与者会⼀直阻塞着。

第⼆种是第⼆阶段执⾏的是提交事务操作,该操作如果提交失败的话,还是要一直重试,因为有可能⼀些参与者的事务已经提交成功了,所以不断重试就是唯一选择,最后实在不行还需要人工介入。

协调者出问题

协调者是一个单点存在单点故障

  1. 假设协调者在发送准备命令之前挂了,还⾏等于事务还没开始。
  2. 假设协调者在发送准备命令之后挂了,这就不太⾏了,有些参与者等于都执⾏了处于事务资源锁定的状态。不仅事务执⾏不下去,还会因为锁定了⼀些公共资源⽽阻塞系统其它操作。
  3. 假设协调者在发送回滚事务命令之前挂了,那么事务也是执⾏不下去,且在第⼀阶段那些准备成功参与者都阻塞着。
  4. 假设协调者在发送回滚事务命令之后挂了,这个还⾏,⾄少命令发出去了,很⼤的概率都会回滚成功,
    资源都会释放。但是如果出现⽹络分区问题,某些参与者将因为收不到命令⽽阻塞着。
  5. 假设协调者在发送提交事务命令之前挂了,这个不⾏,傻了!这下是所有资源都阻塞着。
  6. 假设协调者在发送提交事务命令之后挂了,这个还⾏,也是⾄少命令发出去了,很⼤概率都会提交成
    功,然后释放资源,但是如果出现⽹络分区问题某些参与者将因为收不到命令⽽阻塞着。 协调者故障,通过选举得到新协调者
    因为协调者单点问题,因此我们可以通过选举等操作选出⼀个新协调者来顶替。
    如果处于第⼀阶段,其实影响不⼤都回滚好了,在第⼀阶段事务肯定还没提交。
    如果处于第⼆阶段,假设参与者都没挂,此时新协调者可以向所有参与者确认它们⾃身情况来推断下⼀ 步的操作。
  7. 假设有个别参与者挂了!这就有点僵硬了,⽐如协调者发送了回滚命令,此时第⼀个参与者收到了并执
    ⾏,然后协调者和第⼀个参与者都挂了。此时其他参与者都没收到请求,然后新协调者来了,它询问其他参与者都说OK,但它不知道挂了的那个参与者到底O不OK,所以它傻了。

问题其实就出在每个参与者⾃身的状态只有⾃⼰和协调者知道,因此新协调者⽆法通过在场的参与者的状态推断出挂了的参与者是什么情况。

虽然协议上没说,不过在实现的时候我们可以灵活的让协调者将⾃⼰发过的请求在哪个地⽅记⼀下,也就是⽇志记录,这样新协调者来的时候不就知道此时该不该发了?但是就算协调者知道⾃⼰该发提交请求,那么在参与者也⼀起挂了的情况下没⽤,

因为你不知道参与者在挂之前有没有提交事务。如果参与者在挂之前事务提交成功,新协调者确定存活着的参与者都没问题,那肯定得向其他参与者发送提交事务命令才能保证数据⼀致。

如果参与者在挂之前事务还未提交成功,参与者恢复了之后数据是回滚的,此时协调者必须是向其他参与者发送回滚事务命令才能保持事务的⼀致。所以说极端情况下还是⽆法避免数据不⼀致问题。

二、3PC 三阶段提交

3PC 的出现是为了解决 2PC 的⼀些问题,相⽐于 2PC 它在参与者中也引⼊了超时机制,并且新增了⼀
个阶段使得参与者可以利⽤这⼀个阶段统⼀各⾃的状态。

3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英⽂就是: CanCommit、
PreCommit 和 DoCommit 。

看起来是把 2PC 的提交阶段变成了预提交阶段和提交阶段,但是 3PC 的准备阶段协调者只是询问参与
者的⾃身状况,⽐如你现在还好吗?负载重不重?这类的。

⽽预提交阶段就是和 2PC 的准备阶段⼀样,除了事务的提交该做的都做了。

在这里插入图片描述
不管哪⼀个阶段有参与者返回失败都会宣布事务失败,这和 2PC 是⼀样的(当然到最后的提交阶段和
2PC ⼀样只要是提交请求就只能不断重试)。

⾸先准备阶段的变更成不会直接执⾏事务,⽽是会先去询问此时的参与者是否有条件接这个事务,因此
不会⼀来就⼲活直接锁资源,使得在某些资源不可⽤的情况下所有参与者都阻塞着。

⽽预提交阶段的引⼊起到了⼀个统⼀状态的作⽤,它像⼀道栅栏,表明在预提交阶段前所有参与者其实
还未都回应,在预处理阶段表明所有参与者都已经回应了。

假如你是⼀位参与者,你知道⾃⼰进⼊了预提交状态那你就可以推断出来其他参与者也都进⼊了预提交
状态。

但是多引⼊⼀个阶段也多⼀个交互,因此性能会差⼀些,⽽且绝⼤部分的情况下资源应该都是可⽤的,
这样等于每次明知可⽤执⾏还得询问⼀次。

我们再来看下参与者超时能带来什么样的影响。我们知道 2PC 是同步阻塞的,上⾯我们已经分析了协调者挂在了提交请求还未发出去的时候是最伤的,所有参与者都已经锁定资源并且阻塞等待着。

那么引⼊了超时机制,参与者就不会傻等了,如果是等待提交命令超时,那么参与者就会提交事务了,
因为都到了这⼀阶段了⼤概率是提交的,如果是等待预提交命令超时,那该⼲啥就⼲啥了,反正本来啥
也没⼲。

然⽽超时机制也会带来数据不⼀致的问题,⽐如在等待提交命令时候超时了,参与者默认执⾏的是提交
事务操作,但是有可能执⾏的是回滚操作,这样⼀来数据就不⼀致了。

当然 3PC 协调者超时还是在的,具体不分析了和 2PC 是⼀样的。
从维基百科上看,3PC 的引⼊是为了解决提交阶段 2PC 协调者和某参与者都挂了之后新选举的协调者
不知道当前应该提交还是回滚的问题。

新协调者来的时候发现有⼀个参与者处于预提交或者提交阶段,那么表明已经经过了所有参与者的确认
了,所以此时执⾏的就是提交命令。

所以说 3PC 就是通过引⼊预提交阶段来使得参与者之间的状态得到统⼀,也就是留了⼀个阶段让⼤家
同步⼀下。

但是这也只能让协调者知道该如果做,但不能保证这样做⼀定对,这其实和上⾯ 2PC 分析⼀致,因为
挂了的参与者到底有没有执⾏事务⽆法断定。

所以说 3PC 通过预提交阶段可以减少故障恢复时候的复杂性,但是不能保证数据⼀致,除⾮挂了的那
个参与者恢复。

让我们总结⼀下, 3PC 相对于 2PC 做了⼀定的改进:引⼊了参与者超时机制,并且增加了预提交阶段
使得故障恢复之后协调者的决策复杂度降低,但整体的交互过程更⻓了,性能有所下降,并且还是会存
在数据不⼀致问题。

所以 2PC 和 3PC 都不能保证数据100%⼀致,因此⼀般都需要有定时扫描补偿机制。
我再说下 3PC 我没有找到具体的实现,所以我认为 3PC 只是纯的理论上的东⻄,⽽且可以看到相⽐于
2PC 它是做了⼀些努⼒但是效果甚微,所以只做了解即可。

三、TCC

2PC 和 3PC 都是数据库层⾯的,⽽ TCC 是业务层⾯的分布式事务,就像我前⾯说的分布式事务不仅
仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上⽤场了!

TCC 指的是 Try - Confirm - Cancel 。
Try 指的是预留,即资源的预留和锁定,注意是预留。

Confirm 指的是确认操作,这⼀步其实就是真正的执⾏了。
Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。

其实从思想上看和 2PC 差不多,都是先试探性的执⾏,如果都可以那就真正的执⾏,如果不⾏就回

⽐如说⼀个事务要执⾏A、B、C三个操作,那么先对三个操作执⾏预留动作。如果都预留成功了那么就
执⾏确认操作,如果有⼀个预留失败那就都执⾏撤销动作。

我们来看下流程,TCC模型还有个事务管理者的⻆⾊,⽤来记录TCC全局事务状态并提交或者回滚事
务。

在这里插入图片描述

可以看到流程还是很简单的,难点在于业务上的定义,对于每⼀个操作你都需要定义三个动作分别对应
Try - Confirm - Cancel 。
因此 TCC 对业务的侵⼊较⼤和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作。
还有⼀点要注意,撤销和确认操作的执⾏可能需要重试,
因此还需要保证操作的幂等。相对于 2PC、3PC ,TCC 适⽤的范围更⼤,但是开发量也更⼤,
毕竟都在业务上实现,⽽且有时候你会发现这三个⽅法还真不好写。不过也因为是在业务上实现的,所以TCC可以跨数据库、跨不同的业务系统来实现事务。

四、本地消息表

本地消息表其实就是利⽤了 各系统本地的事务来实现分布式事务。

本地消息表顾名思义就是会有⼀张存放本地消息的表,⼀般都是放在数据库中,然后在执⾏业务的时候
将业务的执⾏和将消息放⼊消息表中的操作放在同⼀个事务中,这样就能保证消息放⼊本地表中业务肯
定是执⾏成功的。

然后再去调⽤下⼀个操作,如果下⼀个操作调⽤成功了好说,消息表的消息状态可以直接改成已成功。
如果调⽤失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调⽤对应的服
务,服务更新成功了再变更消息的状态。

这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的⽅法是幂等的,⽽且
⼀般重试会有最⼤次数,超过最⼤次数可以记录下报警让⼈⼯处理。

可以看到本地消息表其实实现的是最终⼀致性,容忍了数据暂时不⼀致的情况。

五、消息事务

RocketMQ 就很好的⽀持了消息事务,让我们来看⼀下如何通过消息实现事务。

第⼀步先给 Broker 发送事务消息即半消息,半消息不是说⼀半消息,⽽是这个消息对消费者来说不可
⻅,然后发送成功后发送⽅再执⾏本地事务。

再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。

并且 RocketMQ 的发送⽅会提供⼀个反查事务状态接⼝,如果⼀段时间内半消息没有收到任何操作请
求,那么 Broker 会通过反查接⼝得知发送⽅事务是否执⾏成功,然后执⾏ Commit 或者 RollBack 命
令。

如果是 Commit 那么订阅⽅就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即
可。

如果是 RollBack 那么订阅⽅收不到这条消息,等于事务就没执⾏过。

可以看到通过 RocketMQ 还是⽐较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义
好事务反查接⼝即可。

在这里插入图片描述

六、最⼤努⼒通知

其实我觉得本地消息表也可以算最⼤努⼒,事务消息也可以算最⼤努⼒。

就本地消息表来说会有后台任务定时去查看未完成的消息,然后去调⽤对应的服务,当⼀个消息多次调
⽤都失败的时候可以记录下然后引⼊⼈⼯,或者直接舍弃。这其实算是最⼤努⼒了。

事务消息也是⼀样,当半消息被commit了之后确实就是普通消息了,如果订阅者⼀直不消费或者消费不
了则会⼀直重试,到最后进⼊死信队列。其实这也算最⼤努⼒。

所以最⼤努⼒通知其实只是表明了⼀种柔性事务的思想:我已经尽⼒我最⼤的努⼒想达成事务的最终⼀
致了。
适⽤于对时间不敏感的业务,例如短信通知

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值