分布式事务:如何保证多个系统间的数据是一致的?_腾讯新闻 (qq.com)
面试必问:分布式事务六种解决方案 - 知乎 (zhihu.com)
强一致性事务
1.2pc
第一阶段:准备阶段,协调者会给各参与者发送准备命令,除了提交数据库事务以外的所有工作,都要在准备阶段完成
第二阶段:提交阶段,同步等待所有资源的响应之后就进入第二阶段即提交阶段(注意提交阶段不一定是提交事务,也可能是回滚事务)
(1)执行状况
- 第一阶段所有参与者都返回准备成功,那么协调者则向所有参与者发送提交事务命令,然后等待所有事务都提交成功之后,返回事务执行成功;第一阶段有一个参与者返回失败,那么协调者就会向所有参与者发送回滚事务的请求,即分布式事务执行失败。
- 第二阶段提交失败,分为两种情况:第二阶段执行的是回滚事务操作,那么答案是不断重试,直到所有参与者都回滚了,不然那些在第一阶段准备成功的参与者会一直阻塞着;第二阶段执行的是提交事务操作,那么答案也是不断重试,因为有可能一些参与者的事务已经提交成功了,这个时候只有一条路,就是头铁往前冲,不断的重试,直到提交成功,到最后真的不行只能人工介入处理
(2)超时机制
- 2PC 是一个同步阻塞协议,像第一阶段协调者会等待所有参与者响应才会进行下一步操作,当然第一阶段的协调者有超时机制,假设因为网络原因没有收到某参与者的响应或某参与者挂了,那么超时后就会判断事务失败,向所有参与者发送回滚命令。
- 第二阶段协调者的没法超时,因为按照我们上面分析只能不断重试!
(3)协调者故障分析
协调者是一个单点,存在单点故障问题
- 在发送准备命令之前挂了,还行等于事务还没开始。
- 在发送准备命令之后挂了,这就不太行了,有些参与者等于都执行了处于事务资源锁定的状态。不仅事务执行不下去,还会因为锁定了一些公共资源而阻塞系统其它操作。
- 在发送回滚事务命令之前挂了,那么事务也是执行不下去,且在第一阶段那些准备成功参与者都阻塞着。
- 在发送回滚事务命令之后挂了,这个还行,至少命令发出去了,很大的概率都会回滚成功,资源都会释放。但是如果出现网络分区问题,某些参与者将因为收不到命令而阻塞着。
- 在发送提交事务命令之前挂了,这个不行,傻了!这下是所有资源都阻塞着。
- 发送提交事务命令之后挂了,这个还行,也是至少命令发出去了,很大概率都会提交成功,然后释放资源,但是如果出现网络分区问题某些参与者将因为收不到命令而阻塞着。
(4)选举得到新协调者
- 处于第一阶段,其实影响不大都回滚好了,在第一阶段事务肯定还没提交
- 第二阶段,假设参与者都没挂,此时新协调者可以向所有参与者确认它们自身情况来推断下一步的操作。
- 第二阶段,假设其他参与者状态都ok,有个别参与者挂了,但是在挂之前并不知道它执行了什么操作(每个参与者自身的状态只有自己和协调者知道),这时候新协调者就无法进行操作;就算知道了,如果之前执行的是提交事务操作,但并不知道事务有没有提交成功,成功,则其他参与者都要提交成功,失败,参与者恢复了之后数据是回滚的,其他参与者都要回滚。因此极端情况下还是有数据不一致问题。
总结:
2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。
2.3pc
3PC 的出现是为了解决 2PC 的一些问题,相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。三个阶段,分别是准备阶段、预提交阶段和提交阶段(后面两个相当于2pc的准备和提交阶段)
准备阶段:不会直接执行事务,而是会先去询问此时的参与者是否有条件接这个事务,因此不会一来就干活直接锁资源,使得在某些资源不可用的情况下所有参与者都阻塞着。
预提交阶段:起到了一个统一状态的作用,它像一道栅栏,表明在预提交阶段前所有参与者其实还未都回应,在预处理阶段表明所有参与者都已经回应了
总结: 3PC 通过预提交阶段可以减少故障恢复时候的复杂性,但是不能保证数据一致
3. TCC
2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了!
TCC 指的是Try - Confirm - Cancel
。
- Try 指的是预留,即资源的预留和锁定,注意是预留。
- Confirm 指的是确认操作,这一步其实就是真正的执行了。
- Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。
最终一致性事务(适合对时间不敏感的业务)
1.本地消息表
本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
2.消息事务
第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令
- 反查事务状态接口:如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。
3.最大努力通知
本地消息表、事务消息也可以算最大努力,最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。
就本地消息表来说会有后台任务定时去查看未完成的消息,然后去调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。这其实算是最大努力了。
事务消息也是一样,当半消息被commit了之后确实就是普通消息了,如果订阅者一直不消费或者消费不了则会一直重试,到最后进入死信队列。其实这也算最大努力。