系列文章目录
第一章 ZooKeeper入门概述:Znode,Watcher,ZAB .
文章目录
技术解析:基于 ZooKeeper 实现高可用的主-从协调系统
在分布式计算中,实现可靠的进程协调是构建稳定系统的基础。关键问题包括如何选举唯一的领导者、如何动态管理集群成员以及如何进行任务分发。本文将详细阐述如何使用 Apache ZooKeeper 的基本功能,构建一个高可用的主-从(Master-Slave)模式的协调系统。(通过该例子理解Znode和Watcher 如何进行分布式协调)
我们将通过 ZooKeeper 的命令行工具 zkCli
来演示整个过程,以清晰地展示其底层工作原理。
一、 系统设计:角色与数据模型
该系统由三个功能角色和一套定义在 ZooKeeper 中的数据结构组成。
集群配置: 三台服务器分别为host1,host2,host3.
系统角色:
- 主节点 (Master): 系统的控制单元。其功能是监控从节点和任务的状态,并负责将任务分配给当前可用的从节点。
- 从节点 (Slave): 任务的执行单元。它向系统注册自身的存在,并监视等待主节点分配的任务。
- 客户端 (Client): 任务的提交单元。它负责创建新任务,并监视任务的最终完成状态。
ZooKeeper 数据模型 (Znode 结构):
系统的状态和协调逻辑通过 ZooKeeper 的 znode 树来表达。关键路径定义如下:
/master
(临时节点): 用于实现主节点选举。成功创建此节点的进程即为主节点。/workers
(持久节点): 用于从节点注册。其下的每个子节点代表一个处于活动状态的从节点。/tasks
(持久节点): 用于任务提交。客户端在此路径下创建子节点以发布新任务,形成一个任务序列。/assign
(持久节点): 用于任务分配。主节点在此路径下为每个从节点创建专用的子路径,用于存放分配给该从节点的任务信息。
二、 主节点:选举、故障转移与状态监控
主节点的功能实现依赖于 ZooKeeper 提供的原子操作和事件通知机制。
2.1 主节点选举与高可用性
核心机制:利用临时节点的唯一性及其与客户端会话的生命周期绑定。
创建尝试 (Creation Attempt):
所有候选主节点的进程都尝试在/master
路径下创建一个临时节点 (-e
)。# host1 尝试创建 /master 节点 [zk: host1:2181(CONNECTED) 1] create -e /master "master1.example" Created /master
由于 Znode 创建是原子操作,只有一个进程能成功。该进程成为活动主节点 (Active Master)。节点的数据字段可用于存储其网络地址等元信息。
处理创建失败 (Handling Creation Failure):
其他进程在尝试创建时,将收到节点已存在的错误。# 进程host2 尝试创建,操作失败 [zk: host2:2181(CONNECTED) 0] create -e /master "master2.example" Node already exists: /master
收到此错误的进程将进入备份主节点 (Backup Master) 状态。
故障转移机制 (Failover Mechanism):
备份主节点必须能够检测到活动主节点的故障并接管其角色。核心机制:在
/master
节点上设置监视点 (Watch)。# 进程host2 (备份主节点) 对 /master 节点设置监视 [zk: host2:2181(CONNECTED) 1] ls /master true []
ls /master true
命令在/master
节点上注册了一个一次性的监视点。若活动主节点的进程失效或网络连接中断,其 ZooKeeper 会话将超时。由于/master
是一个临时节点,它会随着会อ会话的终止而被自动删除。
我们在当前主节点模拟该过程,在host1上删除/master
此删除操作会触发一个NodeDeleted
事件,该事件会通知所有在该节点上注册了监视点的备份主节点(host2)。
#host1
[zk: host1:2181(CONNECTED) 2] delete /master
#host2
[zk: host2:2181(CONNECTED) 2]
WATCHER::
WatchedEvent state:SyncConnected type:NodeDeleted path:/master
收到通知后,所有备份主节点host2将重新执行步骤1的创建尝试,从而使其中一个成为新的活动主节点。此流程确保了主节点角色的高可用性。此时host2成为了主节点。
[zk: host2:2181(CONNECTED) 2] create -e /master "master2.example"
Created /master
2.2 监控从节点与任务
活动主节点需要监控系统的动态变化,即从节点的可用性和新任务的提交情况。
创建workers 。tasks,和assign
[zk: host2:2181(CONNECTED) 3] create /workers ""
Created /workers
[zk: host2:2181(CONNECTED) 4] create /assign ""
Created /assign
[zk: host2:2181(CONNECTED) 5] create /tasks ""
Created /tasks
[zk: host2:2181(CONNECTED) 6] ls /
[zookeeper, workers, tasks, master, assign]
核心机制:活动住节点监视子节点列表的变化 (NodeChildrenChanged
)。
# 监视 /workers 路径的子节点变化(是否有从节点注册)
[zk: host2:2181(CONNECTED) 7] ls /workers true
[]
# 监视 /tasks 路径的子节点变化(是否有任务待处理)
[zk: host2:2181(CONNECTED) 8] ls /tasks true
[]
通过在 ls
命令中使用 true
参数,主节点可以监视 /workers
和 /tasks
两个路径。
- 当
/workers
下有子节点被创建或删除时,表明有从节点上线或下线,主节点会收到通知。 - 当
/tasks
下有子节点被创建时,表明有新任务提交,主节点同样会收到通知。
三、 从节点:注册与任务接收
从节点负责执行具体任务,它需要向系统声明其可用性,并准备接收任务。
- 注册 (Registration):
从节点启动后,在/workers
路径下创建一个代表自身的临时节点。
(host1 ,host 3注册从节点)[zk: host1:2181(CONNECTED) 1] create -e /workers/worker1.example.com "worker1.example.com:2224" Created /workers/worker1.example.com
主节点host2发现workers有了变化(NodeChildrenChanged ),知道有了从节点上线。由于Watcher的一次性,host2为了可以继续监控,需要重新申请一个workers的监视Watcher
[zk: host2:2181(CONNECTED) 9]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/workers
[zk: host2:2181(CONNECTED) 9] ls /workers true
[worker1.example.com]
Note: 使用临时节点是一种有效的存活性检测机制。只要从节点进程正常,该节点就存在。进程异常终止将导致节点自动删除,主节点可通过监视点立即感知到该从节点的离线。
准备接收任务 (Preparing for Assignment):
每个从节点(例如 host1)在启动时,都会在 /assign 路径下创建一个以自身标识符(如 orker1.example.com)命名的持久性 Znode,例如 /assign/host1。这个 Znode 作为该从节点的专属任务队列。随后,从节点会在此 Znode 上设置一个子节点变化(NodeChildrenChanged)的监视点。当主节点向该路径下添加子节点(即分配新任务)时,该从节点会收到通知并处理任务。使用持久节点确保了即使在从节点短暂离线期间,分配给它的任务信息也不会丢失。# worker1 创建其任务分配路径 [zk: host1:2181(CONNECTED) 2] create /assign/worker1.example.com "" Created /assign/worker1.example.com # 监视该路径,等待任务分配 [zk: host1:2181(CONNECTED) 3] ls /assign/worker1.example.com true []
四、 客户端:任务提交与结果获取
客户端是任务的初始输入源。
任务提交
客户端(用host3模拟)通过在/tasks
路径下创建一个顺序节点 (-s
) 来提交一个新任务。# 客户端提交一个任务,其数据为 "cmd" [zk: host3:2181(CONNECTED) 6] create -s /tasks/task- "cmd" Created /tasks/task-0000000001 #客户端监控任务是否完成 [zk: host3:2181(CONNECTED) 7] ls /tasks/task-0000000001 true []
使用顺序节点,ZooKeeper 会自动为节点名附加一个单调递增的数字后缀。这提供了一个简单且可靠的分布式序列生成功能,可用于任务排序。
活动主节点分配任务处理
#主节点收到任务通知
[zk: host2:2181(CONNECTED) 5]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/tasks
#主节点将任务分配给host1
[zk: host2:2181(CONNECTED) 5] create -e /assign/worker1.example.com/task-0000000001 ""
Created /assign/worker1.example.com/task-0000000001
#主节点监控任务是否完成
[zk: host2:2181(CONNECTED) 6] ls /tasks/task-0000000001 true
[]
- host2处理任务,处理结束,告诉主节点和客户端结束:
#host1收到任务分配通知
[zk: host1:2181(CONNECTED) 4]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/assign/worker1.example.com
#host1完成任务,将任务完成通知写入
[zk: host1:2181(CONNECTED) 4] create /tasks/task-0000000001/status "done"
Created /tasks/task-0000000001/status
#客户端和主节点检测任务状态,如果不是done,则表示任务执行出现错误,可以尝试从新提交等。
[zk: host3:2181(CONNECTED) 5] get /tasks/task-0000000001/status
done
cZxid = 0x100000037
ctime = Sun Sep 07 02:00:09 PDT 2025
mZxid = 0x100000037
mtime = Sun Sep 07 02:00:09 PDT 2025
pZxid = 0x100000037
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
- 主节点和客户端接收通知
# 客户端监视任务节点 /tasks/task-0000000001
[zk: host3:2181(CONNECTED) 8]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/tasks/task-0000000001
# 主节点监视任务节点 /tasks/task-0000000001
[zk: host2:2181(CONNECTED) 7]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/tasks/task-0000000001
五、 完整交互流程
以下是一个任务从创建到完成的完整事件序列:
- 初始化: 主节点
master1
选举成功,开始监视/workers
和/tasks
。 - 从节点注册: 从节点
worker1
启动,创建/workers/worker1.example.com
。主节点收到通知,更新其可用从节点列表。 - 客户端提交任务: 客户端创建顺序节点
/tasks/task-0000000001
。 - 主节点分配任务:
- 主节点收到
/tasks
的变化通知,检测到新任务task-0000000001
。 - 主节点查询
/workers
,选择可用的worker1
。 - 主节点在
worker1
的分配路径下创建任务指派节点:
create /assign/worker1.example.com/task-0000000000 ""
- 主节点收到
- 从节点执行任务:
worker1
收到其分配路径/assign/worker1.example.com
的子节点变化通知。- 它读取到新分配的任务
task-0000000001
,并开始处理。
- 反馈结果:
worker1
完成任务后,在原始任务节点下创建状态子节点:
create /tasks/task-0000000000/status "done"
- 客户端获取结果:
- 客户端对
/tasks/task-0000000000
的监视被触发。 - 客户端和主节点通过
get /tasks/task-0000000000/status
读取到结果"done"
,任务流程结束。
- 客户端对
结论:ZooKeeper 的核心协调机制
此示例展示了如何组合使用 ZooKeeper 的几个核心功能来实现一个复杂的分布式协调模式:
- 临时节点 (Ephemeral Nodes): 用于实现成员存活性检测和资源独占锁定。
- 顺序节点 (Sequential Nodes): 用于实现分布式队列或唯一 ID 生成。
- 监视点 (Watches): 提供了一个高效的、一次性的事件通知机制,是构建事件驱动系统的基础。
通过这些原语,不同角色的进程无需直接网络通信,而是通过改变和监视 ZooKeeper 中的共享状态来协同工作。这种方式降低了系统各组件间的耦合度,并利用 ZooKeeper 的一致性保证来确保整个系统的正确性。