分布式一致性理论
CAP 理论
一个分布式系统不可能同时满足一致性( C:Consistency ),可用性( A: Availability )和分区容错性( P:Partition tolerance )这三个基本需求,最多只能同时满足其中的 2 个。
如下:
| 选项 | 描述 |
|---|---|
C(Consistence) |
一致性,指数据在多个副本之间能够保持一致的特性(严格的一致性)。 |
A(Availability) |
可用性,指系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据。 |
P(Network partitioning) |
分区容错性,分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障。 |
Base 理论
BASE 是 Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写。
既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)
基本可用
允许出现响应时间损失或者功能损失。
软状态
允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
最终一致性
系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值。
分布式一致性协议
2PC (两阶段提交协议)
在分布式系统中,会有多个机器节点,因此需要一个 “协调者” ,而各个节点就是 “参与者”,协调者统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点就是 “参与者”。
- 协调者拥有超时机制,即如果在一定时间内没有收到 cohort 的消息则默认失败
协调流程
阶段一
阶段一主要是询问参与者是否可以进行提交。
- 事务询问
- 协调者向所有的参与者询问,是否准备好了执行事务,并开始等待各参与者的响应。
- 执行事务
- 各参与者节点执行事务操作,并将 Undo 和 Redo 信息记入事务日志中
- 各参与者向协调者反馈事务询问的响应
- 如果参与者成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行;如果参与者没有成功执行事务,就返回 No 给协调者,表示事务不可以执行。
阶段二
阶段二会根据阶段一的投票结果执行两种操作:执行事务提交,回滚事务。
- 如果所有的节点在阶段一都成功返回了 yes,则执行事务提交
- 如果接收到了一个 no,则执行事务回滚
执行事务提交步骤如下:
- 发送提交请求:协调者向所有参与者发出 commit 请求。
- 事务提交:参与者收到 commit 请求后,会正式执行事务提交操作,并在完成提交之后释放整个事务执行期间占用的事务资源。
- 反馈事务提交结果:参与者在完成事务提交之后,向协调者发送 Ack 信息。
- 协调者接收到所有参与者反馈的 Ack 信息后,完成事务。
中断事务步骤如下:
- 发送回滚请求:协调者向所有参与者发出 Rollback 请求。
- 事务回滚:参与者接收到 Rollback 请求后,会利用其在阶段一种记录的 Undo 信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
- 反馈事务回滚结果:参与者在完成事务回滚之后,想协调者发送 Ack 信息。
- 中断事务:协调者接收到所有参与者反馈的 Ack 信息后,完成事务中断。
优缺点
优点
原理简单,实现方便
缺点
同步阻塞,单点问题,数据不一致,过于保守
- 同步阻塞:
- 在二阶段提交的过程中,所有的节点都在等待其他节点的响应,无法进行其他操作。这种同步阻塞极大的限制了分布式系统的性能。
- 单点问题:
- 协调者在整个二阶段提交过程中很重要,如果协调者在提交阶段出现问题,那么整个流程将无法运转,更重要的是:其他参与者将会处于一直锁定事务资源的状态中,而无法继续完成事务操作。
- 数据不一致:
- 假设当协调者向所有的参与者发送 commti 请求之后,发生了局部网络异常或者是协调者在尚未发送完所有 commit 请求之前自身发生了崩溃,导致最终只有部分参与者收到了 commit 请求。这将导致严重的数据不一致问题。
- 过于保守:
- 如果在二阶段提交的提交询问阶段中,参与者出现故障而导致协调者始终无法获取到所有参与者的响应信息的话,这时协调者只能依靠其自身的超时机制来判断是否需要中断事务,显然,这种策略过于保守。换句话说,二阶段提交协议没有设计较为完善的容错机制,任意一个节点是失败都会导致整个事务的失败。
其他引用
3PC(三阶段提交协议)
三阶段提交协议在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。
协调流程
协调流程如下:

image.png
阶段一: CanCommit
- 事务询问:
- 协调者向所有的参与者发送一个包含事务内容的 canCommit 请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
- 各参与者向协调者反馈事务询问的响应:
- 参与者接收来自协调者的 canCommit 请求,如果参与者认为自己可以顺利执行事务,就返回 Yes,否则反馈 No 响应。
阶段二:preCommit
协调者在得到所有参与者的响应之后,会根据结果执行2种操作:执行事务预提交,或者中断事务。
执行事务预提交分为 3 个步骤:
- 发送预提交请求:协调者向所有参与者节点发出 preCommit 的请求,并进入 prepared 状态。
- 事务预提交:参与者受到 preCommit 请求后,会执行事务操作,对应 2PC 中的 “执行事务”,也会 Undo 和 Redo 信息记录到事务日志中。
- 各参与者向协调者反馈事务执行的结果:如果参与者成功执行了事务,就反馈 Ack 响应,同时等待指令:提交(commit) 或终止(abor)。
中断事务也分为2个步骤:
- 发送中断请求:协调者向所有参与者节点发出 abort 请求 。
- 中断事务:参与者如果收到 abort 请求或者超时了,都会中断事务。
阶段三:doCommit
该阶段做真正的提交,同样也会出现两种情况:
执行提交
- 发送提交请求:进入这一阶段,如果协调者正常工作,并且接收到了所有协调者的 Ack 响应,那么协调者将从 “预提交” 状态变为 “提交” 状态,并向所有的参与者发送 doCommit 请求 。
- 事务提交:参与者收到 doCommit 请求后,会正式执行事务提交操作,并在完成之后释放在整个事务执行期间占用的事务资源。
- 反馈事务提交结果:参与者完成事务提交后,向协调者发送 Ack 消息。
- 完成事务:协调者接收到所有参与者反馈的 Ack 消息后,完成事务。
中断事务
假设有任何参与者反馈了 no 响应,或者超时了,就中断事务。
- 发送中断请求:协调者向所有的参与者节点发送 abort 请求。
- 事务回滚:参与者接收到 abort 请求后,会利用其在二阶段记录的 undo 信息来执行事务回滚操作,并在完成回滚之后释放整个事务执行期间占用的资源。
- 反馈事务回滚结果:参与者在完成事务回滚之后,想协调者发送 Ack 消息。
- 中断事务:协调者接收到所有的 Ack 消息后,中断事务。
note
- 一旦进入阶段三,可能会出现 2 种故障:
- 协调者出现问题
- 协调者和参与者之间的网络故障
- 一段出现了任一一种情况,最终都会导致参与者无法收到 doCommit 请求或者 abort 请求,针对这种情况,参与者都会在等待超时之后,继续进行事务提交
优缺点
优点
- 减少了参与者的阻塞范围(第一个阶段是不阻塞的)
- 能够在单点故障后继续达成一致(2PC 在提交阶段会出现此问题,而 3PC 会根据协调者的状态进行回滚或者提交)
缺点
如果参与者收到了 preCommit 消息后,出现了网络分区,那么参与者等待超时后,都会进行事务的提交,这必然会出现事务不一致的问题
其他引用
paxos 一致性算法
paxos 算法保证了一致性。
在一个分布式系统中,有一组的 process,每个 process 都可以提出一个 value,consensus 算法就是用来从这些 values 里选定一个最终 value。如果没有 value 被提出来,那么就没有 value 被选中;如果有1个 value 被选中,那么所有的 process 都应该被通知到。
在 2PC 或者 3PC 中,如果协调者宕机了,整个系统就宕机了,这个时候就需要引用多个协调者,paxos 就是用来协调协调者的协议。
算法的提出与证明
首先将议员的角色分为 proposers,acceptors,和 learners(允许身兼数职)。proposers 提出提案,提案信息包括提案编号和提议的 value;acceptor 收到提案后可以接受(accept)提案,若提案获得多数派(majority)的 acceptors 的接受,则称该提案被批准(chosen);learners 只能“学习”被批准的提案。划分角色后,就可以更精确的定义问题:
- 决议(
value)只有在被proposers提出后才能被批准(未经批准的决议称为“提案(proposal)”); - 在一次
Paxos算法的执行实例中,只批准(chosen)一个value; learners只能获得被批准(chosen)的value。
通过不断加强上述3个约束(主要是第二个)获得了 Paxos 算法。
批准 value 的过程中,首先 proposers 将 value 发送给 acceptors,之后 acceptors 对 value 进行接受(accept)。为了满足只批准一个 value 的约束,要求经“多数派(majority)”接受的 value成为正式的决议(称为“批准”决议)。这是因为无论是按照人数还是按照权重划分,两组“多数派”至少有一个公共的 acceptor,如果每个 acceptor 只能接受一个 value,约束 2 就能保证。
于是产生了一个显而易见的新约束:
P1:一个
acceptor必须接受(accept)第一次收到的提案。
注意 P1 是不完备的。如果恰好一半 acceptor 接受的提案具有 value A,另一半接受的提案具有 value B,那么就无法形成多数派,无法批准任何一个 value。
约束 2 并不要求只批准一个提案,暗示可能存在多个提案。只要提案的 value 是一样的,批准多个提案不违背约束 2。于是可以产生约束 P2:
P2:一旦一个具有
value v的提案被批准(chosen),那么之后批准(chosen)的提案必须具有value v。
note
- 通过某种方法可以为每个提案分配一个编号,在提案之间建立一个全序关系,所谓“之后”都是指所有编号更大的提案。
如果 P1 和 P2 都能够保证,那么约束 2 就能够保证。
批准一个 value 意味着多个 acceptor 接受(accept)了该 value。因此,可以对 P2 进行加强:
P2a:一旦一个具有
value v的提案被批准(chosen),那么之后任何acceptor再次接受(accept)的提案必须具有value v。(只是接受,还没有经过proposer批准)
由于通信是异步的,P2a 和 P1 会发生冲突。如果一个 value 被批准后,一个 proposer 和一个 acceptor 从休眠中苏醒,前者提出一个具有新的 value 的提案。根据 P1,后者应当接受,根据 P2a,则不应当接受,这种场景下 P2a 和 P1 有矛盾。于是需要换个思路,转而对 proposer 的行为进行约束:
P2b:一旦一个具有
value v的提案被批准(chosen),那么以后任何proposer提出的提案必须具有value v。
由于 acceptor 能接受的提案都必须由 proposer 提出,所以 P2b 蕴涵了 P2a,是一个更强的约束。
但是根据 P2b 难以提出实现手段。因此需要进一步加强 P2b。
假设一个编号为 m 的 value v 已经获得批准(chosen),来看看在什么情况下对任何编号为 n(n > m)的提案都含有 value v。因为 m 已经获得批准(chosen),显然存在一个 acceptors的多数派 C,他们都接受(accept)了 v。考虑到任何多数派都和 C 具有至少一个公共成员,可以找到一个蕴涵 P2b 的约束 P2c:
P2c:如果一个编号为
n的提案具有value v,那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于n
的任何提案,要么他们已经接受(accept)的所有编号小于n的提案中编号最大的那个提案具有value v。
如果一个没有 chose 过任何 proposer 提案的 acceptor 在 prepare 过程中接受了一个 proposer 针对提案 n 的问题,但是在开始对 n 进行投票前,又接受(accept)了编号小于n的另一个提案(例如 n-1),如果 n-1 和 n 具有不同的 value,这个投票就会违背 P2c。因此在 prepare过程中,acceptor 进行的回答同时也应包含承诺:不会再接受(accept)编号小于 n 的提案。这是对 P1 的加强:
P1a:当且仅当
acceptor没有回应过编号大于n的prepare请求时,acceptor接受(accept)编号为n的提案。
算法流程
通过一个决议分为两个阶段:
prepare 阶段
proposer 选择一个提案编号 n 并将 prepare 请求发送给acceptors中的一个多数派;
acceptor 收到 prepare 消息后,如果提案的编号大于它已经回复的所有 prepare 消息(回复消息表示接受 accept),则 acceptor 将自己上次接受的提案回复给 proposer,并承诺不再回复小于 n 的提案;
批准阶段
当一个 proposer 收到了多数 acceptors 对 prepare 的回复后,就进入批准阶段。它要向回复 prepare 请求的 acceptors 发送 accept 请求,包括编号 n 和根据 P2c 决定的 value (如果根据 P2c 没有已经接受的 value,那么它可以自由决定 value)。
在不违背自己向其他 proposer 的承诺的前提下, acceptor 收到 accept 请求后即批准这个请求。
这个过程在任何时候中断都可以保证正确性。例如如果一个 proposer 发现已经有其他 proposers提出了编号更高的提案,则有必要中断这个过程。因此为了优化,在上述 prepare 过程中,如果一个 acceptor 发现存在一个更高编号的提案,则需要通知 proposer,提醒其中断这次提案。
一个实例如下:

image.png
在这之后,提议者还需要做一件事,就是告知D,E,被决定的决议已经是什么了。即可。
这个过程叫 Learn。D,E 被称为 Learner.
paxos 总结
- 在一个决议提议的过程中,其他决议会被否决。如上
E的提议被否决,不会被应用,意味着更多的网络io,意味着更多的冲突。 - 每一个服务器都可以作为提议者或者接收者
- 不保证决议的顺序性
其他引用
zookeeper(zab)
对于 paxos 来说,每一个议案都要经过不同节点的提出,并且讨论,在提出一个议案的阶段,另外的提议会被否决,导致了性能的低下。
ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持 崩溃恢复 和 原子广播 协议。
基于该协议,Zookeeper 实现了一种 主备模式 的系统架构来保持集群中各个副本之间数据一致性。具体如下图所示:

zookeeper
即只有一个 proposal 可以提出提议,其他的进程都只能复制决议。
所有客户端写入数据都是写入到 主进程(称为 Leader)中,然后,由 Leader 复制到备份进程(称为 Follower)中。从而保证数据一致性。
note
- 通过
TCP协议的FIFO特性进行网络通信,能够能容易地把证消息广播过程种消息接收与发送地顺序性。(同时,使用TCP可以感受到commit是否被对方机器接收)
消息广播
ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个 二阶段提交过程。但是只需要 Follower 有一半以上返回 Ack 信息就可以执行提交,大大减小了同步阻塞。也提高了可用性。
对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit操作(先提交自己,再发送 commit 给所有 Follwer)。
流程如下:
Leader接收到消息请求后,将消息赋予一个全局唯一的 64 位自增id,叫做:zxid,通过zxid的大小比较即可实现因果有序这一特性。Leader通过先进先出队列(会给每个follower都创建一个队列,保证发送的顺序性)(通过TCP协议来实现,以此实现了全局有序这一特性)将带有zxid的消息作为一个提案(proposal)分发给所有follower。- 当
follower接收到proposal,先将proposal写到本地事务日志,写事务成功后再向leader回一个ACK。 - 当
leader接收到过半的ACKs后,leader就向所有follower发送COMMIT命令,同意会在本地执行该消息。 - 当
follower收到消息的COMMIT命令时,就会执行该消息
崩溃恢复
Leader 挂了之后,ZAB 协议就自动进入崩溃恢复模式,选举出新的 Leader,并完成数据同步,然后退出崩溃恢复模式进入消息广播模式。
可能 Leader 遇到如下异常情况:
- 假设一个事务在
Leader上提交了,并且过半Follow都响应ACK了,但是Leader将commit消息发出后就挂了 - 假设一个事务在
Leader提交了之后,Leader就挂掉了。
要保证如果发生上述 2 种情况,数据还能保持一致性,那么 ZAB 协议选举算法必须确保已经提交的proposal(发送过commit消息),在Follow上也必须完成提交;并且丢弃已经被跳过的事务proposal。
第一种情况 主要是当 leader 收到合法数量 follower 的 ACKs 后,就向各个 follower 广播 COMMIT命令,同时也会在本地执行 COMMIT 并向连接的客户端返回「成功」。但是如果在各个 follower在收到 COMMIT 命令前 leader 就挂了,导致剩下的服务器并没有执行都这条消息。
为了实现已经被处理的消息不能丢这个目的,Zab 的恢复模式使用了以下的策略:
- 选举拥有
proposal最大值(即zxid最大) 的节点作为新的leader- 由于所有提案被
COMMIT之前必须有合法数量的follower ACK,即必须有合法数量的服务器的事务日志上有该提案的proposal,因此,只要有合法数量的节点正常工作,就必然有一个节点保存了所有被COMMIT消息的proposal状态。
- 由于所有提案被
- 新的
leader将自己事务日志中proposal但未COMMIT的消息处理。 - 新的
leader与follower建立先进先出的队列, 先将自身有而follower没有的proposal发送给follower,再将这些proposal的COMMIT命令发送给follower,以保证所有的follower都保存了所有的proposal、所有的follower都处理了所有的消息。通过以上策略,能保证已经被处理的消息不会丢
第二种情况主要是当 leader 接收到消息请求生成 proposal 后就挂了,其他 follower 并没有收到此 proposal,因此经过恢复模式重新选了 leader 后,这条消息是被跳过的(其他机器日志中没有这一条记录,但是他的日志中有这一条记录)。 此时,之前挂了的 leader 重新启动并注册成了 follower,他保留了被跳过消息的 proposal 状态,与整个系统的状态是不一致的,需要将其删除。
Zab 通过巧妙的设计 zxid 来实现这一目的。一个 zxid 是 64 位,高 32 是纪元(epoch)编号,每经过一次 leader 选举产生一个新的 leader,新 leader 会将 epoch + 1。低 32 位是消息计数器,每接到一个消息,则 $lo^{32} + 1$,新 leader 选举后这个值重置为 0。这样设计的好处是旧的 leader 挂了后重启,它不会被选举为 leader,因为此时它的 zxid 肯定小于当前的新 leader。当旧的 leader 作为 follower 接入新的 leader 后,新的 leader 会让它将所有的拥有旧的 epoch 未被 COMMIT 的 proposal 清除。
note
- 脑裂:1 个集群如果发生了网络故障,很可能出现 1 个集群分成了两部分,而这两个部分都不知道对方是否存活,不知道到底是网络问题还是直接机器 down 了,所以这两部分都要选举 1 个 Leader ,而一旦两部分都选出了 Leader, 并且网络又恢复了,那么就会出现两个 Brain 的情况,整个集群的行为不一致了。
- Zab 使用 Majority Quorums 的方式解决脑裂,一定要超过半数才能选举出来
leader
zab 总结
- Zk 集群中的
client和任意一个节点建立TCP长连接,完成所有的交互动作(虽然所有的交互都是 节点转发到leader) - Zk 中的读请求,直接由连接的节点处理,不需要经过
leader,这种模式可能导致读取到的数据是过时的,但是可以保证一定是半数节点之前确认过的数据。 - Zk有
sync()方法,可以保证读取到最新的数据。
其他引用
Zookeeper系列(5)--ZAB协议,消息广播,崩溃恢复,数据同步
raft 协议
Raft是用于管理复制日志的一致性算法,raft 协议也是一个主备模型,有一个唯一的 leader 控制任务的提交。
如下是一个 raft 协议中每一个节点可能存在的状态,主要分为领袖、群众和候选人。

image.png
raft 最关键的一个概念是任期,每一个 leader 都有自己的任期,必须在任期内发送心跳信息给 follower 来延长自己的任期。
Leader 选举过程
- 所有节点初始状态都是
Follower角色 - 超时时间(每一个节点会自定义一个超时时间)内没有收到
Leader的请求则转换为Candidate进行选举 - 每一个
Candidate首先会投给自己,如果没有一个Candidate得到了过半的票数,则每一个Candidate随机休息一段时间(Election timeout) - 如果某一
Candidate休息结束,则发送信息,如果其他Candidate仍然在休息,就只能投给当前Candidate Candidate收到大多数节点的选票则转换为Leader;发现Leader或者收到更高任期的请求则转换为FollowerLeader在收到更高任期的请求后转换为Follower
数据同步过程
Raft 协议强依赖 Leader 节点的可用性来确保集群数据的一致性。数据的流向只能从 Leader 节点向 Follower 节点转移。当 Client 向集群 Leader 节点提交数据后,Leader 节点接收到的数据处于未提交状态(Uncommitted),接着 Leader 节点会并发向所有 Follower 节点复制数据并等待接收响应,确保至少集群中超过半数节点已接收到数据后再向 Client 确认数据已接收。一旦向 Client 发出数据接收 Ack 响应后,表明此时数据状态进入已提交(Committed),Leader 节点再向 Follower 节点发通知告知该数据状态已提交。
在数据同步阶段,可能出现七种情况:
- 数据到达
Leader节点前,Leader挂掉。- 这个阶段
Leader挂掉不影响一致性。
- 这个阶段
- 数据到达
Leader节点,但未复制到Follower节点,Leader挂掉。- 这个阶段
Leader挂掉,数据属于未提交状态,Client不会收到Ack会认为超时失败可安全发起重试。Follower节点上没有该数据,重新选主后Client重试重新提交可成功。原来的Leader节点恢复后作为Follower加入集群重新从当前任期的新Leader处同步数据,强制保持和Leader数据一致。
- 这个阶段
- 数据到达
Leader节点,成功复制到Follower所有节点,但还未向Leader响应接收,Leader挂掉。- 这个阶段
Leader挂掉,虽然数据在Follower节点处于未提交状态(Uncommitted)但保持一致,重新选出Leader后可完成数据提交,此时Client由于不知到底提交成功没有,可重试提交。针对这种情况Raft要求RPC请求实现幂等性,也就是要实现内部去重机制。(类似于 zab)
- 这个阶段
- 数据到达
Leader节点,成功复制到Follower部分节点,但还未向Leader响应接收,Leader挂掉。- 这个阶段
Leader挂掉,数据在Follower节点处于未提交状态(Uncommitted)且不一致,Raft 协议要求投票只能投给拥有最新数据的节点。所以拥有最新数据的节点会被选为Leader再强制同步数据到Follower,数据不会丢失并最终一致。
- 这个阶段
- 数据到达
Leader节点,成功复制到Follower所有或多数节点,数据在Leader处于已提交状态,但在Follower处于未提交状态,,Leader挂掉。- 这个阶段
Leader挂掉,重新选出新Leader后的处理流程和阶段 3 一样。
- 这个阶段
- 网络分区导致的脑裂情况,出现双
Leader- 原先的
Leader独自在一个区,向它提交数据不可能复制到多数节点所以永远提交不成功。向新的Leader提交数据可以提交成功,网络恢复后旧的Leader发现集群中有更新任期(Term)的新Leader则自动降级为Follower并从新Leader处同步数据达成集群数据一致。
- 原先的
其他引用
nwr 协议
NWR 是一种在分布式存储系统中用于控制一致性级别的一种策略。在 Amazon 的 Dynamo 云存储系统中,就应用 NWR 来控制一致性。
让我们先来看看这三个字母的含义:
- N:在分布式存储系统中,有多少份备份数据
- W:代表一次成功的更新操作要求至少有
w份数据写入成功 - R: 代表一次成功的读数据操作要求至少有R份数据成功读取
NWR值的不同组合会产生不同的一致性效果,当W+R>N的时候,整个系统对于客户端来讲能保证强一致性。当W+R以常见的N=3、W=2、R=2为例:N=3,表示,任何一个对象都必须有三个副本(Replica),W=2表示,对数据的修改操作(Write)只需要在 3 个Replica中的 2 个上面完成就返回,R=2表示,从三个对象中要读取到2个数据对象,才能返回。
在分布式系统中,数据的单点是不允许存在的。即线上正常存在的 Replica数量是 1 的情况是非常危险的,因为一旦这个 Replica 再次错误,就 可能发生数据的永久性错误。假如我们把 N 设置成为 2,那么,只要有一个存储节点发生损坏,就会有单点的存在。所以 N 必须大于 2。N约高,系统的维护和整体 成本就越高。工业界通常把 N 设置为3。
当 W 是 2、R 是 2 的时候,W+R>N,这种情况对于客户端就是强一致性的。
在具体实现系统时,仅仅依靠 NWR 协议还不能完成一致性保证,因为在上述过程中,当读取到多个备份数据时,需要判断哪些数据是最新的,如何判断数据的新旧?这需要向量时钟来配合,所以对于 Dynamo 来说,是通过NWR协议结合向量时钟来共同完成一致性保证的。