在分布式系统的架构设计中,往往需要对可用性和一致性进行权衡,为了解决分布式一致性的问题,诞生了2PC、3PC和Paxos等算法。
2PC
2PC(Two-Phrase Commit)全称两阶段提交,从名字也可以看出,2PC是将事务的提交划分为两个阶段执行。
阶段一:事务提交过程
- 发送请求:协调者向各个参与者发送事务内容,并询问是否可以执行事务,等待参与者响应
- 执行事务:各个参与者开始执行事务,并将Undo和Redo记录到事务日志中
- 响应请求:事务执行成功,参与者向协调者返回YES;事务执行失败,参与者向协调者返回NO
阶段二:执行事务提交
阶段二将包含两种情况:第一种是参与者全部返回YES,则协调者发起事务commit的提交请求,如果有参与者返回false或者协调者等待响应超时,则发起 中断事务请求。
执行提交:
- 发送请求:协调者向各个参与者发送commit请求
- 提交事务:参与者在收到commit请求后,执行事务提交,提交完成后释放相关资源
- 响应请求:参与者提交事务成功后,向协调者发送ACK消息
- 事务完成:参与者在收到ACK后,完成事务提交
中断事务:
- 发送请求:协调者向各个参与者发送中断事务请求
- 回滚事务:参与者利用阶段一的Undo信息执行回滚操作,回滚完成后释放相关资源
- 响应请求:回滚完成后,向协调者发送ACK消息
- 事务完成:参与者在收到ACK后,完成事务回滚
两阶段提交看似很完美,但是存在以下缺点:同步阻塞、单点故障、脑裂、太过保守。
- 同步阻塞:在2PC过程中,参与者在等待其他参与者的响应过程中无法执行其他操作
- 单点故障:这里的协调者只有一个,假设协调者宕掉,那么整个2PC阶段将无法执行
- 脑裂:在2PC的第二阶段,假设因为网络分区,部分参与者收到了commit请求执行了事务提交,而另一部分参与者因无法收到请求无法执行事务提交,因此出现数据不一致现象
- 太过保守:协调者在等到超时之后将会发起中断事务请求,回滚事务
3PC
为了解决2PC阶段遇到的问题,又演化出来了三阶段提交过程,三阶段提交过程可以简单的理解为将2PC中的第一阶段一拆为二,形成了canCommit、preCommit和doCommit的三个阶段。
阶段一:canCommit
- 事务询问:协调者向各个参与者发送一个包含事务内容的canCommit请求,询问是否可以提交事务请求,并开始等待参与者的响应
- 响应询问:参与者收到canCommit请求后,如果可以顺利执行事务,那么会返回YES响应,并进入预备状态,否则返回NO响应
阶段二:preCommit
在阶段二中,存在两种情况:协调者如果收到所有的响应都是YES则执行事务预提交请求,如果有NO的返回或者协调者等到超时则执行中断事务请求
执行事务预提交:
- 发送事务预提交请求:协调者向各个参与者发送preCommit请求,并进入Prepared阶段
- 事务预提交:参与者收到preCommit请求后,会执行事务操作,并将Undo和Redo记录到事务日志中去
- 响应预提交请求:参与者事务执行成功之后返回ACK响应,同时等待最终的指令
中断事务:
- 发送中断请求:协调者向各个参与者发送中断请求(abort)
- 中断事务:参与者收到abort请求或者等待协调者超时都会中断事务
阶段三:doCommit
该阶段执行真正的事务提交,主要包含两种情况:参与者在第二阶段返回的全是YES响应,则执行提交请求,否则如果有NO响应或者等待参与者响应超时后都会发起中断事务请求。
执行提交:
- 发送请求:协调者向各个参与者发送提交事务请求,状态从预提交状态转换为提交状态
- 事务提交:参与者在收到doCommit请求或者等到超时后,会执行事务提交操作,完成提交后将会释放事务占有的资源
- 反馈提交结果:参与者在完成事务提交后,向协调者返回ACK响应
- 完成事务:协调者收到所有参与者返回的ACK消息后,完成事务
中断事务:
- 发送中断请求:协调者向所有的参与者发送abort请求
- 事务回滚:参与者利用第二阶段的Undo信息来执行事务回滚操作,回滚完成后释放事务所占用的资源
- 反馈结果:事务回滚完成后,向协调者发送ACK消息
- 中断事务:协调者收到所有参与者的ACK消息后,中断事务
3PC最大的优点是降低了2PC的同步阻塞范围,但是还是存在数据不一致的问题:假设现在事务进入第三阶段,如果发生了网络分区,协调者和参与者通信发生故障,协调者发送的是中断事务请求,此时收到请求的参与者将进行事务回滚,但是通信故障的参与者将无法收到请求而等到超时进行事务提交操作,然后会导致数据不一致。