2PC和3PC

本文来自我的文章分布式一致性和分布式共识协议太长,因此将其中的2PC和3PC部分单独列出来作为一篇文章。

缓存数据库双写问题

分布式一致性和分布式共识协议

两阶段提交协议Two-Phase commit(2PC)

协议内容

  1. 第一阶段(投票阶段)
    首先协调者向所有的参与者发出提交请求VOTE_REQUEST,参与者按照事务的标准流程写UNDO和REDO等日志,并在本地执行事务。如果事务执行顺利,则不提交(尽管事务中的全部操作已经正确完成),返回一个VOTE_COMMIT给协调者,表示自己成功执行了事务。如果事务执行出现错误,则返回一个VOTE_ABORT。

  2. 第二阶段(执行提交阶段)
    假设协调者没有宕机,相应会出现两种状态:

    1. 成功,发生在所有的参与者节点都返回VOTE_COMMIT
      此时协调者向所有参与者发送GLOBAL_COMMIT,参与者收到之后正式提交事务并释放资源,然后返回ACK确认。
    2. 失败,发生在任意参与者节点返回VOTE_ABORT,或者有的参与者timeout
      此时协调者向所有参与者发送GLOBAL_ROLLBACK,参与者收到之后UNDO回前像状态,然后返回ACK确认。

协调者宕机/分区情况对一致性的影响

OK,刚才协调者没有宕机,看起来很美好,可是如果协调者宕机了呢?

我们考虑协调者将commit信息传递给了一部分参与者的情况,由于2PC是阻塞的,这意味着所有没有收到commit消息的节点都会阻塞在等到GLOBAL_COMMIT消息这里。如果协调者又重启或者从分区中恢复,那么参与者仍然有机会提交。
但是如果协调者永久地宕机了,那么剩下来的节点就永远处在一个FLP里面的所谓b-valent了。此时整个事务就不在Safe了,因为并非所有的节点都能一致提交或者回滚。但仍然可以认为2PC是Safe的协议,因为它实际上是不假设永久宕机的情况的

不过我们脑洞大开,如果在永久宕机之后是否可以通过一些选举的协议来选出新的协调者呢?

  1. 假设参与者全部宕机
    这时候整个集群“死绝了”,变成平凡情况,由于没人(有能力)commit,所以不一致性不会受到破坏。
  2. 假设参与者部分宕机
    这时候不一致性是一定遭到破坏的了。虽然未宕机的参与者中可能存在有收到来自协调者消息的,可以选一个新协调者出来,但是宕机的参与者究竟是否提交成功是个量子力学问题了,所以新协调者即使知道原协调者发出了commit指令,也不能断然决定去commit。

二阶段提交协议的不足

总而言之,2PC存在两个显著问题,阻塞和不能处理网络分区。

  1. 阻塞
    2PC协议中,参与者一直是事务阻塞的,因此在事务进行的过程中,系统不能响应第三方节点的访问。这是偏于保守的,牺牲了一部分的可用性。
    阻塞带来的另一个问题来自于协调者可能的故障。由于协调者对于参与者有timeout机制,但是参与者对协调者没有timeout机制,如果协调者宕机,那么所有的参与者会跟着阻塞下去。特别地,即使做一个脚本定期扫描各数据库上被悬挂的事务,也不能确定是按照回滚还是提交进行处理

  2. 网络分区
    我们知道2PC协议通过分出投票阶段能够根据所有节点上事务的执行情况判断执行提交或者回滚。但它在第二阶段依然会出现不一致问题。

    1. 第二阶段出现网络分区
      假设协调者发出了GLOBAL_COMMIT请求时发生了网络分区,此时有一部分节点收到消息正常commit,但另一部分节点未收到,还处于阻塞状态。
      此时协调者仍可以通过最终返回的ACK进行补救。
    2. 第二阶段协调者宕机
      假设协调者宕机了,那么整个集群就不可用了,这个在前面已经讨论过了。

三阶段提交协议

三阶段提交协议致力于解决2PC的阻塞问题。为此它引入了参与者超时机制和一个额外的PreCommit阶段。

协议内容

  1. CanCommit阶段
    这个类似于2PC的投票阶段,协调者发出询问是否可以提交,Yes为可以提交,No相反。

  2. PreCommit阶段
    需要分为三种情况讨论:

    1. 如果上阶段全部为Yes
      协调者发送PreCommit请求并进入Prepared状态
      参与者接受到PreCommit后确保事务操作全部执行并记录UNDO与REDO,返回ACK

    2. 如果上阶段有No
      协调者发送abort请求
      参与者接受到abort后,REDO,中断事务,发送ACK

    3. 例外情况:参与者未收到协调者的消息
      这可以认为是协调者的timeout,此时中断事务
      注意到这里参与者是可以处理协调者的timeout的

  3. DoCommit阶段
    这是真正的事务提交阶段,同样分为三种情况

    1. 协调者收全上阶段ACK
      协调者发送DoCommit请求
      参与者接受到DoCommit后提交事务,返回ACK

    2. 协调者未收到上阶段ACK
      这发生在协调者没有收到一些参与者的ACK(网络分区或该参与者abort)
      协调者发送abort请求
      参与者接受到abort,使用同上阶段的方式中断事务

    3. 例外情况:参与者未收到协调者的消息
      这又是一个协调者的timeout,此时提交事务
      为什么选择提交事务而不是中断事务?因为此时提交事务成功的可能性非常非常大了,但仍有例外,例如:
      进入PreCommit后,协调者发出的是abort请求,如果只有一个Cohort收到并进行了abort操作,而其他对于系统状态未知的Cohort会根据3PC选择继续Commit,这仍然会导致不一致,不过这个概率就显然非常小了

三阶段提交协议的不足

相对于2PC,3PC避免了协调者宕机之后可能出现的参与者们阻塞的情况。但仍然有较小的概率会导致不一致。

Reference

  1. NJU教学PPT
  2. A brief history of Consensus, 2PC and Transaction
  3. https://en.wikipedia.org/wiki/Two-phase_commit_protocol