先记住这个答案
W+R>N 保证任意一次读的 R 个节点与最近一次成功写的 W 个节点至少重叠一个。设写操作已在 W 个节点提交,这 W 个节点存有最新版本号 v。读操作访问 R 个节点,若读集合与写集合无重合,则总节点数至少 W+R,超过 N,故必有交叠。客户端收集所有响应,比较版本号并回传最大值,就能读到包含版本 v 的数据。该机制依赖单调递增的版本或逻辑时钟,并要求单写者避免并发乱序。它提供单键的已提交读,但不涉及多对象事务,也不能替代 Paxos 类共识来广播写入顺序。
- 读写集合交集非空,是基本数学保证
- 读响应后取最大版本号得到最新值
- 需单写者且不保证多对象事务
相交的集合论证明
设系统有 N 个副本。一次写成功后,新值至少存在于 W 个节点;随后一次读若只覆盖 R 个节点,读与写的节点集合如果完全分离,则系统至少需要 W+R 个不同节点。当 W+R>N 时,这种分离不可能发生,所以读集合与写集合必有一个共同节点。交集最小个数为 W+R-N。
为了从该重叠节点取得最新值,每个副本需要保留更新版本号(如 Lamport 时间戳或单调计数器)。读方收集所有响应后,舍弃低版本,只返回最高版本对应的数据。因为重叠节点必然保存了本次或更后写入的版本,所以返回值至少不旧于刚才的写。
五副本集群中的读写交集
假设一个 KV 存储集群有 N=5 个副本,设置 W=3,R=3。控制器负责对 key 执行写操作,每次写带自增版本号。当写值 v2 成功写入副本 a、b、c 后返回客户端;随后一个读请求恰好落在 c、d、e 上。由于 c 在写集合中,它存有 v2,客户端把 c、d、e 的版本号排序后得到 v2,正确返回新值。
若此时另一个节点 c 短暂不可用,读只能访问 d、e,不满足 R=3,常规做法是等待副本恢复或放弃请求,不会退回读旧值,否则会破坏 W+R>N 的语义。灵活上下文中可采用 W=2,R=4 以增加重叠,但要以更低的读可用性为代价。
失效模式与弱一致边界
即使 W+R>N,若写操作在确认成功前实际上只写入了少于 W 个节点(例如请求超时未响),则新值可能未满足分布要求。此时读方可能碰上写集合与读集合不相交的情况,读到旧值。因此“读最新”的前提是写方确实收到了 W 个确认,而不是客户端猜测成功。
更关键的边界是并发写:若两个写者同时推进不同版本且不共享总序,系统无法用单一时间戳判断哪个最新,NWR 只能返回冲突列表供应用层合并。它弱于 Raft 或 Paxos——后者通过多数派投票赋予每一条写以全局序列,从而保证读取结果的确定性;NWR 不提供该序列,对多对象事务更是无从谈起。
容易答错的地方
- W+R>N就保证强一致读
- 纠正:只有当写已被确认成功时,后续读才能保证拿到该版本。若写处于未确认的中间态,或并发写未排序,读可能看到旧值或冲突。还需读方取最大版本号,且确保只有单一序列生成版本。实际上这只提供已提交的单键可见性,而非完整线性一致。
- NWR和多数派一样
- 多数派是 W>N/2 且 R>N/2 的特殊情形,可保证任意两次操作必交叠。但 NWR 并不要求每个节点都交换状态,它依赖外部时钟分配顺序。若同时发生两个写,没有集中共识,结果可能分裂,应用需另外解决冲突。因此不能把 NWR 当作共识协议。
面试官还会怎么问?
为什么有时会选择 W=1,R=N?
如果只关心写可用性且不允许丢失,可设 W=1 让写即时成功,R=N 使读总能碰到写节点。但这样单个节点故障就不再有读副本可用,可用性被压低;而且写只落一台容易在故障后丢失数据。一般 W 和 R 需根据读写比例与故障容忍定制。
NWR 能保证线性一致性吗?
不能直接。要获得线性一致性,还需每次读都要访问包含最新提交的副本,并确认没有并发写。NWR 只保证读写集合有交集,但要确定哪个是系统最终顺序中的最新值,必须依赖全局唯一写者或带租约的锁。否则并发写会造成歧义,所以它本身达不到线性化的原子性。
如果 N=3,W=1,R=3,满足 W+R>N,可以保证读最新吗?
在单写者且写已确认的前提下可以,因为 R=3 读取全部副本,必有一份新值。但 W=1 意味着数据只写在单个节点,若该节点故障且未复制,系统整体会丢数据,因此这种配置常搭配异步复制或补偿机制。可靠性由其他手段保证,NWR 本身不提供。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。