前言
现在的互联网项目基本都是分布式部署,分布式让系统处理能力变得强大,但也带来了新的挑战,比如zookeeper分布式集群部署,需要考虑各个节点的数据一致性,为了解决这个问题,科学家们提出了2PC(二阶段提交)和3PC(三阶段提交)。
二阶段提交 2PC
二阶段提交中有两个角色,协调者和参与者,分为两个步骤:
1.准备阶段
- 协调者向参与者发送请求,询问参与者是否可以执行事务,并开始等待各参与者响应
- 参与者收到执行事务的请求,如果可以执行,那么开始执行事务,记录undo和redo日志(这一步参与者已经开始执行事务,但没有提交事务)
- 执行成功后给协调者反馈,如果事务执行成功返回 同意,反之返回 中止 消息
2.提交阶段
- 协调者向参与者发出提交事务的请求
- 参与者收到请求后完成事务提交操作,之后开始释放整个事务期间内占用的资源
- 参与者想协调者节点发送完成消息
- 协调者接收到所有参与者节点反馈的 完成 消息后,完成事务
如果接受不到准备阶段参与者返回的消息或者超时时,直接给每一个参与者发送回滚消息
回滚
如果任一参与者在第一阶段返回的响应消息为“中止”或者协调者在第一阶段的询问超时之前无法获取到所有参与者响应的信息,这需要进行事务回滚操作。
- 协调者节点向所有参与者发送 回滚操作 的请求
- 参与者节点根据之前写入的undo日志执行回滚,并释放整个事务期间内占用的资源
- 参与者节点向协调者响应 回滚完成 消息
- 协调者接收到所有参与者节点反馈的 回滚完成 消息后,取消事务
二阶段提交存在的问题
大致看起来确实能够提供原子性的操作,但认真研究会发现存在几个问题:
- 同步阻塞:执行过程中所有参与者节点都会事务阻塞。当参与者占用公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
- 单点故障:一旦协调者发生故障,参与者会一直阻塞下去。
- 数据不一致:在第二阶段中,当协调者向参与者发送commit请求后,发生了网络异常或者其他原因导致部分参与者无法接收到commit请求,最终会导致部分参与者执行了commit,而部分参与者却没有执行commit而导致数据不一致的问题。
2PC无法解决数据不一致的问题,比如:协调者再发出commit消息之后宕机,恰巧唯一接受到这个请求的参与者也宕机了。那么及时协调者通过重新选举产生新的协调者,这条事务的状态也是不确定的,没人知道事务是否已经被提交。
由于二阶段提交存在诸多问题,所以提出优化方案,于是出现三阶段提交,但也没有从根本上解决二阶段提交存在的问题。
三阶段提交 3PC
3PC为协调者和参与者都新增了超时机制,新增一个预准备阶段
三阶段提交故名思议,分为三个步骤:
1.预准备阶段
- 协调者询问所有参与者是否可以执行事务
- 参与者响应协调者 Yes 或者 No
2.准备阶段
如果第一阶段所有参与者都返回Yes
- 协调者向参与者发送执行事务请求
- 参与者接收请求,执行事务,记录undo和redo日志
- 参与者响应协调者ACK
- 协调者发送事务中断请求给参与者
- 参与者接收到请求后执行中断操作,释放整个事务期间占用的资源
3.提交阶段
如果在第二阶段协调者收到所有参与者反馈的ACK消息
- 协调者请求参与者执行提交事务
- 参与者执行事务提交,并释放整个事务期间占用的资源
- 参与者提交事务后相应ACK给协调者
- 协调者接收到ACK消息,事务完成
- 协调者发送中断请求给参与者
- 参与者根据undo日志执行回滚操作,释放所有事务资源
- 参与者完成事务回滚之后,响应ACK给协调者
- 协调者接收到ACK消息后执行事务中断
当步骤进行到提交阶段,参与者没有接收到协调者的提交事务请求,当超时后参与者会继续执行事务提交操作,因为参与者任务,已经到这一步了,大概率是会执行成功的,不应该在这里进行中断,所以会提交事务,执行完成。当然,这一步可能会导致数据不一致,但这是小概率问题,可以通过其他补偿机制进行处理。
总结
- 无论是2PC还是3PC都无法解决分布式的一致性问题。
- 2PC只有协调者可以设置超时时间
- 3PC协调者和参与者都可以设置超时时间
感兴趣的朋友可以看一下ZAB协议,在Zookeeper中,就是根据ZAB来解决数据一致性问题。有时间再写一篇关于ZAB协议的问题。
结尾
本文如有出错的地方,希望不要吝啬,欢迎指出,一起讨论,谢谢。