分布式一致性协议 2PC 和 3PC

657 阅读7分钟

前言

如果在分布式系统中发起一个事务,该事务涉及到多个不同节点,那么为了保证事务ACID特性,就需要引入一个协调者来统一调度事务涉及的多个节点,被调度的节点称为事务参与者,由此衍生出了 2PC 和 3PC 协议,本文主要主要介绍这两个协议。

2PC

2PC(Two-Phase Commit)两阶段提交,是不是想到了 MySQL 的2PC,MySQL 的2PC 是为了保证单个数据库事务的完整性,让每次执行写操作时 redo_log 和 binlog 两个文件的一致性。

分布式事务的2PC协议也是一样,为了保证分布式事务的一致性。它分为两个阶段:Prepare 和 Commit

Prepare

Prepare 提交事务请求阶段:

1、协调者向所有参与者发起事务请求,询问是否可执行事务操作,然后等待各个参与者的响应

2、各参与者接收到协调者事务请求后,执行事务操作,并将信息记录到事务日志中

3、如果参与者成功执行了事务并写入日志信息,则向协调者返回 Yes 响应,否则返回 NO 响应,当然,参与者也可能宕机,不返回响应

Commit

Commit 执行事务提交阶段,分为两个情况:正常提交和回退。

1、协调者向所有参与者发送 Commit 请求

2、参与者收到 Commit 请求后,执行事务提交,提交完成后释放事务执行期占用的所有资源

3、参与者执行事务提交后向协调者发送 ACK 响应

4、接收到所有参与者的 ACK 响应后,完成事务提交

中断事务

在执行 Prepare 阶段过程中,如果某些参与者执行事务失败,宕机或协调者之间的网络中断,那么协调者就无法收到所有参与者的 Yes 响应,或者某个参与者返回了 No 响应,此时,协调者就会进入回退流程,对事务进行回退。

注:只要协调者没有收到所有参与者的 Yes 响应,就会发起事务中断请求。

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

2、参与者收到 rollback 后,使用 Prepare 阶段的 undo 日志执行事务回滚,完成后释放事务执行期间占用的所有资源

3、参与者执行事务回滚后向协调者发送 ACK 响应

4、接收到所有参与者的ACK响应后,完成事务中断

2PC 存在的问题

1、参与者在等待协调者的指令时,其实就是在等待其他参与者的响应,在此过程中,参与者是无法进行其他操作的,也就是阻塞了其运行。如果参与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去

2、在 2PC 中,一切请求都来自协调者,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源

如果协调者也是分布式,使用选主方式提供服务,那么在一个协调者挂掉后,可以选取另一个协调者继续后续的服务,可以解决单点问题。但是,新协调者无法知道上一个事务的全部状态信息(例如已等待 Prepare 响应的时长等),所以也无法顺利处理上一个事务。

3PC

上述中说明了 2PC 协议存在的问题,那么 3PC 就是在 2PC 的基础上,为了解决 2PC 的某些缺点而设计的,3PC 分为三个阶段:CanCommit,PreCommit,DoCommit

CanCommit

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

2、参与者收到 canCommit 请求后,分析事务内容,判断自身是否可以执行事务,如果可以,那么就返回 Yes 响应,进入预备状态,否则返回 No 响应

注:此阶段参与者并没有执行事务,而 2PC 的 prepare 阶段中参与者是执行了事务的

PreCommit

PreCommit 阶段根据各参与者返回的 canCommit 响应,决定下一步动作,如果收到了所有参与者的 Yes 响应,则执行事务预提交,否则执行事务中断

预提交阶段

1、协调者发送 PreCommit 请求,并进入 Prepared 阶段

2、参与者收到 PreCommit 请求后,执行事务操作,并将 undo 和 redo 信息记录到事务日志中

3、如果参与者成功执行了事务并写入 undo 和 redo 信息,那么反馈 ACK 给协调者,并等待下一步指令

事务中断

如果 canCommit 阶段协调者没有收到所有参与者的 Yes 响应,协调者就发起 Abort 请求。

1、协调者向所有参与者发送 Abort 请求

2、参与者收到 Abort 请求后,会触发事务中断,此外,如果参与者在等待协调者指令超时,会自己触发事务中断,在 2PC 中,参与者会一直阻塞的等待协调者指令,所以 3PC 中解决了因为这种情况带来的阻塞

doCommit

协调者根据 PreCommit 阶段参与者的响应决定最终操作,如果协调者收到了所有参与者在 PreCommit 阶段的 ACK 响应,那么会进入执行事务提交阶段,否则执行事务中断

事务提交

1、协调者收到所有参与者的 PreCommit 阶段的 ACK 响应后,向所有参与者发送 doCommit 请求,并进入提交状态

2、参与者收到 Commit 请求后,执行事务提交,提交完成后释放事务执行期间占用的所有资源

3、参与者完成事务提交之后,向协调者返回 ACK 响应

4、协调者收到所有参与者的 ACK 响应后,完成事务

事务中断

1、协调者向所有参与者发送 Abort 请求

2、参与者收到 Abort 请求后,会使用 PreCommit 阶段记录的 Undo 信息进行事务回滚,并在完成回滚后释放所有事务资源。

注:因为 canCommit 阶段并没有执行事务,所以在 PreCommit 阶段执行事务中断,是不需要事务回滚的,也就不需要回滚后的反馈结果,直接中断事务即可

3、参与者执行事务回滚后向协调者发送 ACK 响应

4、协调者接收到所有参与者反馈的 ACK 响应后,完成事务中断

3PC 的改进与不足

改进

1、降低了阻塞

  • 参与者返回 canCommit 请求的响应后,等待 PreCommit 阶段的指令,若等待超时,则自动 Abort,降低了阻塞
  • 参与者返回 PreCommit 阶段的请求响应后,等待第三阶段指令,若等待超时,则自动 commit 事务,也降低了阻塞

2、解决单点故障问题

  • 参与者返回 canCommit 请求响应后,等待 PreCommit 阶段指令,若协调者宕机,等待超时后参与者自动 Abort
  • 参与者返回 PreCommit 请求响应后,等待 doCommit 阶段指令,若协调者宕机,等待超时后自动 commit 事务

不足

数据的不一致问题仍然可能存在,比如 doCommit 阶段协调者发出了 Abort 请求,然后有些参与者没有收到 Abort,那么就会自动 commit,造成数据不一致。

小结

2PC 和 3PC 都无法完美解决分布式数据一致性问题,虽然无法保证事务ACID特性,但解决问题的思想在很多实际架构中有着广泛应用。

Paxos 是 Google 大神提出的解决分布式一致性的算法,后续文章会介绍到。