两阶段提交与自杀
写在开头:以下内容基于个人对算法的理解和实操经验,可能存在漏洞,不要盲目相信……
在分布式系统中,为了实现事务(Transaction),通常比在单机环境中要困难许多。为了解决分布式事务的问题,两阶段提交(2PC)是一个比较传统的做法。
2PC的核心思想是(我认为的)分离事务的执行和提交。假设分布式系统中有1个master节点和n个slave节点。在第一阶段(prepare):
- master发起一个事务T,执行(但不提交,由事务隔离性对外不可见),并分发给slave。
2. slave尝试执行T(不提交),返回给master结果(成功or失败),本地写入redo和undo日志
3(a). master收到全部成功结果,发送提交T指令给slave,转到4
3(b). master收到了失败结果,发送abort T给slave,事务结束(执行失败)
4. slave尝试提交T,返回给master结果(成功or失败)
5(a). master收到全部成功结果,本地提交T,若成功则事务结束(执行成功),否则转到5(b)
5(b). master收到失败的slave结果,发送abort T给slave,事务结束(执行失败)
6. slave收到abort指令,使用undo日志回滚
大概就是如图这个样子,具体实现还可以优化的,比如在各个地方加计时器超时自动回滚等等。
看起来挺完美,但是实际上分布式环境受到网络、IO等各种因素的影响,有很多预想不到的情况出现。像这种2PC明显就有很多问题:
- 单点故障:因为每个事务执行都是串行的,单点故障会拖慢整体效率,某些情况会使系统状态不确定。
- 一致性:主从节点不是同时commit的,肯定会有不一致的时间存在。
网上有一种说法:说如果在master发出commit之后,只有一台机器(S1)收到了这个commit指令,恰巧这个时候Master和S1同时挂了,这样这个事务就进入了一种进退两难的局面:新的Master不知道这个事务应该被提交还是被取消。
网上很多人说这个很“致命”,但是实际上,新的Master可以通过重新询问slave来确定这个事务是否可以被提交(可能与最初的决策不一样,但是结果对现在的环境仍然是合法的),但是这个时间可能会很长,相当于第二阶段重新做了一遍。而所谓的“三阶段提交”我任务无非是对决策再加了一次确认,这样新的Master起来之后发现之前的确认日志,就可以断定这个事务在系统中是可以被提交的。
===========================================================
之前实习还做过这样一种方案,因为很多分布式服务都会有HA框架在的,为了提高二段提交的效率,我们给二段提交加了几个限制:
- 多数slave提交成功,master就认为成功
- 事务全串行,唯一递增ID,节点执行事务ID必须连续(有一个BufferQueue存放未执行事务)
- 谁出错谁自杀(提交失败或Queue溢出)
因为自杀的成本是比较低的(HA会帮忙恢复),而且在我们的限制下,如果出现提交错误,那基本都是由IO问题引起的,而且出现的概率是非常低的。此时失败节点大概率已经出现了与主节点的不一致,所以这个时候自杀重启动是正确的选择。
但是这个feature已经在我离职之后被砍掉了