在分布式系统中,当一个事务操作需要跨越多个分布式节点时,为了保持事务的ACID特性,就出现了“协调者(Coordinator)”来统一调度所有分布式节点的执行逻辑,而被调度的分布式节点则被称为“参与者(Cohort)”。协调者(Coordinator)负责调度参与者(Cohort)的行为,并最终决定这些参与者(Cohort)是否把事务真正进行提交。基于这个思想,衍生出2PC和3PC两种协议。
一. 2PC (Two-Phase Commit)
2PC(Two-Phase Commit)为二阶段提交,为了分布式系统下所有节点操作事务能够保存原子性和一致性而设计的一种算法。主要应用在关系型数据库来完成分布式事务处理,利用该协议可以方便地利用协调者(Coordinator)进行统一的事务提交和回滚,从而能够有效的保证分布式数据一致性。
2PC的两阶段
阶段一(提交请求阶段)
各参与者(Cohort)投票表明是否要继续执行接下来的事务提交操作:
- 协调者(Coordinator)节点向所有参与者(Cohort)节点询问是否可以执行提交操作,并开始等待各参与者(Cohort)节点的响应。
- 参与者(Cohort)节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。
- 各参与者(Cohort)节点响应协调者(Coordinator)节点发起的询问。如果参与者(Cohort)节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者(Cohort)节点的事务操作实际执行失败,则它返回一个"中止"消息。
阶段二(提交执行阶段)
协调者(Coordinator)会根据参与者(Cohort)的反馈情况来决定是否可以进行事务提交操作,可分为事务提交以及事务中断两种情况 。
事务提交:
当协调者(Coordinator)节点从所有参与者(Cohort)节点获得的对应的消息都为"Yes"时:
- 协调者(Coordinator)节点向所有参与者(Cohort)节点发出"Commit"的请求。
- 参与者(Cohort)节点收到Commit请求后,正式执行事务操作,并释放在整个事务期间内占用的资源。
- 参与者(Cohort)节点向协调者(Coordinator)节点发送"Ack"消息,确认完成。
-
协调者(Coordinator)节点收到所有参与者(Cohort)节点反馈的"Ack"完成消息后,完成事务。
事务中断:
如果任一参与者(Cohort)节点在第一阶段返回的响应消息为"No",或者协调者(Coordinator)节点在第一阶段的询问超时之前无法获取所有参与者(Cohort)节点的响应消息时:
- 协调者(Coordinator)节点向所有参与者(Cohort)节点发出"Rollback"请求。
- 参与者(Cohort)节点接收到"Rollback"请求,利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
- 参与者(Cohort)节点向协调者(Coordinator)节点发送"Ack"回滚完成消息。
- 协调者(Coordinator)节点收到所有参与者(Cohort)节点反馈的"Ack"回滚完成消息后,取消事务。
缺点
同步阻塞问题:执行过程中,所有参与者(Cohort)节点都是事务阻塞型。各个参与者(Cohort)在等待其他参与者响应的过程中,将无法进行其他任务操作;
单点故障:由于协调者(Coordinator)的重要性,一旦协调者(Coordinator)发生故障,参与者(Cohort)会一直阻塞下去。特别是在阶段二中,协调者(Coordinator)发生故障,那么所有的参与者(Cohort)还都处于锁定事务资源的状态中,而无法继续完成事务操作。
数据不一致:在阶段二中,当协调者(Coordinator)向参与者(Cohort)发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者(Coordinator)出现问题,将导致只有一部分参与者(Cohort)收到commit请求,以至于这部分参与者(Cohort)接到commit请求之后就会执行commit操作,而其他部分未接到commit请求的参与者(Cohort)则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
不具备完善容错机制:任意一个节点的失败都会导致整个事务的失败。参与者(Cohort)宕机或者超时,都需要协调者(Coordinator)自身机制去判断或者协调者(Coordinator)在发出commit消息之后宕机,而唯一接收到这条消息的参与者(Cohort)同时也宕机了。那么即使协调者(Coordinator)通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
二. 3PC (Three-Phase Commit)
3PC (Three-Phase Commit)为了改进2PC中出现的同步阻塞、单点问题、脑裂以及保守的容错机制缺陷提出的三阶段提交协议,分为CanCommit、PreCommit和doCommit三阶段进行事务处理协议。如下图:
阶段一:CanCommit
- 协调者(Coordinator)节点向所有参与者(Cohort)节点询问是否可以执行提交操作,并开始等待各参与者(Cohort)节点的响应。
- 协调者(Coordinator)向参与者(Cohort)发送commit请求,参与者(Cohort)如果可以提交就返回Yes响应进入预备状态,否则返回No响应。
阶段二:PreCommit
协调者(Coordinator)根据参与者(Cohort)的反应情况来决定是否可以继续事务的PreCommit操作。根据响应情况,有以下两种可能执行提交和中断事务:
执行提交:
协调者(Coordinator)从所有的参与者(Cohort)获得的反馈都是Yes响应,那么就会进行事务的预执行:
- 发送预提交请求::协调者(Coordinator)向参与者(Cohort)发送PreCommit请求,并进入Prepared阶段。
- 事务预提交:参与者(Cohort)接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
- 响应反馈:如果参与者(Cohort)成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
中断事务:
有任何一个参与者(Cohort)向协调者(Coordinator)发送了No响应,或者等待超时之后,Coordinator都没有接到参与者(Cohort)的响应,那么就中断事务:
- 发送中断请求:协调者(Coordinator)向所有参与者(Cohort)发送abort请求。
- 中断事务:参与者(Cohort)收到来自协调者(Coordinator)的abort请求之后(或超时之后,仍未收到参与者(Cohort)的请求),执行事务的中断。
阶段三:DoCommit
该阶段进行真正的事务提交,也可以分为以下两种情况执行提交和中断事务:
执行提交
- 发送提交请求:协调者(Coordinator)接收到参与者(Cohort)t发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者(Cohort)发送doCommit请求。
- 事务提交:参与者(Cohort)接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
- 响应反馈:事务提交完之后,向协调者(Coordinator)发送ACK响应。
- 完成事务:协调者(Coordinator)接收到所有参与者(Cohort)的ACK响应之后,完成事务。
中断事务:
协调者(Coordinator)正常,接收到参与者(Cohort)发送的反馈No响应,或者没有收到到Ack响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
- 发送中断请求:协调者(Coordinator)向所有参与者节点发送abort请求;
- 事务回滚:参与者(Cohort)接收到abort请求后,利用记录的Undo信息进行事务回滚操作,并在回滚完成后释放整个事务执行期间占用的资源。
- 反馈事务回滚结果:参与者(Cohort)在完成事务回滚之后,向协调者(Coordinator)发送Ack消息。
- 中断事务:协调者(Coordinator)接收所有参与者(Cohort)的反馈Ack消息后,中断事务。
优缺点
- 优点: 降低参与者阻塞范围,并能够在出现单点故障后继续达成一致
- 缺点: 引入preCommit阶段,在这个阶段如果出现网络分区,协调者无法与参与者正常通信,参与者依然会进行事务提交,造成数据不一致。
三. 总结
无论是二阶段提交还是三阶段提交都从不同程度地解决了分布式数据一致性问题,使用范围非常广泛,但都无法彻底解决分布式的一致性问题。还有一种一致性协议Paxos算法,解决了无限期等待问题,也解决了“脑裂”问题,通俗易懂的Paxos原理可查看文章《通俗易懂的Paxos算法-基于消息传递的一致性算法》。