当前位置: 首页 > news >正文

Zookeeper:分布式协调服务

一、概念

ZooKeeper 是一个分布式的、开源的分布式应用程序协调服务,为分布式应用提供一致性、配置管理、命名服务、分布式同步和组服务等。可以把它想象成一个为分布式系统提供的“文件系统”+“通知机制”,但它存储的不是普通的文件,而是少量的、关键的数据(如配置信息、状态标志、任务分配等),并且提供了一套保证,使得分布式应用可以基于它实现数据发布/订阅、负载均衡、命名服务、分布式锁、分布式队列、 leader 选举等一系列核心功能。

1.1 核心能力

核心能力总结:Zookeeper 的核心是通过其强一致性的数据模型和高效的监听机制,在分布式系统中可靠地管理“状态”和“元信息”

  • 分布式数据一致性(核心之核心)

    • 机制:所有客户端连接到Zookeeper集群的任何节点,看到的数据视图都是一致的。写入的数据会通过ZAB协议同步到集群中超过半数的节点后,才被认为成功,从而保证强一致性(顺序一致性)。

    • 体现:这是实现后续所有高级功能的基础。

  • 配置管理

    • 场景:将系统的通用配置(如数据库连接串、特性开关等)存储在Zookeeper的一个znode上。

    • 优势:所有服务实例都可以监听这个znode。当配置变更时,Zookeeper会主动通知所有监听的服务,服务可以实时获取最新配置,无需重启。

  • 分布式锁

    • 场景:在分布式环境中,保证对共享资源的互斥访问,避免并发问题。

    • 机制:利用Zookeeper创建临时顺序节点的特性来实现公平的、可重入的分布式锁。这是其最经典的应用之一(Apache Curator客户端提供了开箱即用的锁实现)。

  • 集群管理与选主

    • 场景:对于一个主从架构的服务集群(如Hadoop HBase),需要确定一个主节点来负责调度和管理。

    • 机制:多个候选主节点同时尝试在指定路径下创建同一个临时节点/election/master)。由于Zookeeper保证节点唯一性,只有一个能创建成功,它就成为Master。其他节点监听这个临时节点,一旦Master宕机导致会话失效节点被删除,其他节点就能立即被通知并重新发起选举。

  • 服务发现与命名服务

    • 场景:在微服务架构中,服务实例动态变化(扩容、宕机),客户端需要知道当前有哪些健康的服务实例可用。

    • 机制:服务提供者启动时,在Zookeeper的特定路径下(如/services/serviceA)创建一个临时节点(如/services/serviceA/instance1:8080)来注册自己。客户端通过监听这个路径的子节点变化,就能动态获取最新的服务实例列表。

1.2 核心特性

ZooKeeper 的性能和可靠性建立在几个核心特性之上,通常简写为 ZAB

  1. 顺序一致性(Ordered Consistency)来自客户端的更新将按其被发送的顺序依次应用。这是最重要的保证。例如,如果客户端先更新了 /a,然后又更新了 /b,那么所有客户端看到的顺序一定是先 /a 后 /b,绝不会相反。

  2. 原子性(Atomicity):更新操作要么成功,要么失败,没有中间状态。所有服务器的数据副本要么都更新,要么都不更新。

  3. 单一系统映像(Single System Image):无论客户端连接到 ZooKeeper 集群中的哪一个服务器,它看到的数据模型都是一致的。不会出现连接到 ServerA 和 ServerB 看到的数据不同。

  4. 可靠性(Reliability):一旦一个更新被应用,其结果就会持久化,直到被下一次更新覆盖。

  5. 及时性(Timeliness):客户端在一定时间范围内(通常很小)能看到最新的系统状态视图。

这些特性是由 ZooKeeper Atomic Broadcast (ZAB) 协议 来保障的,它是一种类似 Paxos 的共识算法,用于在集群中各服务器间复制状态、达成一致。

1.3 原理支撑

1.3.1 数据模型:ZNode

理解类似于文件系统的树形结构,每个节点叫ZNode。ZNode不仅可以是路径,还可以存储少量数据(默认 < 1MB)。

  • 如何支撑特性

    • 层级结构:允许用路径的方式组织数据(如/services/serviceA/instance1),非常直观,适合做配置管理和服务发现。

    • 节点类型

      • 持久节点(PERSISTENT):创建后,除非主动删除,否则一直存在。

      • 临时节点(EPHEMERAL)与客户端会话绑定。当创建它的客户端会话失效(断开连接或超时)时,该节点会被自动删除。这是实现服务发现和领导者选举的关键

      • 顺序节点(SEQUENTIAL):在创建节点时,ZooKeeper 会自动在路径末尾附加一个单调递增的序列号。例如,创建 /app/task- 会得到 /app/task-0000000001这是实现分布式锁和公平队列的关键

      • 这些类型可以组合,例如 临时顺序节点(EPHEMERAL_SEQUENTIAL)

    • 版本号:每个ZNode都有一个版本号,更新操作(如setDatadelete)必须提供正确的版本号才能成功。这实现了乐观锁机制,保证原子性。

1.3.2 会话机制

客户端与Zookeeper服务器建立TCP连接后,会创建一个会话。会话有超时时间,客户端通过定期发送心跳(Ping)来保持会话有效。

  • 如何支撑特性

    • 连接无关性:客户端可以断开重连,只要在会话超时时间内重新连上(可以是连到集群中任意服务器),会话依然有效,之前创建的临时节点也依然存在。这提供了很好的容错能力。会话是有状态的,包含超时时间等配置。

    • 临时节点生命周期:会话失效是临时节点被清除的唯一原因,从而可靠地反映了客户端的存活状态,支撑了可靠性时效性

1.3.3 Watch 机制

一种一次性的、异步的通知机制。客户端可以在读取ZNode时设置一个Watch,监控该节点的变化(数据变更、子节点增减等)。当变化发生时,Zookeeper服务端会向客户端发送一个通知,但只通知一次,之后Watch便失效,如需持续监听需要重新设置。

  • 如何支撑特性

    • 服务发现与配置变更:客户端无需轮询,通过Watch可以近乎实时地感知到服务列表或配置的变化,支撑了时效性单一系统映像

    • 分布式锁/选主:等待锁的客户端可以通过Watch监听前一个序号节点的删除事件,从而被唤醒去尝试获取锁,避免了轮询带来的开销。

1.3.4 ZAB 协议 - 核心中的核心

这是Zookeeper实现强一致性的原子弹级协议,是Paxos算法的一种工业实现变种。ZAB协议的核心是为所有改变Zookeeper状态的操作(写请求)建立一个全局的、有序的顺序

ZAB协议有两个核心工作模式:

  • 消息广播(正常状态)

    1. 所有写请求都由一个领导者服务器接收。

    2. 领导者为该事务分配一个全局单调递增的ZXID(事务ID),保证了顺序性

    3. 领导者生成一个提案,并将这个提案连同ZXID一起广播给所有追随者

    4. 追随者接收到提案后,将其写入磁盘日志,并返回一个ACK给领导者。

    5. 领导者收到超过半数的ACK后,就提交这个事务,并向所有追随者发送一个提交消息。

    6. 追随者收到提交消息后,才在内存中正式应用这个事务。

    这个过程保证了:一个提案只有在被集群多数派持久化后,才会被提交。这支撑了原子性可靠性

  • 崩溃恢复(选举Leader)
    当Leader宕机或失去与多数派的连接时,ZAB协议进入崩溃恢复模式,重新选举Leader。

    1. 选举:集群中的服务器开始投票,投票标准是:优先比较ZXID(谁的数据最新),ZXID相同则比较服务器ID(myid)。得票超过半数的服务器成为新的Leader。

    2. 数据同步:新的Leader会与所有Follower进行数据同步,确保所有Follower都拥有Leader上最新已提交的数据(即高ZXID的数据),同时会丢弃那些未被多数派确认的提案(即使它们来自旧Leader)。

    3. 消息广播:同步完成后,集群重新回到消息广播模式。

    崩溃恢复模式确保了:

    • 已经被旧Leader提交的提案,一定会被新Leader接受(因为提交意味着已被多数派持久化)。

    • 被旧Leader提出但未提交的提案,会被丢弃(因为未被多数派确认)。
      这严格保证了数据的一致性,是单一系统映像可靠性的根本保障。

1.3.5 数据存储与内存数据库

  • 事务日志(WAL)

    • 所有改变 ZooKeeper 状态的操作(写请求)都会被序列化并追加到事务日志文件中。

    • 写日志是顺序写磁盘,效率非常高。这是 ZooKeeper 即使写数据需要持久化也能保持较好性能的关键。

    • 当日志文件过大时,ZooKeeper 会进行快照和日志裁剪。

  • 快照(Snapshot)

    • 定期(默认每 10w 次事务)将内存中的整个数据树(DataTree)序列化后 dump 到磁盘,形成一个快照文件。

    • 快照用于加快重启后的恢复速度。恢复时,先加载快照文件,再重放快照之后的所有事务日志,即可恢复到最新状态。

  • 内存数据库

    • ZooKeeper 的所有数据(DataTree)都完整地存储在内存中,这使得它的读速度极快

    • 事务日志和快照只是用于持久化和恢复,服务时的所有读请求都是直接读内存

1.4 集群架构与角色

ZooKeeper 通常以集群模式部署,以实现高可用。

  • Leader:集群中唯一的一个领导者。所有写请求都必须由 Leader 处理。Leader 接收写请求,并通过 ZAB 协议广播给所有 Follower。

  • Follower:处理客户端的读请求,并将写请求转发给 Leader。参与 Leader 选举和提案投票。Follower 的本地数据是 Leader 的副本。

  • Observer:与 Follower 类似,只处理读请求和转发写请求,但不参与投票。它的存在是为了扩展系统的读性能,而不影响写操作的吞吐量(因为投票过程可能成为瓶颈)。

选举过程:当 Leader 宕机时,剩余的服务器会开始新一轮选举,投票选出新的 Leader。在此期间,ZooKeeper 集群将无法处理写请求,但读请求仍然可以正常处理(因为 Follower 有本地副本)。

防止脑裂:脑裂是指一个集群中出现了多个 Leader,导致数据不一致。ZAB 协议通过两次多数投票机制彻底避免了脑裂:

  1. 写操作需要过半确认:Leader 必须收到过半 Follower 的 ACK 才能提交提案。这意味着任何一条已提交的数据都存在于过半的服务器上。

  2. 选举需要过半投票:新的 Leader 必须获得过半服务器的投票才能当选。

二、典型应用场景

ZooKeeper 的 API 很简单(createdeleteexistsgetDatasetDatagetChildren),但通过巧妙使用节点类型和 Watch 机制,可以实现强大的功能:

2.1 配置管理(Configuration Management)

  • 将通用配置(如数据库连接字符串)存储在 ZNode(如 /app/config)中。

  • 所有应用实例启动时读取该配置,并在这个 ZNode 上设置一个 Watch。

  • 当配置需要变更时,管理员更新 /app/config 的数据。

  • ZooKeeper 会通知所有监听了该节点的应用实例,实例收到通知后重新读取新配置。实现了“一次变更,处处生效”

2.2 服务发现与注册(Service Discovery & Registration)

  • 每个服务实例启动时,在特定的路径下(如 /services/order-service)为自己创建一个临时节点(如 /services/order-service/192.168.1.100:8080)。

  • 服务实例下线或宕机(会话结束)时,其创建的临时节点会被自动删除。

  • 服务消费者通过监听 /services/order-service 的子节点变化,就能实时获取所有健康可用的服务实例列表。

2.3 分布式锁(Distributed Lock)

  • 非公平锁:所有客户端尝试创建同一个锁节点(如 /lock),只有一个客户端能创建成功(ZooKeeper保证唯一性),创建成功的客户端即获得锁。释放锁时删除该节点。

  • 公平锁(推荐)

    1. 所有客户端在锁目录下(如 /locks)创建临时顺序节点(如 /locks/lock- -> /locks/lock-000000001)。

    2. 客户端获取 /locks 下的所有子节点,并判断自己创建的节点是否是序号最小的。

    3. 如果是,则获得锁。

    4. 如果不是,则监听比自己序号小1的那个节点的变化(删除事件)。

    5. 当监听到前一个节点被删除(锁被释放)时,自己再尝试获取锁。这形成了一个等待队列,保证了获取锁的公平性。

2.4 领导者选举(Leader Election)

  • 与分布式锁的实现非常相似。

  • 多个候选者都在指定路径下(如 /election)创建临时顺序节点

  • 序号最小的节点成为 Leader。

  • 其他候选者监听排在它前面的节点。如果 Leader 宕机(其临时节点被删除),下一个序号最小的候选者将成为新的 Leader,并收到通知。

2.5 命名服务(Naming Service) & 分布式队列(Queue)

  • 利用顺序节点的单调递增特性,可以生成全局唯一的 ID(命名服务)。

  • 同样,可以利用顺序节点来实现一个先进先出(FIFO)的队列。

三、基础操作

3.1 集群搭建

  • ZooKeeper 旨在以集群模式(复制模式) 运行,通常包含奇数个服务器节点(如 3, 5, 7...)。这是为了满足多数投票机制,防止脑裂。

  • 每个服务器节点都需要一个唯一的配置文件 zoo.cfg,其中关键配置包括:

    • clientPort: 客户端连接的端口(通常 2181)。

    • dataDir: 存储内存快照和事务日志的目录。

    • server.X=host:port1:port2: 集群中所有服务器的列表。

      • X 是每个服务器的 myid(必须在 dataDir 下的 myid 文件中明确写出)。

      • port1: 用于 Follower 与 Leader 之间的数据同步和通信的端口。

      • port2: 用于 Leader 选举的端口。

  • 示例:一个三节点的集群配置:

# 基本时间单元(毫秒)
tickTime=2000
# 初始化连接时最长心跳间隔(tickTime的倍数)
initLimit=10
# 运行时同步操作最长延迟(tickTime的倍数)
syncLimit=5
# 数据存储目录,很重要,需自定义且确保有权限
dataDir=/your/path/to/zookeeper/data
# (可选)事务日志目录,可与dataDir不同以提升性能
dataLogDir=/your/path/to/zookeeper/log
# 客户端连接端口
clientPort=2181
# 集群服务器列表,格式为 server.myid=host:quorum_port:leader_election_port
server.1=node1_ip:2888:3888
server.2=node2_ip:2888:3888
server.3=node3_ip:2888:3888
# 其他可选参数,如限制单个IP连接数、开启四字命令等:cite[2]
maxClientCnxns=100
4lw.commands.whitelist=*

在每个节点的 dataDir 目录下,创建一个名为 myid 的文件。文件内容只有一行,即该节点在 zoo.cfg 中 server.x 的对应数字标识(如 server.1 对应的节点 myid 文件内容就是 1)。

# 在 node1 上执行
echo "1" > /your/path/to/zookeeper/data/myid
# 在 node2 上执行
echo "2" > /your/path/to/zookeeper/data/myid
# 在 node3 上执行
echo "3" > /your/path/to/zookeeper/data/myid

基本命令:

# 进入 ZooKeeper 的 bin 目录
cd /path/to/zookeeper/bin
# 启动服务
./zkServer.sh start# 在所有节点上执行 status 命令检查状态 leader或follower
./zkServer.sh status# 使用客户端连接任意节点进行测试
./zkCli.sh -server your_zookeeper_ip:2181# 在连接成功后,可以尝试执行一些基本操作,如 create /test "data" 和 get /test,观察数据是否一致。

3.2 集群运维

ZooKeeper 提供了一系列简单的基于 Telnet 或 NC 的命令来监控集群状态,非常有用。

  • echo stat | nc localhost 2181: 查看客户端连接、节点模式(Leader/Follower)等详细信息。

  • echo ruok | nc localhost 2181: 测试服务器是否处于非错误状态(“I'm OK”)。

  • echo conf | nc localhost 2181: 输出完整的服务配置。

  • echo srvr | nc localhost 2181: 输出服务器的状态信息(比 stat 更简洁)。

  • echo cons | nc localhost 2181: 列出所有连接到该服务器的客户端连接详情。

3.3 客户端编程

开发者通常使用 ZooKeeper 提供的官方客户端库(Java/C等)或更高级的封装库(如Apache Curator)来交互。

3.3.1 原生 ZooKeeper API

核心操作非常简洁,围绕 ZNode 进行 CRUD 和监听。

  • create(path, data, acl, nodeType): 创建节点。

  • delete(path, version): 删除节点(可指定版本实现乐观锁)。

  • exists(path, watch): 判断节点是否存在,并可设置 Watch。

  • getData(path, watch, stat): 获取节点数据和元信息(如版本号),并可设置 Watch。

  • setData(path, data, version): 设置节点数据(可指定版本实现乐观锁)。

  • getChildren(path, watch): 获取子节点列表,并可设置 Watch。

// 1. 创建连接
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, new Watcher() {@Overridepublic void process(WatchedEvent event) {// 处理连接状态和Watch事件System.out.println("收到事件:" + event);}
});// 2. 创建节点(持久节点)
String path = zk.create("/myapp/config", "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);// 3. 获取数据并监听变化
byte[] data = zk.getData("/myapp/config", new Watcher() {@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.NodeDataChanged) {System.out.println("节点数据被修改了!");// 通常这里会重新获取数据并再次设置Watch}}
}, null); // stat 信息会放在这个null的位置
System.out.println("数据是: " + new String(data));// 4. 获取子节点
List<String> children = zk.getChildren("/myapp", true);// 5. 判断节点是否存在
Stat stat = zk.exists("/some/node", true);
if (stat != null) {// 节点存在
}// 6. 更新数据
zk.setData("/myapp/config", "newData".getBytes(), -1); // -1表示匹配任何版本// 7. 删除节点
zk.delete("/myapp/config", -1);

注意:原生API比较底层,需要开发者自己处理连接重试、Watch重复注册等复杂逻辑。

3.3.2 Curator框架(生产级首选)

Netflix开源的Curator封装并简化了Zookeeper的操作,提供了更 fluent 的API和大量高阶解决方案。

// 1. 创建客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);
client.start();// 2. 创建节点
client.create().forPath("/myapp/config", "data".getBytes());
// 创建临时节点
client.create().withMode(CreateMode.EPHEMERAL).forPath("/instance/id_01", "host:port".getBytes());// 3. 获取数据
byte[] data = client.getData().forPath("/myapp/config");// 4. 带监听获取数据(Curator的NodeCache工具,可以持续监听节点变化)
NodeCache nodeCache = new NodeCache(client, "/myapp/config");
nodeCache.start(true);
nodeCache.getListenable().addListener(() -> {byte[] newData = nodeCache.getCurrentData().getData();System.out.println("节点数据变化,新数据: " + new String(newData));
});// 5. 使用InterProcessMutex实现分布式锁
InterProcessMutex lock = new InterProcessMutex(client, "/locks/my_lock");
if (lock.acquire(10, TimeUnit.SECONDS)) { // 获取锁,最多等10秒try {// 你的业务临界区代码System.out.println("Doing work inside the lock!");} finally {lock.release(); // 必须释放锁}
}

文章转载自:

http://WiuGSkFv.trrrm.cn
http://JVye0CVH.trrrm.cn
http://u83sSpNb.trrrm.cn
http://hqm5b5xT.trrrm.cn
http://T1wXr50d.trrrm.cn
http://Bgw3SMMy.trrrm.cn
http://PsEA19Wl.trrrm.cn
http://WX2c8RPH.trrrm.cn
http://3xID3PKB.trrrm.cn
http://BOT2aAd8.trrrm.cn
http://47L1QeWH.trrrm.cn
http://ycHL0VY2.trrrm.cn
http://9XOb5Reg.trrrm.cn
http://OxFTG90i.trrrm.cn
http://lWy01Plg.trrrm.cn
http://ZPsFL6z9.trrrm.cn
http://8s8W13e8.trrrm.cn
http://I3pnrIup.trrrm.cn
http://60p7zxwg.trrrm.cn
http://xGfztAv0.trrrm.cn
http://wxj0g46U.trrrm.cn
http://f8XQd6z5.trrrm.cn
http://Hq0wucz3.trrrm.cn
http://Omgk5Uuz.trrrm.cn
http://Mb0iRTdC.trrrm.cn
http://NKRUMWgx.trrrm.cn
http://wVXq6Dh4.trrrm.cn
http://0dcuICK0.trrrm.cn
http://fVg32GUJ.trrrm.cn
http://aFQOpOlt.trrrm.cn
http://www.dtcms.com/a/381440.html

相关文章:

  • 在 R 语言里,`$` 只有一个作用 按名字提取“列表型”对象里的单个元素 对象 $ 名字
  • 【pure-admin】项目登录模块分析
  • 关于Redis不同序列化压缩性能的对比
  • window显示驱动开发—VidPN 对象和接口
  • 系统架构设计师——【2024年上半年案例题】真题模拟与解析(二)
  • 突破性能瓶颈:基于腾讯云EdgeOne的AI图片生成器全球加速实践
  • JavaScript事件机制与性能优化:防抖 / 节流 / 事件委托 / Passive Event Listeners 全解析
  • 文章目录集合
  • 海外短剧系统开发:技术架构与性能优化实践
  • Windsurf 插件正式登陆 JetBrains IDE:让 AI 直接在你的 IDE 里“打工”
  • 西门子 S7-200 SMART PLC 核心指令详解:从移位、上升沿和比较指令到流水灯控制程序实战
  • 【重要通知】ChatGPT Plus将于9月16日调整全球充值定价,低价区将被弃用,开发者如何应对?
  • 跨省跨国监控难题破解:多层级运维的“中国解法”
  • Spring Boot 与 Elasticsearch 集成踩坑指南:索引映射、批量写入与查询性能
  • 基础算法---【高精度算法】
  • React 18的createRoot与render全面对比
  • 在 React 中如何优化状态的使用?
  • 什么是半导体制造中的PVD涂层?
  • 半导体制造的光刻工艺该如何选择合适的光刻胶?
  • 用图论来解决问题
  • 机器视觉在半导体制造中有哪些检测应用
  • 从废料到碳减排:猎板 PCB 埋容埋阻的绿色制造革命,如何实现环保与性能双赢
  • CoCo:智谱推出的企业级超级助手Agent
  • 【高等数学】第十一章 曲线积分与曲面积分——第七节 斯托克斯公式 环流量与旋度
  • 嵌入式基础_STM32F103C8T6移植FreeRTOS(标准库函数)
  • 互联网大厂Java面试实录:从基础到微服务全栈技术答疑
  • DAY 28 类的定义和方法-2025.9.15
  • Linux信号小细节整理
  • Django全栈班v1.04 Python基础语法 20250913 下午
  • 第38次CCFCSP第三题--消息解码