1. 前言
副本一致性是分布式系统的基本问题之一。副本问题是组通信问题,达成副本一致有很多种方式,在介绍具体算法之前,先从更高的层面上看看通信模型。

客户端发起请求,最后客户端收到回复,有两种常见的模式:
- 同步:所有节点必须全部成功,才返回client
- 异步:一个成功,立刻返回
如果你还不了解分布式系统,那么欢迎戳它:分布式系统基本概念-(1)
如果你想进一步了解分布式系统的基本特征以及consensus问题,那么欢迎戳它:分布式系统上下层概念抽象-(2)
如果你想进一步了解分布式系统时钟,向量时钟等问题,那么欢迎戳它:分布式系统时钟和有序-(3)
1.1. 同步副本
- 从组通信的角度,同步副本是一个N-to-N的模型。也就是说在客户端返回之前,所有的server必须事先完成处理。
- 从性能的角度,客户端的响应latency最终取决于最慢的server,这也就是说此模型对网络延迟很敏感。
- 从可用性容错的角度,N-N意味着任何server都不能发生故障,只要有一个故障,系统就不能接受写入请求。
- 从可靠性角度,系统能保证最强的durability,只有全部N个servers故障,才可能导致写入丢失。
1.2. 异步副本
同上述分析
- 从组通信的角度,异步副本是1-to-N的模型,也即是只要一个server写入成功立刻返回客户端,这个server内部负责随后的通信。
- 从性能的角度,延迟低,不需要等待全部的servers,而且对网络延迟并不敏感;
- 从可用性容错的角度,只要剩下至少一个节点,系统依然可以对外提供写服务;
- 从可靠性的角度,系统提供概率性的保证,理想情况下所有副本最后都同步成功,然而碰巧同步之前这个server故障,数据可能丢失。
异步副本并不能保证系统的所有的节点都是一致的状态的。如果发生网络分区,不同分区的节点都可能承担了写入操作,那么客户端读取的时候,可能读取到不一致的结果。上面的讨论主要涉及写入,并未涉及读取操作,当读取的时候,我们总希望尽可能读取更少的节点,同时有具有数据的一致性保证。
2. 副本相关的算法
有两大类:
- 阻止分叉(single copy systems)
- 有分叉的风险(multi-master systems)
对于第一种single-copy systems,这个系统确实像一个单机系统一样工作,能够保证副本的一致性,且阻止网络分叉。这类问题就是很有名的consensus问题。对于consensus,它有以下属性:
- Agreement:所有正常的节点都达成一致的value v;
- Integrity:所有正常的节点最多选择一个value,而且如果它选择的value必须是被propose过的value(不能任意猜测一个);
- Termination:算法会终止,所有的节点都会做出决定;
- Validity:如果所有的节点都propose相同的value v,那么所有的节点会选择v。
对于第二种multi-master,副本可能出现分叉,需要解决冲突。
有四种达成single-copy的算法:
- 1n messages (asynchronous primary/backup)
- 2n messages (synchronous primary/backup)
- 4n messages (2-phase commit, Multi-Paxos)
- 6n messages (3-phase commit, Paxos with repeated leader election)
2.1. primary/backup Replication
这其实就是常见的master/slave方案,有异步和同步两种模式。
同步:2条消息,先向slave发起update请求,然后等待slave的回复;
异步:1条消息,只需要向slave发请求,不等待回复。
异步模式只有weak保证:例如Mysql的master使用log异步复制的方式来保持master和slave的同步,然而master和slave总是会相差至少一个update。如果master更新完local之后,还未向slave发起update消息就挂了,那么这个更新就丢了。
同步模式只有weak保证:这个可能很难理解。假如master向slave发起更新请求,且更新成功。在master向客户端返回结果之前,master挂了,这样client以为没有成功,然后slave却成功提交了请求。
PB模式只能提供best-effort的一致性保证,特别是当节点在不合时宜
的时候挂掉,很容易导致丢失更新或者错误更新的问题,另外它也很难抵御脑裂(双主)这种情况。
为了应对这种failure引起的不一致的问题,2PC(2 phase commit)提出来了。
2.2. Two phase commit (2PC)
两阶段提交协议存在于很多传统的数据库中,像Mysql cluster就是使用的2PC协议。
[ Coordinator ] -> OK to commit? [ Peers ]
<- Yes / No
[ Coordinator ] -> Commit / Rollback [ Peers ]
<- ACK
第一阶段:投票阶段,如果选择voting,那么参与者将结果保存在临时的区域(write-ahead log)
第二阶段:决策阶段,协调者根据参与者的反馈执行相应的操作,只有当所有参与者都投票,协调者才会通知参与者将临时的结果持久保存下来。
第一个阶段给取消操作提供了可能。不同于PB Replication,主发送update请求,备立刻执行,2PC采用了两个阶段,第一阶段可认为是尝试执行阶段,第二阶段是确认执行阶段。 可以看出这是一个N-to-N的模式,对故障和延迟很敏感,而且存在单点和脑裂问题。
网络出问题的情形有两种:1)分区故障,2)很高的网络延迟,3)远端的节点故障,例如卡住或者crash
2PC无法处理分区故障,那我们来考虑一下除分区故障之外的故障下如何保证一致性的? 首先要明确的是协调者和参与者的所有操作都写入了日志,很多恢复操作都是因为有日志才可行。
- 协调者故障
新选出的协调者,读取旧的日志,就可以正常启动,原来处于任何阶段都是可恢复的。 - 参与者故障
如果参与者处于第一个阶段,那么由于协调者收不到参与者的确认,会陷入阻塞
如果参与者处于第二个阶段,那么它恢复之后,只需要执行和其他参与者一样的操作即可 - 协调者和参与者都故障
当协调者重启之后,收集参与者中的信息,只要有参与者执行了commit或者abort,那么表明第一阶段已经结束,直接要求全部的参与者执行相应的commit或者abort(注意所有的参与者要不执行commit,要不执行abort,不会出现不一致)。最难处理的是,未宕机的所有参与者都处于prepare阶段,而宕机节点不知道处于什么状态。如果宕机节点处于commit或者abort阶段,这种就无法处理了,只能等待宕机节点恢复。
协调者和参与者都故障下的不一致问题:
- 协调者将commit请求下发出去(发向参与者A,B,C),立刻宕机;
- 参与者A收到commit,然后执行commit,随后突然宕机;
- 参与者B和C由于网络或者请求还未完全发出去等各种可能的问题,未收到commit请求;
- 协调者宕机恢复后,发现B和C依然处于第一个阶段,A节点宕机,节点状态未知;
- 这个时候协调者就懵逼了,不知道是应该commit事务,还是abort事务,从而无法保证数据一致。(对这个地方有很大的疑问,还希望有知晓的同学帮忙解答,协调者在发出commit请求之前难道不应该先写入日志吗?有了日志之后协调者重启之后就知道它上次执行的请求是commit,那么直接要求所有节点执行commit不就OK了?重复执行commit应该是没问题的吧?)
2PC协议不能解决分区故障,因为新选出的协调者可能和旧协调者处于不同的网络分区中,从而独立服务。
2.3. 分区容错的共识算法
网络分区的概念已经说过多次,简单说来就是整个网络拆分成了两个部分(或者更多),两个部分各自为战,形成了两个子网络,分别对外提供写服务,导致数据不一致。
解决分区故障的共识算法主要基于多数派具有话语权的思路,只要这个网络中多于一半的节点可正常工作,那么就可对外提供服务,因此节点总数目为奇数。当发生网络分区,具有大半节点的网络对外服务,而另个分区就停止了服务。
2.3.1. Epochs
Paxos和Raft中的正常操作的一个阶段被称为epoch。每个epoch中只有一个节点被指派为leader,且服务到这个epoch窗口结束。

epoch如同一个逻辑锁一样,可用来标记哪些节点是异常或者落后的节点。例如一个故障或者在分区之外的节点加入了进来,它的epoch number落后于本分区内的正常节点,从而它的命令就会被忽略。
2.3.2. 斗争选举
初始状态下,所有的节点在启动之后都是Follower,它和系统中的leader通过心跳保持交互。如果某个Follower发现leader挂掉,那么它会尝试当leader。它首先将自己的状态修改为candicate状态(候选人),并将自己的term号增加1,然后发起proposal,也就是告诉大家我要竞选leader了,你们同意不。
备注:
关于Paxos,我准备另开一个博客,介绍阅读的paxos made simple的一些收获。
除了paxos,还有Zookeeper使用的ZAB,简单版本的共识协议Raft,都会有专门的博客来分析。
3. 汇总
3.1. Primary/Backup
- Single, static master
- Replicated log, slaves are not involved in executing operations
- No bounds on replication delay
- Not partition tolerant
- Manual/ad-hoc failover, not fault tolerant, “hot backup”
3.2. 2PC
- Unanimous (全体一致的) vote: commit or abort
- Static master
- 2PC cannot survive simultaneous failure of the coordinator and a node during a commit
- Not partition tolerant, tail latency sensitive
3.3. Paxos
- Majority vote
- Dynamic master
- Robust to n/2-1 simultaneous failures as part of protocol
- Less sensitive to tail latency
4. Further reading
Primary-backup and 2PC
- [1] Replication techniques for availability - Robbert van Renesse & Rachid Guerraoui 2010
- [2] Concurrency Control and Recovery in Database Systems
Paxos