根据上一篇文章描述《分布式系统的一致性协议之 CAP理论原理》可知,目前分布式系统中通用的一致性方案,主要包括:冷备、Master/Slave、Master/Master、两阶段提交以及基于 Paxos 算法的各种实现。
所谓冷备,也就是做定时全量、增量的备份,在系统一致性、可用性出现问题时,能够通过备份数据还原系统到达某一点。这种方案操作难度小,但存在即时性低、丢失率高的问题。
同步方式有Master push or Slave pull两种,通常是由Slave 周期性来pull,所以是最终一致性。
问题: 若在 pull 周期内(不是期间?),master挂掉,那么会导致这个时间片内的数据丢失。
问题:如何保证强一致性?
Master 写操作,写完成功后,再写 Slave,两者成功后返回成功。若 Slave失败,两种方法:
数据同步一般是通过 Master 间的异步完成,所以是最终一致。
好处: 一台Master挂掉,另外一台照样可以提供读写服务。当数据没有被赋值到别的Master上时,数据会丢失。
对同一数据的处理问题:
Dynamo的Vector Clock的设计(记录数据的版本号和修改者),当数据发生冲突时,要开发者自己来处理。
二阶段提交协议(Two-phase Commit,即2PC)是常用的分布式事务解决方案,它可以保证在分布式事务中,要么所有参与进程都提交事务,要么都取消事务,即实现 ACID 的原子性(A)。在数据一致性中,它的含义是:要么所有副本(备份数据)同时修改某个数值,要么都不更改,以此来保证数据的强一致性。
2PC 要解决的问题可以简单总结为:在分布式系统中,每个节点虽然可以知道自己的操作是成功还是失败,却是无法知道其他节点的操作状态。当一个事务需要跨越多个节点时,为了保持事务的 ACID 特性,需要引入一个作为协调者的组件来统一掌控所有节点(参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为: 参与者将操作结果通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
关于两阶段提交的过程如下图所示:
顾名思义,2PC 分为两个过程:
VOTE_COMMIT
消息作为回应,告知 Coordinator 自己已经做好了准备,否则会返回一个 VOTE_ABORT
消息;GLOBAL_COMMIT
消息,否则发送 GLOBAL_ABORT
消息;对于参与者而言,如果收到 GLOBAL_COMMIT
消息,就会提交本地事务,否则就会取消本地事务。2PC 一致性问题
这里先讨论一下,2PC 是否可以在任何情况下都可以解决一致性问题,在实际的网络生产中,各种情况都有可能发生,这里,我们先从理论上分析各种意外情况。
2PC 在执行过程中可能发生 Coordinator 或者参与者突然宕机的情况,在不同时期宕机可能有不同的现象。
情况 | 分析及解决方案 |
---|---|
Coordinator 挂了,参与者没挂 | 这种情况其实比较好解决,只要找一个 Coordinator 的替代者。当他成为新的 Coordinator 的时候,询问所有参与者的最后那条事务的执行情况,他就可以知道是应该做什么样的操作了。所以,这种情况不会导致数据不一致。 |
参与者挂了(无法恢复),Coordinator 没挂 | 如果挂了之后没有恢复,那么是不会导致数据一致性问题。 |
参与者挂了(后来恢复),Coordinator 没挂 | 恢复后参与者如果发现有未执行完的事务操作,直接取消,然后再询问 Coordinator 目前我应该怎么做,协调者就会比对自己的事务执行记录和该参与者的事务执行记录,告诉他应该怎么做来保持数据的一致性。 |
还有一种情况是:参与者挂了,Coordinator 也挂了,需要再细分为几种类型来讨论:
情况 | 分析及解决方案 |
---|---|
Coordinator 和参与者在第一阶段挂了 | 由于这时还没有执行 commit 操作,新选出来的 Coordinator 可以询问各个参与者的情况,再决定是进行 commit 还是 roolback。因为还没有 commit,所以不会导致数据一致性问题。 |
Coordinator 和参与者在第二阶段挂了,但是挂的这个参与者在挂之前还没有做相关操作 | 这种情况下,当新的 Coordinator 被选出来之后,他同样是询问所有参与者的情况。只要有机器执行了 abort(roolback)操作或者第一阶段返回的信息是 No 的话,那就直接执行 roolback 操作。如果没有人执行 abort 操作,但是有机器执行了 commit 操作,那么就直接执行 commit 操作。这样,当挂掉的参与者恢复之后,只要按照 Coordinator 的指示进行事务的 commit 还是 roolback 操作就可以了。因为挂掉的机器并没有做 commit 或者 roolback 操作,而没有挂掉的机器们和新的 Coordinator 又执行了同样的操作,那么这种情况不会导致数据不一致现象。 |
Coordinator 和参与者在第二阶段挂了,挂的这个参与者在挂之前已经执行了操作。但是由于他挂了,没有人知道他执行了什么操作。 | 这种情况下,新的 Coordinator 被选出来之后,如果他想负起 Coordinator 的责任的话他就只能按照之前那种情况来执行 commit 或者 roolback 操作。这样新的 Coordinator 和所有没挂掉的参与者就保持了数据的一致性,我们假定他们执行了 commit。但是,这个时候,那个挂掉的参与者恢复了怎么办,因为他已经执行完了之前的事务,如果他执行的是 commit 那还好,和其他的机器保持一致了,万一他执行的是 roolback 操作呢?这不就导致数据的不一致性了么?虽然这个时候可以再通过手段让他和 Coordinator 通信,再想办法把数据搞成一致的,但是,这段时间内他的数据状态已经是不一致的了! |
所以,2PC协议中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一致。为了解决这个问题,衍生除了3PC。
2PC 优缺点
简单总结一下 2PC 的优缺点:
关于这几个缺点,在实际应用中,都是对2PC 做了相应的改造:
三阶段提交协议(Three-Phase Commit, 3PC)最关键要解决的就是 Coordinator 和参与者同时挂掉导致数据不一致的问题,所以 3PC 把在 2PC 中又添加一个阶段,这样三阶段提交就有:CanCommit、PreCommit 和 DoCommit 三个阶段。
三阶段提交协议的过程如下图(图来自 维基百科:三阶段提交)所示:
3PC 的详细过程如下(这个过程步骤内容来自 2PC到3PC到Paxos到Raft到ISR):
阶段一 CanCommit
阶段二 PreCommit
执行事务预提交:如果 Coordinator 接收到各参与者反馈都是Yes,那么执行事务预提交:
中断事务:如果任何一个参与者向 Coordinator 反馈了 No 响应,或者在等待超时后,Coordinator 无法接收到所有参与者的反馈,那么就会中断事务。
阶段三 doCommit
执行提交
中断事务:假设 Coordinator 正常工作,并且有任一参与者反馈 No,或者在等待超时后无法接收所有参与者的反馈,都会中断事务
3PC 分析
3PC 虽然解决了 Coordinator 与参与者都异常情况下导致数据不一致的问题,3PC 依然带来其他问题:比如,网络分区问题,在 preCommit 消息发送后突然两个机房断开,这时候 Coordinator 所在机房会 abort, 另外剩余参与者的机房则会 commit(网络中断导致未收到消息的节点会执行commit)。
而且由于3PC 的设计过于复杂,在解决2PC 问题的同时也引入了新的问题,所以在实际上应用不是很广泛。
参考:
分布式事务与一致性算法Paxos & raft & zab
分布式系统的一致性协议之 2PC 和 3PC
跟我学分布式事务之2PC和3PC
一致性及解决一致性的两种方式:2PC和3PC