赵玉伟的博客

两阶段、三阶段提交

概述

当一个系统的功能复杂度上升到一定层级后, 会导致业务之间的依赖变的错综复杂。 在互联网领域中, 需求的迭代、变更却会持续进行, 如果是一个单服务系统,业务交织在一起的结果是改一个功能点, 但影响到了其它的各个领域, 导致”牵一发而动全身“。带来的后果是系统难维护甚至不可维护,不能扩展等等。服务化可以有效的解决这个问题, 其实也是一种”分治“的思想, 就是把一个大的问题按照某个维度切分成多个小的问题, 分而治之, 可以明确问题的解决脉络。 虽然服务化也会带来一些问题, 比如: 增加运维成本, 服务化分的粒度等, 以及接下来的分布式事务问题,但是,集中式服务到分布式服务势在必行。

CAP&BASE

分布式架构必然涉及多个节点, 节点与节点之间必然需要通过网络进行通信, 网络通信肯定会有异常(超时、 没有响应、 返回错误等); 服务节点也肯定会宕机。 这是实施分布式系统的基础问题,也就是,假想下网络通信一定不出问题, 节点一定不会宕机, 在这种完美的情况下, 开发一套分布式系统就像开发一个单服务系统一样简单。所以,分布式系统很大一部分情况是考虑各种异常的case。 由此, 业界内有CAP理论(个人认为按照重要程度排序,应该叫PAC)和BASE思想作为我们实施的基础, 关于CAP和BASE大家可以网上搜索, 简而言之,就是用base思想解决分布式系统中的cap问题。
CAP:
Partition tolerance: 网络的可靠性。 不能出现子网络(脑裂), 实际中这是一定要绝对保证的, 这个要求最高。
Availability: 可用性。 实际中可以出现基本可用,比如业务降级, 这个要求次之。
Consistency: 一致性。 实际中很少保证数据强一致性, 一般只能保证某一阶段的弱一致性和最终一致。

分布式事务

事务具有一致性的特点, 由于网络的不可靠性以及事务涉及多个节点, 在这种基础下为了提高可用性, 实际上分布式事务基本上不保证强一致性,只保证最终一致。在分布式事务的问题中, 有以下三种解决方案:

1、两阶段提交
2、三阶段提交
3、paxos算法

其中,两阶段、三阶段提交只是一种理论, 目前解决很少把这种理论转化成生产力,因为可用性太差, 但是给我们提供了解决分布式事务的一种思路, 所以有必要了解。 实际中多数分布式事务都是由paxos算法作为实现基础,但是该算法比较复杂, 我退而求其次, 说一下两阶段、三阶段提交这两种协议。

两阶段提交(2PC)

实现基础:
1、依赖的数据库可以支持两阶段提交,比如mysql中的事务提交可以理解为两阶段,mysql通过写redolog、undolog实现。
2、由一个协调者向多个参与者发起事务提交请求, 同时, 协调者和参与者需要记录log,用来记录事务走过的“轨迹”, 以便当节点挂掉重启后, 可以根据log恢复事务的最后状态。

阶段一: 投票
协调者向各个参与者发起是否可以提交的询问请求,等待参与者响应。
参与者执行事务的询问请求, 记录redolog、undolog。
参与者响应协调者的请求,根据自身的操作结果,返回“同意”或者“终止”。

阶段二: 执行
A:执行“提交”
协调者收到每个参与者的“同意”响应,再向各参与者发起“提交”请求,等待参与者响应。
参与者收到“提交”请求后, 每个参与者提交事务, 释放占用的资源。
参与者发送“完成”响应。
协调者收到“完成”后响应后,结束事务。

B:执行“终止”:
协调者收到某一个或者多个参与者的“终止”响应,或者协调者“等待超时后, 再向各参阅者发起“终止”请求, 等待参与者响应。
参与者收到“终止”请求后, 每个参与者回滚事务,释放占用的资源。
参与者发送“完成”请求。
协调者收到”完成“响应后,结束事务。

背后的问题:
1、 从事务开始阻塞到事务结束, 参与者阻塞太久, 这其中参与者占用了公用资源时(比如synchronized), 其它的第三方、第四方的请求会被block。
2、 容错性。 协调者单点,一旦挂掉, 参与者不会主动修改自己的状态,导致持续阻塞,直到协调者重启或者协调者的备机替换。
3、 容错性。 参与者挂掉, 协调者只能增加超时机制, 一旦某个参与者超时, 协调者向其它参与者发送回滚请求。
4、 不一致性。 如果参与者和协调者没有同时挂掉, 因为都会记录状态,所以可以通过恢复后再来一次的方式继续流程。但是,在执行阶段, 协调者和某个参与者同时挂掉, 导致状态丢失。 比如: 协调者发送commit后挂掉了,同时参与者收到请求后也挂掉了, 那么参与者有可能没来得及执行commit, 也有可能已经执行完了commit, 这个状态没有记录,这种可能性会导致数据不一致的产生。 因为协调者重启后,按照协调者记录的log继续执行未完成的流程, 假如继续给其它参与者发送commit,而挂掉的机器却执行了rollback。

三阶段提交(3PC)

三阶段是在二阶段的问题上,对其做的补充,主要解决以下两个问题:
1、减少阻塞时间。 在协调者和参与者都引入超时机制, 这样可以解决, 如果协调者挂掉, 参与者可以在超时后执行回滚或者提交操作, 减少了阻塞时间,同时也保证协调者挂掉后, 有一个最基本的容错机制。
2、插入一个准备阶段,概率性的解决了一致性问题,只是概率性的解决。

以下直接引入:
直接分析协调者和参与者都挂的情况。
第二阶段协调者和参与者挂了,挂了的这个参与者在挂之前已经执行了操作。但是由于他挂了,没有人知道他执行了什么操作。
这种情况下,当新的协调者被选出来之后,他同样是询问所有的参与者的情况来觉得是commit还是roolback。这看上去和二阶段提交一样啊?他是怎么解决一致性问题的呢?
看上去和二阶段提交的那种数据不一致的情况的现象是一样的,但仔细分析所有参与者的状态的话就会发现其实并不一样。我们假设挂掉的那台参与者执行的操作是commit。那么其他没挂的操作者的状态应该是什么?他们的状态要么是prepare-commit要么是commit。因为3PC的第三阶段一旦有机器执行了commit,那必然第一阶段大家都是同意commit。所以,这时,新选举出来的协调者一旦发现未挂掉的参与者中有人处于commit状态或者是prepare-commit的话,那就执行commit操作。否则就执行rollback操作。这样挂掉的参与者恢复之后就能和其他机器保持数据一致性了。(为了简单的让大家理解,笔者这里简化了新选举出来的协调者执行操作的具体细节,真实情况比我描述的要复杂)
简单概括一下就是,如果挂掉的那台机器已经执行了commit,那么协调者可以从所有未挂掉的参与者的状态中分析出来,并执行commit。如果挂掉的那个参与者执行了rollback,那么协调者和其他的参与者执行的肯定也是rollback操作。*

关于两阶段三阶段的配图不再配, 因为配图主要演示了正常的流程, 而重点却在于异常的处理机制。 该文章主要用来记录本人对两阶段、三阶段的理解, 深度有限, 可以参考以下两篇文章。

实际的处理场景

曾经看到过一句话: 解决分布式事务的最好的方式就是不考虑分布式事务。 其实就是服务对外不提供强一致性, 试想一个下单扣款的流程, 业务上实际上可以允许下单之后,余额没有马上扣掉,但是最终肯定会扣掉的。 所以, 实现上, 在下单模块只需要专注下单的业务, 而不需要一定在扣款完成之前保持阻塞。 所以,通消息中间件可以实现数据的强一致性, 这样能够保证每个系统最快的完成自己专注的业务, 提供非常好的可用性。

参考:
http://www.mamicode.com/info-detail-890945.html
http://blog.csdn.net/yyd19921214/article/details/68953629