目录
1.1 Zookeeper 的概述
-
Zookeeper 是一个开源的分布式协调服务框架 ,主要用来解决分布式集群中应用系统的一致性问题和数据管理问题
1.1.1 ZooKeeper 如何保证数据一致性
在分布式场景中,ZooKeeper 的应用非常广泛,比如数据发布和订阅、命名服务、配置中心、注册中心、分布式锁等。
ZooKeeper 提供了一个类似 Linux 文件系统的数据模型,和基于 Watcher 机制的分布式事件通知,这些特性都依赖 ZooKeeper 的高容错数据一致性协议。
那么问题来了,在分布式场景下,ZooKeeper 是如何实现数据一致性的呢?
1.2.2 Zab 一致性协议
ZooKeeper 是通过 Zab 协议来保证分布式事务的最终一致性。Zab(ZooKeeper Atomic Broadcast,ZooKeeper 原子广播协议)支持崩溃恢复,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间数据一致性。
系统架构可以参考下面这张图:
在 ZooKeeper 集群中,所有客户端的请求都是写入到 Leader 进程中的,然后,由 Leader 同步到其他节点,称为 Follower。在集群数据同步的过程中,如果出现 Follower 节点崩溃或者 Leader 进程崩溃时,都会通过 Zab 协议来保证数据一致性。
Zab 协议的具体实现可以分为以下两部分:
消息广播阶段
Leader 节点接受事务提交,并且将新的 Proposal 请求广播给 Follower 节点,收集各个节点的反馈,决定是否进行 Commit,在这个过程中,也会使用上一课时提到的 Quorum 选举机制。
崩溃恢复阶段
如果在同步过程中出现 Leader 节点宕机,会进入崩溃恢复阶段,重新进行 Leader 选举,崩溃恢复阶段还包含数据同步操作,同步集群中最新的数据,保持集群的数据一致性。
整个 ZooKeeper 集群的一致性保证就是在上面两个状态之前切换,当 Leader 服务正常时,就是正常的消息广播模式;当 Leader 不可用时,则进入崩溃恢复模式,崩溃恢复阶段会进行数据同步,完成以后,重新进入消息广播阶段。
1.2:Zookeeper的特点
- Zookeeper 本质上是一个分布式文件系统, 适合存放小文件,也可以理解为一个数据库
- 在上图左侧, Zookeeper 中存储的其实是一个又一个 Znode, Znode 是 Zookeeper 中的节点
- Znode 是有路径的, 例如
/data/host1
,/data/host2
, 这个路径也可以理解为是 Znode 的 Name - Znode 也可以携带数据, 例如说某个 Znode 的路径是
/data/host1
, 其值是一个字符串"192.168.0.1"
- Znode 是有路径的, 例如
- 正因为 Znode 的特性, 所以 Zookeeper 可以对外提供出一个类似于文件系统的试图, 可以通过操作文件系统的方式操作 Zookeeper
-
使用路径获取 Znode
-
获取 Znode 携带的数据
-
修改 Znode 携带的数据
-
删除 Znode
-
添加 Znode
-
1.3.1 数据发布/订阅
数据发布/订阅系统,需要发布者将数据发布到Zookeeper的节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
发布/订阅一般有两种设计模式:推模式和拉模式,服务端主动将数据更新发送给所有订阅的客户端称为推模式;客户端主动请求获取最新数据称为拉模式.
Zookeeper采用了推拉相结合的模式,客户端向服务端注册自己需要关注的节点,一旦该节点数据发生变更,那么服务端就会向相应的客户端推送Watcher事件通知,客户端接收到此通知后,主动到服务端获取最新的数据。
1.3.2 命名服务(一般文件名不能相同,使用zookeeper命名)
命名服务是分步实现系统中较为常见的一类场景,分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等,通过命名服务,客户端可以根据指定名字来获取资源的实体,在分布式环境中,上层应用仅仅需要一个全局唯一的名字。Zookeeper可以实现一套分布式全局唯一ID的分配机制。
通过调用Zookeeper节点创建的API接口就可以创建一个顺序节点,并且在API返回值中会返回这个节点的完整名字,利用此特性,可以生成全局ID,其步骤如下
1. 客户端根据任务类型,在指定类型的任务下通过调用接口创建一个顺序节点,如"job-"。
2. 创建完成后,会返回一个完整的节点名,如"job-00000001"。
3. 客户端拼接type类型和返回值后,就可以作为全局唯一ID了,如"type2-job-00000001"。
1.3.3 分布式协调/通知
Zookeeper中特有的Watcher注册于异步通知机制,能够很好地实现分布式环境下不同机器,甚至不同系统之间的协调与通知,从而实现对数据变更的实时处理。通常的做法是不同的客户端都对Zookeeper上的同一个数据节点进行Watcher注册,监听数据节点的变化(包括节点本身和子节点),若数据节点发生变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并作出相应处理。
在绝大多数分布式系统中,系统机器间的通信无外乎心跳检测、工作进度汇报和系统调度。
① 心跳检测,不同机器间需要检测到彼此是否在正常运行,可以使用Zookeeper实现机器间的心跳检测,基于其临时节点特性(临时节点的生存周期是客户端会话,客户端若当即后,其临时节点自然不再存在),可以让不同机器都在Zookeeper的一个指定节点下创建临时子节点,不同的机器之间可以根据这个临时子节点来判断对应的客户端机器是否存活。通过Zookeeper可以大大减少系统耦合。
② 工作进度汇报,通常任务被分发到不同机器后,需要实时地将自己的任务执行进度汇报给分发系统,可以在Zookeeper上选择一个节点,每个任务客户端都在这个节点下面创建临时子节点,这样不仅可以判断机器是否存活,同时各个机器可以将自己的任务执行进度写到该临时节点中去,以便中心系统能够实时获取任务的执行进度。
③ 系统调度,Zookeeper能够实现如下系统调度模式:分布式系统由控制台和一些客户端系统两部分构成,控制台的职责就是需要将一些指令信息发送给所有的客户端,以控制他们进行相应的业务逻辑,后台管理人员在控制台上做一些操作,实际上就是修改Zookeeper上某些节点的数据,Zookeeper可以把数据变更以时间通知的形式发送给订阅客户端。
1.3.4分布式锁
分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,可以保证不同系统访问一个或一组资源时的一致性,主要分为排它锁和共享锁。
排它锁又称为写锁或独占锁,若事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作,直到T1释放了排它锁。
① 获取锁,在需要获取排它锁时,所有客户端通过调用接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。Zookeeper可以保证只有一个客户端能够创建成功,没有成功的客户端需要注册/exclusive_lock节点监听。
② 释放锁,当获取锁的客户端宕机或者正常完成业务逻辑都会导致临时节点的删除,此时,所有在/exclusive_lock节点上注册监听的客户端都会收到通知,可以重新发起分布式锁获取。
共享锁又称为读锁,若事务T1对数据对象O1加上共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被释放。在需要获取共享锁时,所有客户端都会到/shared_lock下面创建一个临时顺序节点
1.3.5 分布式队列
有一些时候,多个团队需要共同完成一个任务,比如,A团队将Hadoop集群计算的结果交给B团队继续计算,B完成了自己任务再交给C团队继续做。这就有点像业务系统的工作流一样,一环一环地传下 去.
分布式环境下,我们同样需要一个类似单进程队列的组件,用来实现跨进程、跨主机、跨网络的数据共享和数据传递,这就是我们的分布式队列。
1.4.Zookeeper的架构
Zookeeper集群是一个基于主从架构的高可用集群
每个服务器承担如下三种角色中的一种
-
Leader 一个Zookeeper集群同一时间只会有一个实际工作的Leader,它会发起并维护与各Follwer及Observer间的心跳。所有的写操作必须要通过Leader完成再由Leader将写操作广播给其它服务器。
-
Follower 一个Zookeeper集群可能同时存在多个Follower,它会响应Leader的心跳。Follower可直接处理并返回客户端的读请求,同时会将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。
-
Observer 角色与Follower类似,但是无投票权。
1.5:Zookeeper的选举机制
Leader选举是保证分布式数据一致性的关键所在。当Zookeeper集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举。
1.5.1. 服务器启动时期的Leader选举
若进行Leader选举,则至少需要两台机器,这里选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独无法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程如下
(1) 每个Server发出一个投票。由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0),Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
(2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
(3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下
· 优先检查ZXID。ZXID比较大的服务器优先作为Leader。
· 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。
对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
(4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。
(5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。
#####5.2.服务器运行时期的Leader选举
在Zookeeper运行期间,Leader与非Leader服务器各司其职,即便当有非Leader服务器宕机或新加入,此时也不会影响Leader,但是一旦Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致过程相同。
2: Zookeeper安装2.1:三台机器安装jdk
2.1.1 查看自带的openjdk并卸载
rpm -qa | grep java
rpm -e java-1.6.0-openjdk-1.6.0.41-1.13.13.1.el6_8.x86_64 tzdata-java-2016j-1.el6.noarch java-1.7.0-openjdk-1.7.0.131-2.6.9.0.el6_8.x86_64 --nodeps
2.1.2 创建安装目录
~~~shell
mkdir -p /export/softwares #软件包存放目录
mkdir -p /export/servers #安装目录
~~~
2.1.3上传并 解压
#上传jdk到/export/softwares路径下去,并解压
tar -zxvf jdk-8u141-linux-x64.tar.gz -C ../servers/
2.1.4 配置环境变量
vim /etc/profile
添加如下内容
export JAVA_HOME=/export/servers/jdk1.8.0_141
export PATH=:$JAVA_HOME/bin:$PATH
修改完成之后记得 source /etc/profile生效
source /etc/profile
2.2:mysql的安装
第一步:在线安装mysql相关的软件包
yum install mysql mysql-server mysql-devel
第二步:启动mysql的服务
/etc/init.d/mysqld start
第三步:通过mysql安装自带脚本进行设置
/usr/bin/mysql_secure_installation
第四步:进入mysql的客户端然后进行授权
grant all privileges on *.* to 'root'@'%' identified by '123456' with grant option;
flush privileges;
3: Zookeeper安装
3.1 集群规划
服务器IP | 主机名 | myid的值 |
---|---|---|
192.168.174.100 | node01 | 1 |
192.168.174.110 | node02 | 2 |
192.168.174.120 | node03 | 3 |
第一步:下载zookeeeper的压缩包,下载网址如下
http://archive.apache.org/dist/zookeeper/
我们在这个网址下载我们使用的zk版本为3.4.9
下载完成之后,上传到我们的linux的/export/softwares路径下准备进行安装
第二步:解压
解压zookeeper的压缩包到/export/servers路径下去,然后准备进行安装
cd /export/software
tar -zxvf zookeeper-3.4.9.tar.gz -C ../servers/
第三步:修改配置文件
第一台机器修改配置文件
cd /export/servers/zookeeper-3.4.9/conf/
cp zoo_sample.cfg zoo.cfg
mkdir -p /export/servers/zookeeper-3.4.9/zkdatas/
vim zoo.cfg
dataDir=/export/servers/zookeeper-3.4.9/zkdatas
# 保留多少个快照
autopurge.snapRetainCount=3
# 日志多少小时清理一次
autopurge.purgeInterval=1
# 集群中服务器地址
server.1=node01:2888:3888
server.2=node02:2888:3888
server.3=node03:2888:3888
第四步:添加myid配置
在第一台机器的
/export/servers/zookeeper-3.4.9/zkdatas /这个路径下创建一个文件,文件名为myid ,文件内容为1
echo 1 > /export/servers/zookeeper-3.4.9/zkdatas/myid
第五步:安装包分发并修改myid的值
安装包分发到其他机器
第一台机器上面执行以下两个命令
scp -r /export/servers/zookeeper-3.4.9/ node02:/export/servers/
scp -r /export/servers/zookeeper-3.4.9/ node03:/export/servers/
第二台机器上修改myid的值为2
echo 2 > /export/servers/zookeeper-3.4.9/zkdatas/myid
第三台机器上修改myid的值为3
echo 3 > /export/servers/zookeeper-3.4.9/zkdatas/myid
第六步:三台机器启动zookeeper服务
三台机器启动zookeeper服务
这个命令三台机器都要执行
/export/servers/zookeeper-3.4.9/bin/zkServer.sh start
查看启动状态
/export/servers/zookeeper-3.4.9/bin/zkServer.sh status
4 Zookeeper的Shell 客户端操作
命令 | 说明 | 参数 |
---|---|---|
create [-s] [-e] path data acl |
创建Znode | -s 指定是顺序节点 -e 指定是临时节点 |
ls path [watch] |
列出Path下所有子Znode | |
get path [watch] |
获取Path对应的Znode的数据和属性 | |
ls2 path [watch] |
查看Path下所有子Znode以及子Znode的属性 | |
set path data [version] |
更新节点 | version 数据版本 |
delete path [version] |
删除节点, 如果要删除的节点有子Znode则无法删除 | version 数据版本 |
rmr path |
删除节点, 如果有子Znode则递归删除 | |
setquota -n|-b val path |
修改Znode配额 | -n 设置子节点最大个数 -b 设置节点数据最大长度 |
history |
列出历史记录 |
1:创建普通节点
create /app1 hello
2: 创建顺序节点
create -s /app3 world
3:创建临时节点
create -e /tempnode world
4:创建顺序的临时节点
create -s -e /tempnode2 aaa
5:获取节点数据
get /app1
6:修改节点数据
set /app1 xxx
7:删除节点
delete /app1 删除的节点不能有子节点
rmr /app1 递归删除
Znode 的特点
- 文件系统的核心是
Znode
- 如果想要选取一个
Znode
, 需要使用路径的形式, 例如/test1/test11
- Znode 本身并不是文件, 也不是文件夹, Znode 因为具有一个类似于 Name 的路径, 所以可以从逻辑上实现一个树状文件系统
- ZK 保证 Znode 访问的原子性, 不会出现部分 ZK 节点更新成功, 部分 ZK 节点更新失败的问题
-
Znode
中数据是有大小限制的, 最大只能为1M
-
Znode
是由三个部分构成-
stat
: 状态, Znode的权限信息, 版本等 -
data
: 数据, 每个Znode都是可以携带数据的, 无论是否有子节点 -
children
: 子节点列表
-
Znode 的类型
- 每个
Znode
有两大特性, 可以构成四种不同类型的Znode
- 持久性
-
持久
客户端断开时, 不会删除持有的Znode -
临时
客户端断开时, 删除所有持有的Znode, 临时Znode不允许有子Znode
-
- 顺序性
-
有序
创建的Znode有先后顺序, 顺序就是在后面追加一个序列号, 序列号是由父节点管理的自增 -
无序
创建的Znode没有先后顺序
-
- 持久性
-
Znode
的属性-
dataVersion
数据版本, 每次当Znode
中的数据发生变化的时候,dataVersion
都会自增一下 -
cversion
节点版本, 每次当Znode
的节点发生变化的时候,cversion
都会自增 -
aclVersion
ACL(Access Control List)
的版本号, 当Znode
的权限信息发生变化的时候aclVersion会自增 -
zxid
事务ID -
ctime
创建时间 -
mtime
最近一次更新的时间 -
ephemeralOwner
如果Znode
为临时节点,ephemeralOwner
表示与该节点关联的SessionId
-
通知机制
- 通知类似于数据库中的触发器, 对某个Znode设置
Watcher
, 当Znode发生变化的时候,WatchManager
会调用对应的Watcher
- 当Znode发生删除, 修改, 创建, 子节点修改的时候, 对应的
Watcher
会得到通知 -
Watcher
的特点-
一次性触发 一个
Watcher
只会被触发一次, 如果需要继续监听, 则需要再次添加Watcher
- 事件封装:
Watcher
得到的事件是被封装过的, 包括三个内容keeperState, eventType, path
-
一次性触发 一个
KeeperState | EventType | 触发条件 | 说明 |
---|---|---|---|
None | 连接成功 | ||
SyncConnected | NodeCreated | Znode被创建 | 此时处于连接状态 |
SyncConnected | NodeDeleted | Znode被删除 | 此时处于连接状态 |
SyncConnected | NodeDataChanged | Znode数据被改变 | 此时处于连接状态 |
SyncConnected | NodeChildChanged | Znode的子Znode数据被改变 | 此时处于连接状态 |
Disconnected | None | 客户端和服务端断开连接 | 此时客户端和服务器处于断开连接状态 |
Expired | None | 会话超时 | 会收到一个SessionExpiredException |
AuthFailed | None | 权限验证失败 | 会收到一个AuthFailedException |
会话
- 在ZK中所有的客户端和服务器的交互都是在某一个
Session
中的, 客户端和服务器创建一个连接的时候同时也会创建一个Session
-
Session
会在不同的状态之间进行切换:CONNECTING
,CONNECTED
,RECONNECTING
,RECONNECTED
,CLOSED
- ZK中的会话两端也需要进行心跳检测, 服务端会检测如果超过超时时间没收到客户端的心跳, 则会关闭连接, 释放资源, 关闭会话
- ZooKeeper 的数据模型,在结构上和标准文件系统的非常相似,拥有一个层
次的命名空间,都是采用树形层次结构.
- ZooKeeper 树中的每个节点被称为—个Znode。和文件系统的目录树一样,ZooKeeper 树中的每个节点可以拥有子节点。
但也有不同之处:
- Znode 兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、 时间戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有 子 Znode。用户对 Znode 具有增、删、改、查等操作(权限允许的情况下)。
- Znode 存储数据大小有限制。ZooKeeper 虽然可以关联一些数据,但并没有 被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据, 比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的 共同特性就是它们都是很小的数据,通常以 KB 为大小单位。ZooKeeper 的服 务器和客户端都被设计为严格检查并限制每个 Znode 的数据大小至多 1M,常规使用中应该远小于此值。
- Znode 通过路径引用,如同 Unix 中的文件路径。路径必须是绝对的,因此他 们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个 路径只有一个表示,因此这些路径不能改变。在 ZooKeeper 中,路径由 Unicode 字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理 信息,比如关键配额信息。
- 每个 Znode 由 3 部分组成:
6.1 Znode 有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。
- 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时 节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。
- 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
6.2 Znode 还有一个序列化的特性,如果创建的时候指定的话,该 Znode 的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。它的格式为“%10d”(10 位数字,没有数值的数位用 0 补充,例如“0000000001”)。
6.3 这样便会存在四种类型的 Znode 节点,分别对应:
- PERSISTENT:永久节点
- EPHEMERAL:临时节点
- PERSISTENT_SEQUENTIAL:永久节点、序列化
- EPHEMERAL_SEQUENTIAL:临时节点、序列化
7.1 登录Zookeeper客户端
bin/zkCli.sh -server node01:2181
7.2 Zookeeper客户端操作命令
命令 | 说明 | 参数 |
---|---|---|
create [-s] [-e] path data acl |
创建Znode | -s 指定是顺序节点 -e 指定是临时节点 |
ls path [watch] |
列出Path下所有子Znode | |
get path [watch] |
获取Path对应的Znode的数据和属性 | |
ls2 path [watch] |
查看Path下所有子Znode以及子Znode的属性 | |
set path data [version] |
更新节点 | version 数据版本 |
delete path [version] |
删除节点, 如果要删除的节点有子Znode则无法删除 | version 数据版本 |
rmr path |
删除节点, 如果有子Znode则递归删除 | |
setquota -n|-b val path |
修改Znode配额 | -n 设置子节点最大个数 -b 设置节点数据最大长度 |
history |
列出历史记录 |
7.3 操作实例
ls /
create /hello world
create -e /abc 123
create -s /zhangsan boy
create -e -s /lisi boy
set /hello zookeeper
delete /hello
rmr /abc
histroy
7.4 节点属性
每个 znode 都包含了一系列的属性,通过命令 get,可以获得节点的属性。
dataVersion:数据版本号,每次对节点进行 set 操作,dataVersion 的值都会增加 1(即使设置的是相同的数据),可有效避免了数据更新时出现的先后顺序问题。
cversion :子节点的版本号。当 znode 的子节点有变化时,cversion 的值就会增加 1。
aclVersion :ACL 的版本号。
cZxid :Znode 创建的事务 id。
mZxid :Znode 被修改的事务 id,即每次对 znode 的修改都会更新 mZxid。
- 对于 zk 来说,每次的变化都会产生一个唯一的事务 id,zxid(ZooKeeper Transaction Id)。通过 zxid,可以确定更新操作的先后顺序。例如,如果 zxid1
- 小于 zxid2,说明 zxid1 操作先于 zxid2 发生,zxid 对于整个 zk 都是唯一的,
ctime:节点创建时的时间戳.
mtime:节点最新一次更新发生时的时间戳.
ephemeralOwner:如果该节点为临时节点, ephemeralOwner 值表示与该节点绑定的 session id. 如果不 是,ephemeralOwner 值为 0.
7.5 Zookeeper的watch机制
- 通知类似于数据库中的触发器, 对某个Znode设置
Watcher
, 当Znode发生变化的时候,WatchManager
会调用对应的Watcher
- 当Znode发生删除, 修改, 创建, 子节点修改的时候, 对应的
Watcher
会得到通知 -
Watcher
的特点-
一次性触发 一个
Watcher
只会被触发一次, 如果需要继续监听, 则需要再次添加Watcher
- 事件封装:
Watcher
得到的事件是被封装过的, 包括三个内容keeperState, eventType, path
-
一次性触发 一个
KeeperState | EventType | 触发条件 | 说明 |
---|---|---|---|
None | 连接成功 | ||
SyncConnected | NodeCreated | Znode被创建 | 此时处于连接状态 |
SyncConnected | NodeDeleted | Znode被删除 | 此时处于连接状态 |
SyncConnected | NodeDataChanged | Znode数据被改变 | 此时处于连接状态 |
SyncConnected | NodeChildChanged | Znode的子Znode数据被改变 | 此时处于连接状态 |
Disconnected | None | 客户端和服务端断开连接 | 此时客户端和服务器处于断开连接状态 |
Expired | None | 会话超时 | 会收到一个SessionExpiredExceptio |
AuthFailed | None | 权限验证失败 | 会收到一个AuthFailedException |
这里操作Zookeeper的JavaAPI使用的是一套zookeeper客户端框架 Curator ,解决了很多Zookeeper客户端非常底层的细节开发工作 。
Curator包含了几个包:
- curator-framework:对zookeeper的底层api的一些封装
- curator-recipes:封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器等
Maven依赖(使用curator的版本:2.12.0,对应Zookeeper的版本为:3.4.x,如果跨版本会有兼容性问题,很有可能导致节点操作失败):
8.1、创建java工程,导入jar包
创建maven java工程,导入jar包
<!-- <repositories>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
</repositories> -->
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
8.2 节点的操作
创建永久节点
@Test
public void createNode() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 1);
//获取客户端对象
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.174.100:2181,192.168.174.110:2181,192.168.174.120:2181", 1000, 1000, retryPolicy);
//调用start开启客户端操作
client.start();
//通过create来进行创建节点,并且需要指定节点类型
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/hello3/world");
client.close();
}
创建临时节点
public void createNode2() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 1);
CuratorFramework client = CuratorFrameworkFactory.newClient("node01:2181,node02:2181,node03:2181", 3000, 3000, retryPolicy);
client.start();
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/hello5/world");
Thread.sleep(5000);
client.close();
}
修改节点数据
/**
* 节点下面添加数据与修改是类似的,一个节点下面会有一个数据,新的数据会覆盖旧的数据
* @throws Exception
*/
@Test
public void nodeData() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 1);
CuratorFramework client = CuratorFrameworkFactory.newClient("node01:2181,node02:2181,node03:2181", 3000, 3000, retryPolicy);
client.start();
client.setData().forPath("/hello5", "hello7".getBytes());
client.close();
}
节点数据查询
/**
* 数据查询
*/
@Test
public void updateNode() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 1);
CuratorFramework client = CuratorFrameworkFactory.newClient("node01:2181,node02:2181,node03:2181", 3000, 3000, retryPolicy);
client.start();
byte[] forPath = client.getData().forPath("/hello5");
System.out.println(new String(forPath));
client.close();
}
节点watch机制
/**
* zookeeper的watch机制
* @throws Exception
*/
@Test
public void watchNode() throws Exception {
RetryPolicy policy = new ExponentialBackoffRetry(3000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("node01:2181,node02:2181,node03:2181", policy);
client.start();
// ExecutorService pool = Executors.newCachedThreadPool();
//设置节点的cache
TreeCache treeCache = new TreeCache(client, "/hello5");
//设置监听器和处理过程
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
ChildData data = event.getData();
if(data !=null){
switch (event.getType()) {
case NODE_ADDED:
System.out.println("NODE_ADDED : "+ data.getPath() +" 数据:"+ new String(data.getData()));
break;
case NODE_REMOVED:
System.out.println("NODE_REMOVED : "+ data.getPath() +" 数据:"+ new String(data.getData()));
break;
case NODE_UPDATED:
System.out.println("NODE_UPDATED : "+ data.getPath() +" 数据:"+ new String(data.getData()));
break;
default:
break;
}
}else{
System.out.println( "data is null : "+ event.getType());
}
}
});
//开始监听
treeCache.start();
Thread.sleep(50000000);
}