什么是Zookeeper
Zookeeper简称zk,先从字面意思上去理解,那就是动物园管理员。其实zk是大数据领域中的一员,为整个分布式环境提供了协调服务,主要可以用于存储一些配置信息,同时也可以基于zk实现集群。它是一个apache的开源分布式中间件。
举一些zk简单的场景:
-
比如当你使用dubbo的时候,你需要使用zk作为注册中心,当然SpringCloud也可以使用zk来替代eureka作为注册中心。
-
另外kafka和solr都是基于zk的结合实现了集群,当然netty也可以。如果你对上述的一些技术名字没有听过没有接触过,那么没有关系,我们将来都会涉及到,都会带着大家去学习。
-
集群或者分布式系统以及大数据中的一些统一的配置管理信息可以都写入zk便于所有服务器节点监听读写,其实也就是协调作用嘛
-
MQ消费者处理完毕也可以通过zk回写通知生产者处理完成,并且告知结果。
工作机制
zk可以说是一种监工模式。zk中把数据保存到节点(节点后面会说),然后可以监听这个节点,如果节点的数据发生变化,则可以发起通知。我们可以通过下面一张图来描述这个过程。
上面就是zk的工作原理,本质上其实也是生产者与消费者的关系,学生就是生产者,而班主任就是消费者,通过这种关系实现了zk的通知机制。
基本结构
数据模型
ZooKeeper中的数据模型是一种树形结构。
具有一个固定的根节点,我们可以在根节点下创建子节点,并在子节点下继续创建下一级节点。
树中的每一层级用斜杠(/)分隔开,只能用绝对路径(如
get /work/task1
)的方式查询节点,不能使用相对路径。
为什么 ZooKeeper不能采用相对路径查找节点?
ZooKeeper 在底层实现的时候,使用了一个 hashtable,即
hashtableConcurrentHashMap<String, DataNode> nodes
,用节点的完整路径来作为 key 存储节点数据。
节点类型
持久节点:
该数据节点会一直存储在 ZooKeeper 服务器上,即使创建该节点的客户端与服务端的会话关闭了,该节点依然不会被删除。
如果我们想删除持久节点,就要显式调用
delete
函数进行删除操作。
临时节点:
该节点数据不会一直存储在 ZooKeeper 服务器上。
当创建该临时节点的客户端会话因超时或发生异常而关闭时,该节点也相应在 ZooKeeper 服务器上被删除。
可以利用临时节点来做服务器集群内机器运行情况的统计,将集群设置为/servers
节点,并为集群下的每台服务器创建一个临时节点/servers/host
,当服务器下线时该节点自动被删除,最后统计临时节点个数就可以知道集群中的运行情况。
有序节点:
节点有序是说在我们创建有序节点的时候,ZooKeeper 服务器会自动使用一个单调递增的数字作为后缀,追加到我们创建节点的后边。
例如一个客户端创建了一个路径为
works/task-
的有序节点,那么 ZooKeeper 将会生成一个序号并追加到该节点的路径后,最后该节点的路径为works/task-1
。
节点数据
一个二进制数组(
byte data[]
),用来存储节点的数据、ACL 访问控制信息、子节点数据(因为临时节点不允许有子节点,所以其子节点字段为 null),除此之外每个数据节点还有一个记录自身状态信息的字段 stat。
节点状态
每个节点都有属于自己的状态信息,执行
stat /zk_test
,可以看到节点状态信息。
节点版本
每个数据节点有 3 种类型的版本信息,对数据节点的任何更新操作都会引起版本号的变化。
- ZooKeeper 的版本信息表示的是对节点数据内容、子节点信息或者是 ACL 信息的修改次数。
数据存储
内存数据
ZooKeeper的数据模型是存储在内存中的。
- 源码中,通过 DataTree 类来定义的。
数据日志
数据日志是用来记录 ZooKeeper 服务运行状态的数据文件。
通过这个文件能统计 ZooKeeper 服务的运行情况,更可以在 ZooKeeper 服务发生异常的情况下,根据日志文件记录的内容来进行分析,定位问题产生的原因并找到解决异常错误的方法。
事务日志
在 ZooKeeper 服务运行过程中,会不断地接收和处理来自客户端的事务性会话请求,每次在处理事务性请求的时候,都要记录这些信息到事务日志中。
同时集群中,Leader服务器会向 ZooKeeper 集群中的其他角色服务发送数据同步信息,在接收到数据同步信息后,ZooKeeper 集群中的 Follow 和 Observer 服务器就会进行数据同步。
这两种角色服务器所接收到的信息就是 Leader 服务器的事务日志。
在接收到事务日志后,并在本地服务器上执行。
快照文件
在 ZooKeeper 服务运行的过程中,数据快照每间隔一段时间,就会把 ZooKeeper 内存中的数据存储到磁盘中,快照文件是间隔一段时间后对内存数据的备份。
- 与内存数据相比,快照文件的数据具有滞后性。
Watch(监视器)
ZooKeeper 提供了一种强大的事件监听机制------Watch(监视器),允许客户端订阅特定节点的变化事件。当订阅的事件发生时,ZooKeeper 服务端会向相应的客户端发送通知。Watch 事件监听是 ZooKeeper 实现分布式协调、状态同步等核心功能的关键手段之一。
类型与触发条件
ZooKeeper 支持两种类型的 Watch 事件:
-
数据变更 Watch(Data Watches):当客户端对某个 ZNode 设置数据变更 Watch 后,如果该 ZNode 的数据内容发生任何更改(包括创建、更新、删除),ZooKeeper 会向客户端发送一个通知。注意,数据变更 Watch 是一次性触发器,即触发后需要客户端重新设置才能继续监听后续变化。
-
子节点列表变更 Watch(Child Watches):如果客户端对某个 ZNode 设置子节点列表变更 Watch,当该 ZNode 的子节点集发生变化(新增、删除子节点)时,ZooKeeper 会发送通知。同样,子节点列表变更 Watch 也是一次性触发器。
设置 Watch
客户端可以通过以下方式设置 Watch:
-
使用 ZooKeeper API :在使用 ZooKeeper 客户端库(如 Java 的
org.apache.zookeeper.ZooKeeper
类)编写程序时,可以在相应方法中指定是否设置 Watch。例如,getData()
方法可以设置数据变更 Watch,getChildren()
方法可以设置子节点列表变更 Watch。 -
使用 ZooKeeper 命令行工具 :在 ZooKeeper 命令行客户端(如
zkCli.sh
)中,通过在命令中添加watch
参数来设置 Watch。例如,get path watch
会在获取数据的同时设置数据变更 Watch,ls path watch
会在列出子节点的同时设置子节点列表变更 Watch。
事件通知与处理
当触发 Watch 的事件发生时,ZooKeeper 服务端会通过客户端与服务端之间已建立的连接,向客户端发送一个 Watch 事件通知。通知包含以下信息:
-
事件类型 :指明触发的具体事件类型,如
NodeDataChanged
(数据变更)、NodeDeleted
(节点删除)、NodeChildrenChanged
(子节点列表变更)等。 -
节点路径:发生事件的 ZNode 的完整路径。
-
状态码:通常表示事件通知的成功与否,以及可能的错误代码。
客户端在接收到 Watch 事件通知后,通常会调用预定义的回调函数(使用 ZooKeeper API 时)或在命令行中显示通知(使用命令行工具时),以便应用程序及时响应事件并采取相应的行动。
注意事项
-
一次性触发:Watch 是一次性触发器,这意味着一旦触发事件并发送通知后,该 Watch 就会自动移除。若想继续监听同一事件,客户端需在接收到通知后重新设置 Watch。
-
异步通知:Watch 事件通知是异步发送的,不保证立即送达。客户端在设置 Watch 后,不应假设事件会立即触发或通知会立即到达。
-
客户端会话状态:如果客户端会话由于网络问题、超时等原因中断,所有与该会话关联的 Watch 将被移除,客户端需要在重新建立会话后重新设置 Watch。
-
性能考虑:大规模集群中大量使用 Watch 可能会影响 ZooKeeper 服务端的性能。在设计系统时应合理规划 Watch 的使用,避免过度依赖或滥用。
应用场景
Watch 事件监听广泛应用于分布式系统中的各种场景,如:
-
配置变更通知:当存储在 ZooKeeper 中的配置数据发生变化时,客户端可以立即得到通知并应用新的配置。
-
服务发现:客户端监听服务注册节点的子节点列表变化,实时掌握服务实例的上线、下线情况。
-
分布式锁与同步:客户端通过监听锁节点的状态变化来实现锁的获取与释放,或者通过监听同步点的变化实现分布式流程的协调。
-
集群管理与监控:监控集群中节点的状态变化,及时发现异常并作出响应。
总之,ZooKeeper 的 Watch 事件监听机制为分布式系统提供了实时、灵活的事件驱动能力,极大地简化了状态同步、协调控制等复杂问题的实现,是 ZooKeeper 作为分布式协调服务的核心特性之一。