分布式事务 2PC && 3PC

3,107 阅读10分钟
原文链接: int64.me

二阶段提交(Two-phase Commit)是指,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常,二阶段提交也被称为是一种协议(Protocol)…

在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为: 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。 (from Wikipedia)

2PC

  • 阶段1:请求阶段(commit-request phase,或称表决阶段,voting phase)

  • 协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。

  • 参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。

  • 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。 有时候,第一阶段也被称作投票阶段,即各参与者投票是否要继续接下来的提交操作。。

  • 阶段2:提交阶段(commit phase)

在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。参与者在接收到协调者发来的消息后将执行响应的操作。

成功

成功算示意图

  1. 当协调者节点从所有参与者节点获得的相应消息都为”同意”时:
  2. 协调者节点向所有参与者节点发出”正式提交”的请求。
  3. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
  4. 参与者节点向协调者节点发送”完成”消息。
  5. 协调者节点收到所有参与者节点反馈的”完成”消息后,完成事务。

失败

失败算法示意图

  1. 如果任一参与者节点在第一阶段返回的响应消息为”终止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
  2. 协调者节点向所有参与者节点发出”回滚操作”的请求。
  3. 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
  4. 参与者节点向协调者节点发送”回滚完成”消息。
  5. 协调者节点收到所有参与者节点反馈的”回滚完成”消息后,取消事务。

有时候,第二阶段也被称作完成阶段,因为无论结果怎样,协调者都必须在此阶段结束当前事务。

现实生活中其实很多地方都在使用 2PC :

组织者组织出游,给每个参与者发送出游确认邮件,每个参与者回复去或是不去给组织者,如果都回复ok,那么就可以出游,要是有一个人回复NO, 那么这次出游就取消(使用了2PC)

2PC 存在的问题

它的执行过程中间,节点都处于阻塞状态。即节点之间在等待对方的相应消息时,它将什么也做不了。特别是,当一个节点在已经占有了某项资源的情况下,为了等待其他节点的响应消息而陷入阻塞状态时,当第三个节点尝试访问该节点占有的资源时,这个节点也将连带陷入阻塞状态

由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

  • 数据不一致问题

  • 情形一: 协调者挂了,参与者没挂(单点故障,不会造成数据不一致)

  • 情形二: 参与者挂了, 协调者没挂(不会造成数据不一致)

    • 挂了的参与者不会恢复, 不会造成数据不一致
    • 挂了的参与者恢复过来,如果之前有未完成的事务,直接取消掉,然后询问协调者目前我应该怎么做,协调者就会比对自己的事务执行记录和该参与者的事务执行记录,告诉他应该怎么做来保持数据的一致性
  • 参与者和协调者都挂了

    • 都在第一阶段挂了, 参与者都没有执行 commit, 在剩余的参与者中重新选出一个协调者,新的协调者在重新询问是 commit or roolback
    • 都在第二阶段挂了, 挂了的参与者在挂掉之前没有收到协调者的指令, 或者接到指令还没有来得及进行 commit or roolback 操作,这种情形下,当新的协调者被选出来之后,他同样是询问所有的参与者的情况。只要有机器执行了abort(roolback)操作或者第一阶段返回的信息是No的话,那就直接执行roolback操作。如果没有人执行abort操作,但是有机器执行了commit操作,那么就直接执行commit操作。这样,当挂掉的参与者恢复之后,只要按照协调者的指示进行事务的commit还是roolback操作就可以了。因为挂掉的机器并没有做commit或者roolback操作,而没有挂掉的机器们和新的协调者又执行了同样的操作,那么这种情况不会导致数据不一致现象。
    • 第二阶段协调者和参与者挂了,挂了的这个参与者在挂之前已经执行了操作。但是由于他挂了,没有人知道他执行了什么操作。这种情况下,新的协调者被选出来之后,如果他想负起协调者的责任的话他就只能按照之前那种情况来执行commit或者roolback操作。这样新的协调者和所有没挂掉的参与者就保持了数据的一致性,我们假定他们执行了commit。但是,这个时候,那个挂掉的参与者恢复了怎么办,因为他之前已经执行完了之前的事务,如果他执行的是commit那还好,和其他的机器保持一致了,万一他执行的是roolback操作那?这不就导致数据的不一致性了么?虽然这个时候可以再通过手段让他和协调者通信,再想办法把数据搞成一致的,但是,这段时间内他的数据状态已经是不一致的了! 2PC 无法解决这个问题,这个问题有可能导致数据不一致的 ,于是就有了3PC(三阶段提交)

3PC

三阶段提交(英语:Three-phase commit),也叫三阶段提交协议(英语:Three-phase commit protocol),是在计算机网络及数据库的范畴下,使得一个分布式系统内的所有节点能够执行事务的提交的一种分布式算法。三阶段提交是为解决两阶段提交协议|的缺点而设计的。 与两阶段提交不同的是,三阶段提交是“非阻塞”协议。三阶段提交在两阶段提交的第一阶段与第二阶段之间插入了一个准备阶段,使得原先在两阶段提交中,参与者在投票之后,由于协调者发生崩溃或错误,而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题[1]得以解决。 举例来说,假设有一个决策小组由一个主持人负责与多位组员以电话联络方式协调是否通过一个提案,以两阶段提交来说,主持人收到一个提案请求,打电话跟每个组员询问是否通过并统计回复,然后将最后决定打电话通知各组员。要是主持人在跟第一位组员通完电话后失忆,而第一位组员在得知结果并执行后老人痴呆,那么即使重新选出主持人,也没人知道最后的提案决定是什么,也许是通过,也许是驳回,不管大家选择哪一种决定,都有可能与第一位组员已执行过的真实决定不一致,老板就会不开心认为决策小组沟通有问题而解雇。三阶段提交即是引入了另一个步骤,主持人打电话跟组员通知请准备通过提案,以避免没人知道真实决定而造成决定不一致的失业危机。为什么能够解决二阶段提交的问题呢?回到刚刚提到的状况,在主持人通知完第一位组员请准备通过后两人意外失忆,即使没人知道全体在第一阶段的决定为何,全体决策组员仍可以重新协调过程或直接否决,不会有不一致决定而失业。那么当主持人通知完全体组员请准备通过并得到大家的再次确定后进入第三阶段,当主持人通知第一位组员请通过提案后两人意外失忆,这时候其他组员再重新选出主持人后,仍可以知道目前至少是处于准备通过提案阶段,表示第一阶段大家都已经决定要通过了,此时便可以直接通过。(from Wikipedia)

算法示意图

存在的问题

在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。 所以,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

参考

  1. wikipedia
  2. 分布式系统的事务处理
  3. 关于分布式事务、两阶段提交协议、三阶提交协议
  4. 深入理解分布式系统的2PC和3PC