一致性协议之 2PC 与 3PC


在上一篇文章中,讲了 CAP 定理与 BASE 理论,但我们往往需要在系统可用性与数据一致性之间做出平衡,因此就涌现了很多一致性算法和协议,包括二阶段提交协议( 2PC )、三阶段提交协议( 3PC ),Paxos 算法以及 Raft 算法等。

两阶段提交(2PC)

二阶段提交协议( Two-Phase Commit ),简称 2PC ,它是一个非常经典的强一致、中心化的原子提交协议,将整个事务流程分为两个阶段,准备阶段(Prepare Phase)、提交/执行阶段(Commit Phase)。

在分布式系统中,存在 N 个参与者节点,一个中心化的协调者节点,通过委托协调者统一调度所有分布式节点的执行逻辑。

举例:微信下单购买并支付商品,首先下单服务 A 调用 商品服务 B 预扣减库存,接着下单服务 A 再调用支付服务 C,若支付成功则下单服务A 调用商品服务 B 真正扣减库存,并更新订单为成功,否则释放商品库存量、更新订单状态为失败。

那这流程对应 2PC 是如何处理的?

1、准备阶段

2PC

第一阶段主要分为 3 步:

1、事务预处理询问

委托处理请求的 协调者 向所有的 参与者 发送事务预处理请求,该过程称为 Prepare 请求,并等待各 参与者 的响应。

2、执行本地事务:

收到预处理请求的 参与者 节点执行事务操作,并把 Undo 和 Redo 信息记录到事务日志,但在执行完成后并不会真正提交本地数据库事务

3)参与者向协调者反馈事务询问结果

如果 参与者 成功执行了本地事务操作,则给协调者反馈 YES 响应,表示第二阶段可以执行,如果 参与者 执行事务失败,则给协调者反馈 YES 响应,表示提交第二阶段不可以执行。

在准备阶段执行完后,存在两种可能性:

1、全部预处理请求都返回 YES

2、有一个或多个 Prepare 请求返回 NO

2、提交/执行阶段

该阶段的执行流程会根据准备阶段的结果分为两种情况:

1、当准备阶段中的所有参与者对预请求都响应为 YES 时,则执行成功提交流程。

2、否则只要有一个或多个参与者预请求响应为 NO 就执行异常回滚流程。

正常提交流程

触发条件:协调者收到所有参与者的预请求响应为 YES

主要分为两步:

1、协调者向所有参与者发送 commit 提交请求。

2、参与者收到提交请求后,正式执行本地事务的提交操作,并释放整个操作期间占用的事务资源

2PC-2

异常回滚流程

触发条件:协调者收到任意一个参与者的预请求响应为 NO ,或者网络链路异常、等待响应超时,协调者未收到参与者的响应反馈。

异常流程也分为两步:

1、协调者向所有参与者发送 rollback 回滚请求。

2、每个参与者收到 rollback 回滚请求后,回滚第一阶段执行的本地事务更改。

2PC-2

2PC 的优缺点

阅读上述的内容后,不难发现 2PC 原理简单,实现简单,但同时也存在以下缺点:

1、性能问题

不管是在准备阶段还是提交阶段,协调者与参与者的资源都会被锁住,只有当协调者向所有参与者发送提交或回滚请求后,参与者执行完本地事务提交或回滚操作,参与者与协调者才会释放被占用的资源,整个过程漫长,对性能影响比较大

2、单点故障

单节点故障存在三种情景:

  • 协调者故障,参与者正常。

协调者地位十分重要,全局控制每个参与者的状态,无论是准备阶段还是提交阶段,当协调者出现故障后,都会导致参与者一直等待阻塞,无法释放资源。

2PC-2

解决方案:引入协调者主从模式,协调者 Leader 记录每个操作日志并同步 Slave 协调者,当协调者 Leader 故障时,在 Slave 协调者中选举出 Leader,并查询日志信息向参与者查询状态。

  • 协调者正常,参与者故障。

协调者因无法收到参与者的预请求响应,导致协调者一直处理等待状态,无法释放资源。

2PC-2

解决方案:引入超时机制,当参与者在一定时间内未响应,协调者向所有参与者发送终止请求。

  • 协调者故障,参与者故障。

在准备阶段期间,协调者、参与者都发生故障,不管参与者是否收到预处理请求,协调者、参与者恢复后重新执行即可。

在提交阶段期间,协调者已发出提交请求后故障,而参与者未接收到提交请求前故障,此种情景也只需待协调者、参与者恢复后重新执行即可。

但若协调者在发出提交请求后故障,参与者接收到提交请求并执行完提交操作后发生故障,此种情景将导致数据不一致,2PC 目前是没有办法处理这问题的。

2PC-2

3、数据不一致

当协调者向所有的参与者发送提交请求时,由于网络异常或者协调者发生了故障,导致只有部分参与者收到了提交请求,这将导致数据不一致问题。

三阶段提交(3PC)

三阶段提交协议( Three-Phase Commit ),简称 3PC,它是 2PC 的改进版本,主要解决二阶段提交协议中阻塞问题,相对于二阶段提交,有两个改动点:

1、 在协调者和参与者之间引入超时机制。

2、3PC 将 2PC 的准备阶段拆分成了两部分,分别是询问事务是否可提交、事务预处理,通过拆分保证在最后提交阶段之前各参与者节点的状态是一致的。

在三阶段提交中,分为三个过程:CanCommitPreCommitDoCommit

1、CanCommit

2PC-2

该阶段分为两步:

1、事务询问

协调者向所有参与者发送一个包含事务内容的 canCommit 请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。

2、询问响应

参与者接收到协调者的 canCommit 请求后,自身认为可以顺利执行事务,就返回 Yes 响应,否则反馈 No 响应。

2、PreCommit

当协调者在第一阶段得到所有参与者的响应后,会根据结果执行2种操作:执行事务预提交或者中断事务请求。

该阶段与 2PC 的 第一阶段类似,只不过这里 协调者和参与者都引入了超时机制 ( 2PC 中只有协调者超时,参与者没有超时机制)。

2PC-2

3、DoCommit

该阶段与 2PC 的提交阶段是类似的。

3PC 的优缺点

优点:相对于 2PC 而言,3PC 对协调者和参与者都设置了超时等待时间,而 2PC 只对协调者拥有超时机制,因此 3PC 可避免参与者节点因长时间无法与协调者通讯的情况下,无法释放资源的问题。

由于参与者自身拥有超时机制且达到超时阀值时,会自动 commit 本地事务从而进行释放资源,这种机制在一定程度上降低了整个事务的阻塞时间与范围。

缺点:依旧无法解决数据不一致问题。当参与者节点收到 preCommit 请求后,因出现网络分区,导致参与者阻塞,超时后都会进行本地事务的提交,最终会导致数据不一致的问题。