概述
分布式系统中各类型数据的一致性要求不尽相同,而Quorum NWR(Node Write Read)一致性模型则提供一种在强一致性与最终一致性之间进行动态变化的思路。
NWR模型,巧妙利用三个参数N(备份数)、W(写入成功数)、R(读取成功数)之间的关系,从而可以实现不同的一致性要求:
- N:副本数,又被称作复制因子,表示同一份数据在整个集群中的副本个数。与集群中全部节点数量并无相等关系,如下图是一个3节点的集群,可见数据A的副本数为3,数据B的副本数为2。

- W:写操作需要确保成功的最小副本个数,表示写一致性级别(Write Consistency Level);
- R:读操作需要确保成功的最小副本个数,表示读一致性级别(Read Consistency Level)。
N、W、R三个参数的不同组合,可实现强一致性、最终一致性:
- 强一致性:数据在任何时刻始终保持一致性;
- 最终一致性:允许某段时间内数据不一致。但是随着时间的增长,数据最终会到达一致的状态,也叫弱一致性。
Quorum
提到一致性,离不开一个概念:Quorum。
Quorum是一种分布式系统一致性机制,用于在多个节点之间协调数据操作。核心思想:在N个节点中,读写操作必须满足一定的节点确认数,以实现数据一致性。通常定义为: W + R > N W+R>N W+R>N,通过该条件,可确保至少有一个节点同时参与读和写操作,避免完全冲突。
上面的说法太官方,简单理解,Quorum表示大多数。
具体案例分析:
- 4个节点的集群:总节点数 N = 4 N=4 N=4, Q u o r u m = ⌈ 4 / 2 ⌉ + 1 = 3 Quorum=⌈4/2⌉+1=3 Quorum=⌈4/2⌉+1=3
- 5个节点的集群:总节点数 N = 5 N=5 N=5, Q u o r u m = ⌈ 5 / 2 ⌉ + 1 = 3 Quorum=⌈5/2⌉+1=3 Quorum=⌈5/2⌉+1=3
在上述两种情况下,Quorum的值都是3。
熟悉ZooKeeper的朋友们,搭建过ZK分布式集群,不管是伪分布式还是完全分布式,都知道集群节点数应该是奇数,最少得为3。
既然Quorum相同,为什么要多增加一个节点资源,多占用一个系统资源?
原因分析:
- 故障容忍性提升:提高可用性
- 4个节点:允许1个节点故障(因为至少需要3个节点完成操作)
- 5个节点:允许2个节点故障(仍然可以通过3个节点完成操作)
- 一致性保障:增加节点数量,提升副本冗余度,降低数据丢失风险
- 性能的权衡:增加节点后,每个操作涉及的节点负载分布更均匀,可能改善读写性能。
结论:虽然4节点和5节点的Quorum值都等于3,但增加节点提供更高的故障容忍性、数据安全性和系统弹性。从分布式系统设计的角度来看,这些优势通常能够弥补资源消耗的增加。对于关键系统,容忍更多故障比单纯减少资源使用更加重要。
Quorum和NWR
两者的关系
- 实现方式:
- Quorum是一个通用概念,强调通过多数同意来协调操作,提供理论基础;
- NWR是基于Quorum的具体实现,用于分布式存储场景;提供更具体的参数控制,可通过N、W、R的调整动态适配不同场景。
- 公式一致性:
Quorum和NWR都遵循 W + R > N W+R>N W+R>N原则,确保至少有一个节点覆盖最新数据的读写; - 适用范围:
- Quorum更广泛,适用于任何需要协调多个节点的分布式系统(如分布式事务);
- NWR特指分布式存储系统的配置参数。
一致性
具体来说,NWR模型提供三种级别的一致性:
- One: W + R < N , W = 1 W+R<N,W=1 W+R<N,W=1。只要任何一个节点写入成功,就立即返回成功给客户端。这种模式对一致性的要求较低,但性能较好;
- Quorum: W + R > N , N > 2 W+R>N,N>2 W+R>N,N>2。要求大多数节点写入成功后,才返回成功给客户端。它保证了较高的数据一致性,但可能牺牲了一定的性能;
- All: W = N W=N W=N。所有节点都写入成功后,才返回成功。这是最高级别的一致性保证,但性能可能受到影响。
为了保证强一致性,必须满足条件: W + R > N W+R>N W+R>N。这个不等式确保在执行读操作时,至少有一个最新的备份被读取到,从而避免读到过时(不一致)的数据。由于读写操作覆盖到的副本集肯定会有交集,读操作只要比较副本集数据的修改时间或版本号即可选出最新的。如 ( N , W , R ) = ( 1 , 1 , 1 ) (N,W,R)=(1,1,1) (N,W,R)=(1,1,1)为单机系统,是强一致性的。
当 W + R < = N W+R<=N W+R<=N时是弱一致性的,如 ( N , W , R ) = ( 2 , 1 , 1 ) (N,W,R)=(2,1,1) (N,W,R)=(2,1,1)为常见的Master-Slave模式,如Redis的Master-Slave模式,更新成功一个节点即返回,其他节点异步去备份数据。
当 W = N , R = 1 W=N,R=1 W=N,R=1时,即所谓的WARO(Write All Read One),就是CAP理论中CP模型的场景。NWR模型有效解决AP模型下不同业务场景对自定义一致性级别的需求。
此外,N越大,数据可靠性越好,但是由于W或R越大,读写开销越大,性能越差,所以一般需要总和考虑一致性,可用性和读写性能,设置W,R都为N/2+1。
其实,折衷方案和异步更新的方式从本质上来说是一样的,都是损失一定的C来换取A的提高。而且,会产生脑裂问题:即网络分区时节点各自处理请求,无法同步数据,当网络恢复时,导致不一致。
NWR模型的优点:
- 实现简单;
- 无需等待所有节点都写入成功,即可判定数据更新成功;保证可读取到最新数据,提升系统吞吐量,系统可用性大有提升。
局限性:需要设计合理的参数配置,否则可能导致一致性问题或性能瓶颈。
适用场景:
- 高吞吐、低延迟的分布式存储场景;
- 可容忍最终一致性的业务,如缓存系统。
示例分析
NWR模型, W + R > N W+R>N W+R>N,所以 R > N − W R>N-W R>N−W,解读:读取的份数一定要比总备份数减去确保写成功的备份数的差值要大。即,每次读取都至少读取到一个最新的版本,从而不会读到一份旧数据。
当需要高可写的环境时,可配置 W = 1 , N = 3 , R = 3 W=1,N=3,R=3 W=1,N=3,R=3。此时,只要写任何节点成功就认为成功,但是读取时必须从所有的节点都读出数据。
如果要求读的高效率,可配置 W = N , R = 1 W=N,R=1 W=N,R=1。此时,任何一个节点读成功就认为成功,但所有节点必须写成功,才认为成功。
NWR模型的一些设置会造成脏数据问题,可能每次的读写操作都不在同一个节点上,于是会出现一些节点上的数据并不是最新版本,但却进行最新的操作。
NWR和CAP
| 特性 | CAP定理 | NWR模型 |
|---|---|---|
| 定义 | 描述分布式系统中一致性、可用性、分区容忍性的权衡 | 基于Dynamo-Style系统的一致性模型,通过N、W、R参数定义行为。 |
| 目标 | 为分布式系统提供一个理论框架,帮助理解系统的设计权衡 | 提供具体的操作模型,用于配置和实现分布式存储的读写行为。 |
| 核心概念 | 一致性(Consistency,C)、可用性(Availability,A)、分区容忍性(Partition Tolerance,P) | 副本数N、写确认数W、读确认数R |
| 适用范围 | 分布式系统的总体设计理论 | 分布式存储 |
| 着眼点 | 理论性,强调设计过程中不可避免的权衡 | 工程性,关注通过参数调整实现特定的强或弱一致性 |
| 一致性 | 选择强一致性(CP)还是最终一致性(AP)由系统设计决定 | 配置W和R的值,灵活实现强一致性或最终一致性。 |
| 应用场景 | 高层次设计决策,如数据库类型选择 | 数据存储操作的具体实现,如副本读写策略。 |
区别
- 抽象层次不同:
- CAP是理论模型,描述分布式系统在一致性、可用性和分区容忍性之间的平衡;
- NWR是实际操作模型,通过调整参数实现存储系统的具体行为。
- 目标不同:
- CAP帮助理解设计权衡,提供系统总体方向的理论指导;
- NWR关注操作层面的实现细节,提供灵活的参数配置。
- 场景不同:
- CAP更适用于高层次设计决策,如选择数据库架构;
- NWR更适用于具体系统的参数调整和优化。
应用
Amazon Dynamo
Amazon Dynamo使用NWR模型,把CAP的选择权交给用户,让用户选择CAP中的哪两个:
- AP:写多读少,优化写性能,可配置 W = 1 W=1 W=1(写完一个副本就成功,其他副本就异步去慢慢复制都可以),如果 N = 3 N=3 N=3,根据公式 W + R > N W+R>N W+R>N,则 R = 3 R=3 R=3(读取数据时需要读3个副本,以判断数据是否有冲突)。只要写任何节点成功就认为成功,但是读必须从所有的节点都读出数据。
- CP:读多写少,优化读性能,可配置 W = N W=N W=N(写完所有副本才认为成功,只能同步复制),根据公式 W + R > N W+R>N W+R>N,则 R = 1 R=1 R=1(只需读一个副本即可)。这种情况任何一个节点读成功就认为成功,但是写的时候必须写所有三个节点成功才认为成功。
- AC:平衡读写性能,没有分区容错概念,也就是不存在集群,单机器情况,没有容错和扩展性。可配置 N = 1 N=1 N=1(只有一份数据),根据公式 W + R > N W+R>N W+R>N,则 W = 1 , R = 1 W=1,R=1 W=1,R=1。
数据版本
多个线程会同时更新一份数据,在这种情况下怎么保证一致性?
Amazon Dynamo引入数据版本概念,如果用户A读出来的数据的版本是v1,当用户A计算完成后要更新数据时,却发现数据的版本号已经被更新成v2,服务器就会拒绝更新。此时需要用户A自己处理冲突,方法为:重新读取v2数据,然后重新计算,重新更新。
数据版本,类似于多版本并发控制,即乐观锁。
向量时钟
还有更糟糕的时候,用户A更新一个副本,在A的更新还没有异步复制到其他副本之前,如果用户B也更新其他副本,数据就处于不一致的状态。
问题:如何发现不同节点上面的副本不一致?
Dynamo引入向量时钟(Vector Clock,也有翻译为矢量钟)概念,让每个节点各自记录自己的版本信息。具体来说,对于同一个数据,需记录两个信息:
- 更新数据的节点名称
- 版本号
案例分析,有A、B和C三个节点, W = 1 , R = N = 3 W=1,R=N=3 W=1,R=N=3:
- (节点A写了两次)一个写请求,第一次被A处理,A会增加一个版本信息
(A,1)。把这个时候的数据记做D1(A,1)。然后另外一个对同样key的请求还是被A处理,于是有D2(A,2)。此时,D2是可以覆盖D1的,不会有冲突产生; - (节点A的数据成功异步复制到B和C,A、B、C处于一致)现在假设D2传播到所有节点(B和C),B和C收到的数据不是从客户产生的,而是由A异步复制(为了让所有副本保持一致)而来,所以B和C不产生新的版本信息,因此现在B和C所持有的数据还是
D2(A,2)。A,B,C上的数据及其版本号都是一样的; - (B写了一次)如果有一个新的写请求到B上,于是B生成数据
D3(A,2;B,1),意思是:数据D全局版本号为3,A更新2次,B更新1次; - (C写了一次:B的改变还没异步复制到C之前C就写完了)如果D3还没有异步复制到C时,C又处理一个新的写请求,于是C上的数据是
D4(A,2;C,1); - 好,问题来了:如果这时来了一个读请求,因为
W
=
1
,
R
=
N
=
3
W=1,R=N=3
W=1,R=N=3,所以R需要从所有三个节点(A、B、C)上读,此时会读到三个版本:
A:D2(A,2)
B:D3(A,2;B,1)
C:D4(A,2;C,1) - 此时可判断出,D2已经是旧版本(已经包含在D3/D4中),可舍弃;
- 但D3和D4是明显的版本冲突,只能交给用户自己去做版本冲突管理。
Cassandra
Cassandra中的折衷型方案Quorum,只要超过半数的节点更新成功便返回,读取时返回多副本的一致的值。对于不一致的副本,可通过Read Repair方式解决。
Read Repair:读取某条数据时,查询所有副本中的这条数据,比较数据与大多数副本的最新数据是否一致,若否,则进行一致性修复。
Riak
一款用Erlang编写的分布式高可用键值对数据库。
524

被折叠的 条评论
为什么被折叠?



