分布式共识问题
分布式共识问题是指在分布式系统中,多个节点或参与者需要就某个共同的结果达成一致意见的问题。例如主从同步问题, 简单的主从同步可以借助两阶段提交和三阶段提交来解决,确保所有节点上的事务操作能够保持一致性,即要么全部提交,要么全部回滚。
两阶段提交
在主从同步的场景中, backup 需要从 master 同步数据, 从数据写入的场景来说, 假定主从节点是同步复制, 实际上就是一次分布式事务, backup 和 master 的一次写入要么都成功, 要么都失败, 才能保证主从节点的数据一致性;
借助两阶段提交, 需要引入一个协调者: Coordinator, 如下:
将提交过程分为两个阶段: PreCommit、commit;
在 PreCommit 阶段, 执行 WAL 流程, 把事务日志提前写入, 然后 Coordinator 在收到参与者的 PreCommit ack 之后, 开启第二阶段 commit, commit 成功之后, 数据才算是真正的写入. 当 PreCommit 失败时, Coordinator 会发起事务回滚, 所有参与者会基于 WAL 的事务日志, 回滚此次事务;
当 Coordinator 和 Participant 网络异常时, 如果是在 PreCommit 阶段, 那事务都不会提交, 相当于一次写入就失败了; 如果在 commit 阶段网络异常, Coordinator 会重试执行, 直到 Participant 恢复, 事务重新提交或者回滚. 但在此期间, 客户端的写入都会被拒绝, 此时分布式系统处于不可用状态(相当于保证了数据一致性, 牺牲了可用性)
三阶段提交
三阶段提交的工作流程:
- 准备阶段:
- 协调者向所有参与者发送CanCommit请求。
- 参与者在准备好后回复Yes,如果有一个参与者回复No或者超时,则协调者会向所有参与者发送Abort请求,中止事务。
- 预提交阶段:
- 如果所有参与者回复Yes,协调者向所有参与者发送PreCommit请求。
- 参与者在收到PreCommit请求后,将事务写入日志,但不提交。如果有一个参与者写入失败,则协调者会向所有参与者发送Abort请求。
- 提交阶段:
- 协调者向所有参与者发送DoCommit请求。
- 参与者在收到DoCommit请求后,提交事务并释放资源。如果有一个参与者提交失败,则协调者会向所有参与者发送Abort请求。
为了缓解两阶段提交过程中的可用性问题, 三阶段做了如下改造:
在 WAL 之前, 增加一个阶段, CanCommit, 避免在 PreCommit 时, 有参与者失败, 导致后续还需要回滚事务, 让异常请求尽量在最开始就丢弃掉;
在进入最后 DoCommit 执行阶段的时候,如果参与者等待协调者超时了,那么参与者不会一直在那里死等,而是会把已经答应的事务执行完成;
由于参与者默认提交事务的机制, 虽然提升了系统的可用性, 不会阻塞客户端的写入, 但是也会引入一些新的问题:
当参与者在 PreCommit 阶段异常, 协调者会回滚本次事务, 如果在回滚过程中, 有另外一个参与者与协调者网络中断, 导致参与者无法感知到事务回滚就默认提交事务了, 此时系统中的数据就不一致了! 实际上,三阶段提交,就是为了可用性,牺牲了一致性.
总结
在正常场景下, 三阶段提交会比两阶段提交有更好的性能, 原因是在出现网络异常时, 把PreCommit请求阶段拆分成 CanCommit 和 PreCommit 两个动作,缩短了各个参与者发生同步阻塞的时间。同时, 在 DoCommit 阶段, 参与者默认提交也增强整个分布式系统的可用性;
两种方案都是在可用性和一致性之间权衡, 在大数据量的场景下, 这种小概率的异常事件也会成为必然, 并且两种方案都存在单点问题: coordinator, 当协调者不可用时, 整个事务无法推进下去!