二阶段提交协议
二阶段提交(Two-phaseCommit
)是在计算机网络以及数据库领域内,为了使分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。
在分布式系统中,每个节点虽然可以知晓自己的操作是成功或者失败,却无法知道其他节点操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的一致性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。
因此,二阶段提交的算法思路可以概括为:
- 参与者将操作成败通知协调者;
-
由协调者根据所有参与者的反馈情况决定各参与者是要提交操作还是中止操作。
X/Open DTP(Distributed Transaction Process
)是一个分布式事务模型,此模型主要使用二阶段提交(2PC,Two-Phase-Commit
)来保证分布式事务的完整性。在这个模型里面,有三个角色:
-
AP:Application,应用程序,业务层。
-
RM:Resource Manager,资源管理器,关系型数据库或支持 XA 接口(XA 规范是 X/Open 组织定义的分布式事务规范)的组件。
-
TM: Transaction Manager ,事务管理器,负责各个 RM 的提交和回滚。
当应用程序(AP)调用了事务管理器(TM)的提交方法时,事务的提交分为两个阶段实行。
第一阶段(准备阶段)
TM 通知所有参与事务的各个 RM,给每个 RM 发送 prepare
消息。
RM 接收到消息后进入准备阶段后,要么直接返回失败,要么创建并执行本地事务,写本地事务日志(redo 和 undo 日志),但是 不提交(此处只保留最后一步耗时最少的提交操作给第二阶段执行)。
第二阶段(提交 / 回滚阶段)
TM 收到 RM 准备阶段的失败消息或者获取 RM 返回消息超时,则直接给 RM 发送回滚(rollback
)消息,否则发送提交(commit
)消息。
RM 根据 TM 的指令执行提交或者回滚,执行完成后释放所有事务处理过程中使用的锁(最后阶段释放锁)。
二阶段提交的利弊
优点
2PC 提供了一套完整的分布式事务的解决方案,遵循事务严格的 ACID 特性。
缺点
-
TM 通过 XA 接口与各个 RM 之间进行数据交互,从第一阶段的准备阶段,业务所涉及的数据就被锁定,并且锁定跨越整个提交流程。在高并发和涉及业务模块较多的情况下 对数据库的性能影响较大。
-
二阶段是 反可伸缩模式 的,业务规模越大,涉及模块越多,局限性越大,系统可伸缩性越差。
-
在技术栈比较杂的分布式应用中,存储组件有很多 不支持 XA 协议。
二阶段的诸多弊端,导致分布式系统下无法直接使用此方案来解决数据一致性问题,但它提供了解决分布式系统下数据一致性问题的思路。
三阶段提交(3PC)
分布式事务的实现除了 2PC 外,还有 3PC。3PC 主要多了事务超时、多次重复尝试,以及提交 check 的功能。但因为确认步骤过多,很多业务的互斥排队时间会很长,所以 3PC 的事务失败率要比 2PC 高很多。
-
CanCommit 阶段: 协调者向所有参与者发送
CanCommit
请求,询问是否可以提交事务。参与者在此阶段会执行事务的预提交,并向协调者发送Yes
或No
。 -
PreCommit 阶段: 如果所有参与者都发送了
Yes
,协调者会发送PreCommit
请求,表示可以提交事务。参与者在此阶段会执行正式的提交操作,并向协调者发送Ack
。 -
DoCommit 阶段: 如果协调者收到所有参与者的
Ack
,它会发送DoCommit
请求,要求参与者最终提交事务。参与者在此阶段正式提交事务。
CanCommit阶段
-
事务询问:
Coordinator
向各参与者发送CanCommit
的请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应; -
参与者向
Coordinator
反馈询问的响应:参与者收到CanCommit
请求后,正常情况下,如果自身认为可以顺利执行事务,那么会反馈 Yes 响应,并进入预备状态,否则反馈 No。
PreCommit阶段
执行事务预提交:如果 Coordinator
接收到各参与者反馈都是Yes,那么执行事务预提交:
-
发送预提交请求:
Coordinator
向各参与者发送preCommit
请求,并进入prepared
阶段; -
事务预提交:参与者接收到
preCommit
请求后,会执行事务操作,并将Undo
和Redo
信息记录到事务日记中; -
各参与者向
Coordinator
反馈事务执行的响应:如果各参与者都成功执行了事务操作,那么反馈给协调者 ACK 响应,同时等待最终指令,提交commit
或者终止abort
,结束流程;
中断事务:如果任何一个参与者向 Coordinator
反馈了 No 响应,或者在等待超时后,Coordinator
无法接收到所有参与者的反馈,那么就会中断事务。
-
发送中断请求:
Coordinator
向所有参与者发送abort
请求; -
中断事务:无论是收到来自
Coordinator
的abort
请求,还是等待超时,参与者都中断事务。
doCommit阶段
执行提交
-
发送提交请求:假设
Coordinator
正常工作,接收到了所有参与者的 ack 响应,那么它将从预提交阶段进入提交状态,并向所有参与者发送doCommit
请求; -
事务提交:参与者收到
doCommit
请求后,正式提交事务,并在完成事务提交后释放占用的资源; -
反馈事务提交结果:参与者完成事务提交后,向
Coordinator
发送 ACK 信息; -
完成事务:
Coordinator
接收到所有参与者 ack 信息,完成事务。
中断事务:假设 Coordinator
正常工作,并且有任一参与者反馈 No,或者在等待超时后无法接收所有参与者的反馈,都会中断事务
-
发送中断请求
Coordinator
向所有参与者节点发送abort
请求; -
事务回滚:参与者接收到
abort
请求后,利用undo
日志执行事务回滚,并在完成事务回滚后释放占用的资源; -
反馈事务回滚结果:参与者在完成事务回滚之后,向
Coordinator
发送ack
信息; -
中断事务:
Coordinator
接收到所有参与者反馈的ack
信息后,中断事务。
两阶段提交与三阶段提交的区别
-
性能: 2PC 通常比 3PC 更简单,因为 3PC 多了一个预提交阶段。然而,3PC 在某些情况下可能更快,因为它在部分失败的情况下能够更快地恢复。
-
阻塞: 2PC 可能在第二阶段发生阻塞,导致整个系统等待。3PC 试图解决这个问题,但在一些情况下仍然可能出现类似的阻塞。
-
不完全崩溃: 3PC 更适合在部分故障恢复的情况下,因为它在第一和第二阶段之间增加了一个预提交阶段,从而可以更好地处理一些故障情况。
无论选择使用两阶段提交还是三阶段提交,都需要根据具体的应用需求和系统特点进行权衡。同时,需要注意这些协议仍然无法解决所有分布式事务的问题,例如网络分区等情况仍可能导致一致性问题。