ZooKeeper 的一致性模型

ZooKeeper 在分布式系统中采用了一种混合一致性模型,具体表现如下:

事务操作(写操作)
ZooKeeper 通过 ZAB(ZooKeeper Atomic Broadcast)协议保证写操作的线性一致性(Linearizability)。所有写请求由 Leader 按全局顺序广播,并需获得多数节点的确认后才能提交。这种设计确保:

  • 操作的执行顺序与请求的全局时间顺序一致
  • 一旦写操作成功,后续所有读操作(包括跨客户端)都能立即看到最新数据

读操作
默认情况下,读操作遵循顺序一致性(Sequential Consistency),但通过附加机制增强了保证:

  • 顺序一致性确保单个客户端的操作顺序与全局顺序一致
  • 单调读一致性(Monotonic Reads)防止客户端读到比之前更旧的数据
  • 会话一致性(Session Consistency)保证同一会话内操作的可见性顺序

关键实现机制

ZAB 协议流程

  1. Leader 将写请求封装为 Proposal 按 FIFO 顺序广播
  2. Followers 按接收顺序持久化 Proposal 并返回 ACK
  3. Leader 收到多数 ACK 后发送 Commit
  4. Followers 按顺序提交 Proposal 到状态机

读操作优化

  • 客户端可设置 sync 标志强制从 Leader 读取(类似线性化读)
  • 内置的 zxid(事务ID)机制实现单调读一致性
  • 会话期间自动跟踪最后可见的 zxid

性能与一致性的权衡

ZooKeeper 的设计体现了典型的一致性-延迟-容错三角权衡:

  • 强一致性写:牺牲部分写入延迟(需多数节点确认)
  • 可调一致性读:默认顺序一致性换取更高吞吐,必要时可升级为线性化读
  • 最终落地点:满足 CP 系统要求,适合协调服务场景

这种分层一致性模型使其既能作为分布式锁等强一致性原语的基础,又能高效处理配置管理等高频读取场景。概述
在分布式系统中,我们熟知多种一致性模型,包括强一致性、弱一致性、最终一致性、线性一致性、顺序一致性、因果一致性、会话一致性和单调读一致性等。那么,ZooKeeper究竟遵循哪种一致性模型 呢?

事务消息传递流程
在探讨 ZooKeeper 的一致性模型之前,我们先来回顾一下 ZooKeeper 的事务处理流程。为了更直观地理解这一过程,我们参考了 ZooKeeper 官方文档中的插图和描述。

所有通信通道都是先进先出(FIFO ),所以所有操作都是按顺序进行的。具体来说,遵循以下操作约束:

领导者使用相同的顺序向所有跟随者发送提议。此外,这个顺序遵循请求接收的顺序。由于我们使用 FIFO 通道,这意味着跟随者也会按顺序接收提议。
跟随者按接收顺序处理消息。这意味着消息将按顺序确认(ACK),并且由于 FIFO 通道,领导者将按顺序从跟随者接收确认。这也意味着如果消息 m 已写入非易失性存储,那么所有在 m 之前提议的消息也都已写入非易失性存储。
领导者会在多数从节点确认一条消息后,向所有从节点发出 COMMIT。由于消息是按顺序确认的,领导者会按从节点接收的顺序发送 COMMIT。
COMMIT 按顺序处理。从节点在 COMMIT 该提议时,会传递提议消息。
总结如下:当 Leader 收到客户端的请求后,会将该请求封装为一个提议(proposal),并将其发送给所有 Follower。Follower 按照接收到提议的顺序将其存储到本地日志中,并向 Leader 发送ACK消息。当 Leader 收到超过半数 Follower 的ACK后,它会向所有 Follower 发送一个 commit 指令,指示它们可以提交该提议。Follower 接收到 commit 指令后,按照提议的顺序依次执行,从而确保所有副本保持一致。

ZooKeeper是强一致吗?
让我们来看看官网中是如何描述的ZooKeeper一致性保证

从上述分析中可以明确,ZooKeeper 的写操作遵循线性一致性,而读操作则遵循顺序一致性。那么,这两种一致性模型的具体含义是什么呢?

线性一致性(Linearizability):线性一致性是强一致性的一种形式。它要求系统的行为如同所有操作都在一个单一的、连续的时间线上顺序执行。具体而言,对于任意两个操作,系统必须确保它们的执行顺序与它们在时间上的先后顺序完全一致。这意味着,从外部观察者的角度来看,系统的行为就像是所有操作都在某个特定的时间点瞬间完成的,而这些时间点的顺序与操作的实际发起顺序相同。
顺序一致性(Sequential Consistency):顺序一致性要求系统中的操作按照每个节点(或进程)上的发生顺序执行。对于每个节点,操作的执行顺序必须与其在全局时间顺序中的顺序一致。然而,它并不要求不同节点的操作在物理时间上严格遵循先后顺序。换句话说,允许一个节点在本地时间上先读取到旧值,只要整个操作历史对所有节点呈现出一个一致的逻辑顺序即可。顺序一致性是一种介于强一致性和最终一致性之间的中间状态,它比强一致性稍弱,但比大多数最终一致性模型更强。
总之,顺序一致性在保证单个进程内部操作顺序与全局顺序一致的同时,允许一定程度的灵活性,而线性一致性则提供了更强的时间顺序保证,确保操作的执行顺序与它们在时间上的先后顺序完全一致。

如下图所示,当 Leader 节点收到多数 Follower 节点的确认(ack)后,会立即执行 commit 操作,并实时向请求方反馈处理成功。然而,在这一瞬间,另一个客户端可能对尚未同步到最新数据的 Follower 节点发起查询。由于查询操作并非事务性操作,Follower 节点可以独立处理。此时,该 Follower 节点可能尚未获取到最新的数据 x,因此会向客户端返回空值。

那么,小明可能会问:ZooKeeper 的机制仅仅如此吗?当然不是,这只是其中的一部分,接下来让我们继续深入探讨。

单一视图保证
确实,在这种读取场景下,ZooKeeper 并未完全实现线性一致性,而是选择了顺序一致性作为其基本保证。然而,ZooKeeper 并未止步于此,它还通过一些额外的机制,使读取操作更接近强一致性。 ZooKeeper单一视图

从上述情况可以了解到,如果客户端曾经连接过一个较新的服务(即具有更大的 zxid),那么它将永远不会尝试连接到较旧的服务,关于这一机制的详细信息,我们可以参考相关的资料,例如 Git 上的相应 Pull Request,以及源码中的具体实现:
git相应PR:ZOOKEEPER-3373:需要更改文档中"单系统映像"保证的描述 #931
源码引用:org.apache.zookeeper.server.ZooKeeperServer#processConnectRequest

结语
回顾我们最初的问题:ZooKeeper 是否实现了强一致性?现在,我们可以给出明确的答案:

此时我想我们可以给出答案了:

事务操作:ZooKeeper 保证了线性一致性,这等同于强一致性。
读操作:虽然 ZooKeeper 主要保证顺序一致性,但它在此基础上进一步补充了单一视图(单调读一致性)和会话一致性。
这些设计选择都是经过精心权衡的结果(trade-off),旨在在性能和一致性之间找到最佳平衡。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐