第七章 ZooKeeper 技术内幕(三)
7.7 各服务器角色介绍
在一个 ZooKeeper 集群(Ensemble)中,服务器节点通常扮演三种角色:Leader(领导者)、Follower(跟随者)和 Observer(观察者)。一个正常工作的集群在任意时刻只会有一个 Leader,其余的都是 Follower 或 Observer。
7.7.1 Leader (领导者)
Leader 是整个 ZooKeeper 集群的核心和大脑,它负责协调和管理整个集群。其主要职责如下:
-
处理所有写请求(事务性请求):
- 任何会改变 ZooKeeper 数据状态的操作,如
create,setData,delete等,都属于写请求。 - 无论客户端连接到哪个服务器(Follower 或 Observer),所有的写请求都会被转发给 Leader 进行统一处理。
- 这种集中处理的方式是保证数据一致性的基础。
- 任何会改变 ZooKeeper 数据状态的操作,如
-
发起并同步事务:
- Leader 接收到写请求后,会将其转换为一个事务,并为这个事务分配一个全局唯一的、单调递增的
zxid。 - 然后,Leader 会通过 Zab (ZooKeeper Atomic Broadcast) 协议将这个事务广播给所有的 Follower。
- Leader 会等待,直到收到超过半数(Quorum)的 Follower 对该事务的
ACK(确认)响应。 - 一旦收到过半数的
ACK,Leader 就会提交 (commit) 这个事务,并向所有 Follower 发送COMMIT消息,同时也会将该事务应用到自己的内存数据树中。 - 最后,Leader 会向最初发起请求的客户端发送成功响应。
- Leader 接收到写请求后,会将其转换为一个事务,并为这个事务分配一个全局唯一的、单调递增的
-
管理和协调 Follower:
- Leader 负责与所有 Follower 保持心跳(通过 PING 消息),监控它们的存活状态。
- 在数据同步阶段,Leader 负责指导 Follower 如何与自己对齐数据(通过 DIFF, TRUNC, 或 SNAP 方式)。
-
处理非事务性请求: Leader 自身也能处理读请求(
get,exists等),就像一个 Follower 一样。
总结:Leader 是集群的“写操作”瓶颈所在,但也是一致性的唯一保证者。它的存在简化了分布式系统中的一致性问题,将复杂的“多点写入”问题转换为了一个简单的“单点写入、多点复制”模型。
7.7.2 Follower (跟随者)
Follower 是 Leader 的“追随者”,是构成 ZooKeeper 集群高可用性的基石。它们是集群中的“大多数”。
其主要职责如下:
-
处理读请求(非事务性请求):
- Follower 可以独立、快速地处理客户端的读请求。它会直接从自己的本地内存数据树中返回结果。
- 这使得 ZooKeeper 集群可以通过增加 Follower 节点来水平扩展读性能。
-
转发写请求给 Leader:
- 当 Follower 收到一个写请求时,它不会自己处理,而是简单地将其转发给 Leader。
-
参与事务投票:
- 这是 Follower 最重要的职责之一。当收到 Leader 广播来的事务提议(
PROPOSAL)时,它会:
a. 以事务日志(WAL, Write-Ahead Log)的形式将该事务记录到本地磁盘。
b. 成功写入磁盘后,向 Leader 发送一个ACK响应。 - 这个“先写日志,再发 ACK”的机制确保了即使 Follower 在提交事务前宕机,重启后也能通过日志恢复该事务,不会丢失数据。
- 这是 Follower 最重要的职责之一。当收到 Leader 广播来的事务提议(
-
执行 Leader 的 COMMIT 命令:
- 当收到 Leader 的
COMMIT消息后,Follower 会将对应的事务应用到自己的内存数据树中,使其数据状态与 Leader 保持同步。
- 当收到 Leader 的
-
参与 Leader 选举:
- 如果 Leader 宕机,所有的 Follower 会立即进入
LOOKING状态,并参与新一轮的 Leader 选举,以确保集群能够快速恢复服务。
- 如果 Leader 宕机,所有的 Follower 会立即进入
总结:Follower 通过处理读请求分担了 Leader 的压力,并通过参与投票和选举保证了集群的高可用性和数据一致性。
7.7.3 Observer (观察者)
Observer 是在 ZooKeeper 3.3.0 版本中引入的一种特殊角色。它像 Follower 一样从 Leader 同步数据,但有一个关键区别:它不参与任何形式的投票。
其主要职责和特点如下:
-
处理读请求: 与 Follower 完全相同,可以独立处理读请求,用于扩展集群的读性能。
-
转发写请求: 与 Follower 完全相同,将写请求转发给 Leader。
-
同步数据,但不投票:
- Observer 会接收 Leader 广播的事务提议(
PROPOSAL)和提交命令(COMMIT),并像 Follower 一样更新自己的数据。 - 但是,它不会向 Leader 发送
ACK。Leader 不需要等待 Observer 的确认。
- Observer 会接收 Leader 广播的事务提议(
-
不参与 Leader 选举: Observer 不会发起选举,也不会对选举提议进行投票。它只是一个被动的学习者。
为什么需要 Observer?
引入 Observer 的主要目的是在不影响集群写性能和选举效率的前提下,进一步提升集群的读性能和可扩展性。
- 提升写性能: 在一个标准的 Leader-Follower 集群中,每增加一个 Follower,Leader 在提交事务时就需要多等待一个节点的网络通信和磁盘写入。当 Follower 数量非常多时,这会显著增加写操作的延迟。而增加 Observer 则完全没有这个副作用,因为 Leader 不需要等待它们的 ACK。
- 提升选举效率: 选举过程需要集群中所有参与者进行网络通信。节点越多,选举收敛的速度可能越慢。Observer 不参与选举,因此不会增加选举的复杂性。
- 跨数据中心部署: Observer 特别适用于跨数据中心(WAN)的场景。由于跨地域网络延迟高且不稳定,如果将 Follower 部署在远程数据中心,会导致整个集群的写性能急剧下降。而部署 Observer 则可以在远程数据中心提供低延迟的读服务,同时又不会拖慢主集群的写性能。
总结:Observer 是一种“只读”的服务器角色,是提升 ZooKeeper 集群在超大规模或跨地域场景下读扩展能力的利器。
7.7.4 集群间消息通信
为了完成选举、同步、心跳等各种复杂的协调任务,ZooKeeper 服务器之间需要进行高效的通信。其通信方式主要基于 TCP 长连接和 NIO 技术。
-
建立连接:
- 当集群启动,Follower(或 Observer)确定了 Leader 之后,它会主动与 Leader 建立一个 TCP 长连接。
- 这个连接一旦建立,就会在整个运行期间保持,用于后续所有的消息传递。
-
消息队列 (Message Queue):
- 每个服务器内部都有专门的发送队列和接收线程。
- 当 Leader 需要向某个 Follower 发送消息时,它会将消息放入对应 Follower 的发送队列中。一个专门的
LearnerHandler线程会负责从队列中取出消息,并通过 TCP 连接发送出去。 - 这种异步队列的设计可以有效解耦业务处理和网络 I/O,提高吞吐量。
-
消息类型:
- 服务器间的消息被封装成不同的类型,以应对不同的场景。主要包括:
- 选举消息: 在
LOOKING状态下,用于广播和交换投票。通常使用 UDP,因为它更快且允许广播,但新版也可以配置为 TCP。 - PROPOSAL: Leader 向 Follower 提议一个新事务。
- ACK: Follower 对 PROPOSAL 的确认。
- COMMIT: Leader 通知 Follower 提交事务。
- PING: Leader 向 Follower 发送的心跳包,用于检测存活。
- REVALIDATE: Follower 重连 Leader 时,用于验证会话是否有效。
- SYNC: 用于数据同步阶段的消息。
- 选举消息: 在
- 服务器间的消息被封装成不同的类型,以应对不同的场景。主要包括:
-
序列化与反序列化:
- 为了在网络中传输,所有的消息对象都需要被序列化成字节流。ZooKeeper 使用了自家的序列化框架 Jute。
- Jute 能够将 Java/C++ 对象高效地序列化为二进制格式,相比 Java 原生序列化更紧凑、性能更高。
通过这套精心设计的角色分工和高效的通信机制,ZooKeeper 集群得以在保证强一致性的同时,提供了良好的性能、高可用性和优秀的扩展能力。
7.8 请求处理 (Request Processing)
ZooKeeper 的请求可以分为两大类:事务性请求 (Transactional Request) 和 非事务性请求 (Non-transactional Request)。
- 事务性请求:会改变服务器数据状态的请求,如
create,delete,setData。这类请求必须由 Leader 统一处理,并通过 Zab 协议在集群中达成一致。 - 非事务性请求:不会改变数据状态的只读请求,如
getData,getChildren,exists。这类请求可以由任何一台服务器(Leader, Follower, Observer)独立处理。
现在,我们来分别看几个典型请求的处理流程。
7.8.1 会话创建处理 (Session Creation)
会话(Session)是 ZooKeeper 中一个至关重要的概念。客户端与服务器之间的所有操作都绑定在一个会话之上。会话创建本身是一个特殊的事务性请求。
处理流程:
-
客户端发起连接:
- 客户端(
ZooKeeper类)启动时,会尝试连接到其连接串(connectString)中指定的某一台服务器的客户端端口(如 2181)。
- 客户端(
-
服务器接收连接:
- 服务器端的
NIOServerCnxnFactory接收到这个新的 TCP 连接,并创建一个NIOServerCnxn对象来专门处理这个连接。
- 服务器端的
-
创建会话 (ConnectRequest):
- 客户端连接成功后,会立即向服务器发送一个
ConnectRequest,请求创建一个新会话。这个请求中包含了客户端希望的会话超时时间(sessionTimeout)等信息。
- 客户端连接成功后,会立即向服务器发送一个
-
服务器处理会话创建:
NIOServerCnxn收到ConnectRequest后,会将其交给ZooKeeperServer处理。ZooKeeperServer会调用其内部的SessionTracker组件来创建一个会话。SessionTracker是一个会话管理器。- 生成 Session ID:
SessionTracker会生成一个全局唯一的 64 位sessionID。 - 协商 Timeout: 服务器会根据自己的配置(
minSessionTimeout,maxSessionTimeout)与客户端期望的sessionTimeout进行协商,确定一个最终的超时时间。 - 设置初始密码: 为会话生成一个密码(
password),用于后续客户端重连时验证身份。
-
作为事务提交:
- 会话的创建会改变服务器的状态(在
SessionTracker中注册了一个新会话),因此它必须作为事务处理。 - 服务器将 "创建会话" 这个事件包装成一个事务,并提交给请求处理链。
- 这个事务会经过 Zab 协议的广播、投票和提交过程,最终被持久化到所有服务器的事务日志中。
- 会话的创建会改变服务器的状态(在
-
响应客户端:
- 一旦会话创建事务被成功提交,Leader 会通知处理该连接的服务器。
- 该服务器会向客户端发送一个
ConnectResponse,其中包含了最终协商好的sessionID、sessionTimeout和password。
-
会话建立完成:
- 客户端收到
ConnectResponse后,会话正式建立。客户端状态变为CONNECTED,并开始定期向服务器发送心跳(PING 请求)来维持会话。
- 客户端收到
关键点:会话创建是一个需要集群达成共识的事务性操作,因为它需要在整个集群中注册这个会话,以便客户端在连接到任何一台服务器时都能被识别。
7.8.2 SetData 请求处理
setData 是一个典型的事务性请求,我们以此为例,看看一个写操作的完整生命周期。
处理流程:
-
客户端提交请求: 客户端调用
zk.setData("/path", data, version),将请求打包发送给当前连接的服务器。 -
服务器接收并预处理:
NIOServerCnxn接收到请求。- 如果当前服务器是 Follower 或 Observer,它会执行 请求转发(详见 7.8.3),将请求原封不动地发给 Leader。
- 如果当前服务器就是 Leader,它将开始处理这个请求。
-
Leader 处理请求 (请求处理链):
- Leader 将请求交给一个被称为“请求处理链”(Request Processor Chain)的组件进行处理。这是一个责任链模式的实现,请求会依次通过多个处理器。
PrepRequestProcessor: 这是第一站。它会对请求进行解析和预处理,例如:- 验证权限(ACL)。
- 检查版本号(
version)是否匹配,实现乐观锁。 - 将请求转化为一个事务(
SetDataTxn),并生成事务头(TxnHeader),包含cxid,zxid,time等信息。
ProposalRequestProcessor: 这是发起 Zab 协议广播的处理器。它会将PrepRequestProcessor生成的事务包装成一个PROPOSAL,广播给所有的 Follower。SyncRequestProcessor: 将事务以日志形式写入本地磁盘(WAL)。写盘成功后,它会向下一个处理器传递请求。AckRequestProcessor: (仅在 Leader 上) 当SyncRequestProcessor写盘成功后,这个处理器会向 Leader 自己发送一个ACK,表示 Leader 本身已经完成了事务的持久化。FinalRequestProcessor: 当一个事务被 Leader 确认已提交(收到过半数 ACK)后,FinalRequestProcessor会负责将该事务应用到内存数据库(DataTree)中,即真正在内存中修改节点的数据。同时,它还会负责唤醒等待该请求结果的客户端连接。
-
Follower 参与处理:
- Follower 收到 Leader 的
PROPOSAL后,也会将请求传递给自己的处理链。 - Follower 的处理链相对简单,核心是
SyncRequestProcessor,它负责将事务写入本地磁盘,并向 Leader 发送ACK。 - 当收到 Leader 的
COMMIT消息后,Follower 的FinalRequestProcessor才会将事务应用到自己的内存数据树中。
- Follower 收到 Leader 的
-
返回响应:
- Leader 的
FinalRequestProcessor在内存应用成功后,会生成响应,并将其交由NIOServerCnxn发回给最初接收请求的服务器,最终由该服务器返回给客户端。
- Leader 的
7.8.3 事务请求转发 (Forwarding)
这是保证所有写操作都由 Leader 统一处理的关键机制。
- 触发条件: 当一个 Follower 或 Observer 收到一个事务性请求(如
create,setData)时。 - 转发过程:
- Follower/Observer 的
FollowerRequestProcessor或ObserverRequestProcessor会识别出这是一个事务性请求。 - 它不会在本地处理,而是直接通过与 Leader 建立的长连接,将这个请求消息转发给 Leader。
- Leader 接收到这个被转发的请求后,就如同收到一个直接来自客户端的请求一样,开始走标准的请求处理链流程。
- Follower/Observer 的
- 响应返回: 当 Leader 处理完该请求后,会将最终的响应消息发回给最初转发请求的那个 Follower/Observer,再由它返回给客户端。客户端对于这个转发过程是无感的。
7.8.4 GetData 请求处理
getData 是一个典型的非事务性(只读)请求,它的处理流程要简单得多。
处理流程:
-
客户端提交请求: 客户端调用
zk.getData("/path", watch, stat),将请求发送给当前连接的服务器。 -
服务器处理请求:
NIOServerCnxn接收到请求。- 由于
getData是只读请求,任何服务器(Leader, Follower, Observer)都可以直接处理它,无需转发。 - 请求被直接交给
FinalRequestProcessor。 FinalRequestProcessor会直接访问本地内存中的DataTree,查找对应路径/path的节点。- 如果找到了节点,它会读取节点的数据和元数据(
Stat)。 - 处理 Watch: 如果请求中设置了
watch为true,服务器会将这个 Watcher 注册到DataTree的WatchManager中。这个 Watcher 与客户端的会话和节点路径绑定。 - 生成响应:
FinalRequestProcessor将读取到的数据和Stat信息包装成一个响应。
-
返回响应:
- 响应被
NIOServerCnxn直接发送回客户端。
- 响应被
数据一致性说明:
由于读请求是在各个服务器本地处理的,可能会存在数据延迟。例如,一个事务刚刚在 Leader 上提交,但 COMMIT 消息还在发送给某个 Follower 的路上,此时一个读请求到达了这个 Follower,它读到的就是旧数据。ZooKeeper 默认提供的是 “顺序一致性” (Sequential Consistency),但不保证实时一致性。如果需要读取最新的数据,客户端可以先调用 sync() 方法,该方法会强制客户端连接的服务器与 Leader 进行一次数据同步,然后再执行读操作。
7.9 数据与存储 (Data and Storage)
ZooKeeper 的数据模型在逻辑上是一棵树,但其物理存储和管理则要复杂得多。它采用了一种“内存 + 磁盘”相结合的混合模式,以平衡性能和持久性。
7.9.1 内存数据 (In-Memory Data)
ZooKeeper 的所有实时数据和状态都存储在内存中。这是其能够提供极高读性能的根本原因。
DataTree: 这是 ZooKeeper 内存数据的核心载体。它是一个树形数据结构,完整地对应了客户端视角下的 znode 树。对 znode 的任何get或exists操作,都是直接访问这个内存中的DataTree对象,因此速度非常快。DataNode:DataTree中的每个节点都是一个DataNode对象。它包含了 znode 的路径、数据内容、ACL 列表、元数据(Stat对象,如czxid,mzxid,pzxid,version等)以及子节点列表。WatchManager: 这是一个独立的管理器,也存在于内存中。它负责存储所有的 Watcher。WatchManager维护了从 znode 路径到 Watcher 列表的映射,以及从 Watcher 到路径的反向映射。当DataTree中的某个节点发生变化时,会触发WatchManager去查找并通知相关的客户端。SessionTracker: 会话管理器,同样是纯内存的。它负责管理所有活跃的会-话,包括会话 ID、超时时间、临时节点等。Leader 上的SessionTracker负责会话的创建、销毁和续期。
优点: 极高的读性能,因为所有读操作都是内存访问。
挑战: 内存是易失的。如何保证服务器宕机后数据不丢失?这就引出了磁盘存储机制。
7.9.2 事务日志 (Transaction Log)
事务日志(也称 Write-Ahead Log, WAL)是保证 ZooKeeper 数据持久性和一致性的基石。任何对数据状态的变更操作,都必须先以事务的形式写入日志文件,然后才能应用到内存中。
-
作用:
- 持久化: 确保所有已提交的事务都被永久记录,即使服务器宕机,也可以通过重放日志来恢复内存状态。
- 一致性: Leader 通过将事务日志广播给 Follower,并在收到过半数 ACK 后才提交,保证了集群数据的一致性。
-
文件格式:
- 日志文件位于
dataDir目录下的version-2子目录中。 - 文件名格式为
log.<zxid>,其中zxid是该日志文件中第一条事务记录的 ID。例如,log.100000001。 - 当一个日志文件达到一定大小(默认 64MB)或发生日志切换时,会创建一个新的日志文件。
- 日志文件位于
-
内容:
- 文件由一系列事务记录顺序组成。
- 每个记录包含两部分:事务头(
TxnHeader) 和 事务体(Txn)。TxnHeader: 包含sessionID,cxid(客户端请求ID),zxid(全局事务ID),time(时间戳),type(事务类型,如 create, delete)。Txn: 包含事务的具体内容,如create操作的路径和数据。
- 所有内容都通过 Jute 序列化为二进制格式。
-
刷盘机制:
- 为了性能,ZooKeeper 并不会每写入一条日志就立即
fsync到磁盘。它会先写入操作系统的文件缓存(page cache)。 SyncRequestProcessor负责将缓存中的日志批量、异步地刷到磁盘。这种机制在性能和数据安全性之间取得了很好的平衡。只有当事务日志成功刷盘后,Follower 才会向 Leader 发送 ACK。
- 为了性能,ZooKeeper 并不会每写入一条日志就立即
7.9.3 Snapshot -- 数据快照
随着时间的推移,事务日志会变得越来越大。如果一个服务器宕机后需要重启,重放所有历史日志会是一个极其漫长的过程。为了解决这个问题,ZooKeeper 引入了数据快照 (Snapshot) 机制。
-
作用:
- 加速恢复: 快照是某一时刻全量内存数据 (
DataTree和SessionTracker) 的一个持久化副本。服务器重启时,只需加载最新的快照文件到内存,然后重放该快照之后产生的事务日志即可,大大缩短了启动时间。 - 清理日志: 一旦某个时间点的快照被成功创建,那么这个时间点之前的所有事务日志文件就都可以被安全地清除了。
- 加速恢复: 快照是某一时刻全量内存数据 (
-
触发时机:
- 由配置参数
snapCount控制。默认值为 100,000。 - 每当处理了
snapCount / 2到snapCount之间的一个随机数的事务数量后,就会触发一次快照。例如,默认配置下,大约每处理 5万到10万个事务,就会生成一个新的快照。 - 也可以通过管理命令手动触发。
- 由配置参数
-
文件格式:
- 快照文件也位于
dataDir/version-2/目录下。 - 文件名格式为
snapshot.<zxid>,其中zxid是生成此快照时,服务器已经处理的最后一个事务的 ID。这个zxid代表了快照的数据版本。
- 快照文件也位于
-
过程:
- 触发快照时,ZooKeeper 会开启一个独立的线程。
- 该线程会将当前的
DataTree和SessionTracker数据完整地序列化。 - 序列化后的数据被写入一个新的临时快照文件中。
- 写入成功后,将临时文件重命名为正式的
snapshot.<zxid>文件。 - 快照生成后,会清理掉比这个快照
zxid更旧的快照和日志文件(会保留少数几个以备不时之需)。
7.9.4 初始化 (Initialization)
当一个 ZooKeeper 服务器节点启动时,它需要做的第一件事就是恢复自己的内存状态,以便能加入集群或参与选举。这个过程就是初始化。
-
寻找最新可用数据:
- 服务器会扫描
dataDir/version-2/目录,找到所有snapshot.*和log.*文件。 - 它会找到最新的那个快照文件(即
zxid最大的那个)。如果一个快照都没有,说明是第一次启动,内存为空。
- 服务器会扫描
-
加载快照:
- 服务器读取最新快照文件的内容,反序列化,并在内存中重建
DataTree和SessionTracker。 - 加载后,服务器的内存状态就恢复到了快照生成时的那个时间点。此时,服务器的
lastProcessedZxid就是快照的zxid。
- 服务器读取最新快照文件的内容,反序列化,并在内存中重建
-
重放增量日志:
- 接着,服务器会查找所有
zxid大于快照zxid的事务日志文件。 - 它会按
zxid从小到大的顺序,依次读取这些日志文件中的每一条事务记录,并将其应用到内存的DataTree中(这个过程称为“重放”)。 - 这个过程就像快进一样,把快照之后发生的所有数据变更都重新在内存里过一遍。
- 接着,服务器会查找所有
-
初始化完成:
- 当所有相关的增量日志都被重放完毕后,服务器的内存数据就恢复到了它宕机前的最新状态。
- 此时,服务器就拥有了准确的
zxid,可以正式进入 Leader 选举 流程。
7.9.5 数据同步 (Data Synchronization)
当 Leader 选举产生后,集群并不能立刻对外提供服务。Follower 和 Observer 必须先和新的 Leader 同步数据,确保集群中所有节点的数据视图是一致的。
同步的触发:
- Leader 选举结束后,Follower/Observer 连接 Leader 时。
- 运行期间,Follower/Observer 与 Leader 断开重连时。
同步的模式:
Leader 会根据 Follower/Observer 的数据状态,选择以下四种模式之一进行同步:
-
DIFF模式 (差异化同步):- 场景: Follower 的数据状态与 Leader 差距很小。Follower 的
lastProcessedZxid处于 Leader 内存中还记录的事务提议队列 (committedLog) 范围内。 - 过程: Leader 不需要发送全量数据,只需将 Follower 缺失的那些事务
PROPOSAL逐一发送给它即可。Follower 应用这些事务后,就能赶上 Leader。这是最轻量、最快的同步方式。
- 场景: Follower 的数据状态与 Leader 差距很小。Follower 的
-
TRUNC+DIFF模式 (先回滚,再差异化同步):- 场景: Follower 的
zxid比 Leader 还要新。这通常发生在老的 Leader 网络分区后,自己处理了一些未被集群确认的事务,然后又重新加入集群。这些事务是“脏数据”,必须被清除。 - 过程: Leader 会发送一个
TRUNC命令,要求 Follower 回滚到某个共同认可的zxid。然后,再进行DIFF模式的同步。
- 场景: Follower 的
-
SNAP模式 (快照同步):- 场景: Follower 的数据状态与 Leader 差距太大,或者 Follower 是一个全新的节点。具体来说,Follower 的
lastProcessedZxid比 Leader 事务提议队列的起始zxid还要旧。 - 过程: Leader 会将自己的内存数据(
DataTree和会话信息)以快照的形式,通过网络发送给 Follower。Follower 接收到快照后,会直接加载到自己的内存中,完全覆盖本地数据。这是最耗时、最消耗网络带宽的同步方式,但也是最彻底的。
- 场景: Follower 的数据状态与 Leader 差距太大,或者 Follower 是一个全新的节点。具体来说,Follower 的
-
PROPOSAL模式:- 这其实不是一种独立的同步模式,而是同步完成后的正常状态。一旦 Follower 与 Leader 数据完全对齐,Leader 就会将它加入到正常的“广播列表”中,开始向它发送新的事务
PROPOSAL。
- 这其实不是一种独立的同步模式,而是同步完成后的正常状态。一旦 Follower 与 Leader 数据完全对齐,Leader 就会将它加入到正常的“广播列表”中,开始向它发送新的事务
通过这套完整的数据管理和同步机制,ZooKeeper 确保了即使在节点频繁启停、发生网络故障的情况下,数据也能保持最终一致,并且能快速从故障中恢复服务。

浙公网安备 33010602011771号