
ZooKeeper(动物园管理者) 简称 ZK,一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。ZooKeeper 使用 Java 所编写,但是支持 Java 和 C 两种编程语言。

是指在节点创建后,就一直存在,直到有删除操作来主动删除这个节点——不会因为创建该节点的客户端会话失效而消失
这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。
具有临时节点特点,额外的特性是,每个父节点会为他的第一级子节点维护一份时序。这点和刚才提到的持久顺序节点类似
# 1.安装jdk并配置环境变量
因为zookeeper是由java写的,所以我们要先安装JDK环境
https://www.oracle.com/java/technologies/downloads/#java8
下载 JDK 在linux环境下的安装包 jdk-8u341-linux-x64.rpm
然后上传到Linux上
执行下面命令安装jdk
rpm -ivh jdk-8u341-linux-x64.rpm
然后就是配置环境变量
vim /etc/profile
最后两行
export JAVA_HOME=/usr/java/jdk1.8.0_341-amd64
export PATH=$PATH:$JAVA_HOME/bin
然后保存退出
执行下面命令使刚才配置生效(好处是无需重启Linux操作系统)
source /etc/profile
然后执行下面命令看java环境是否配置成功
java -version
# 下载zk安装包
- http://archive.apache.org/dist/zookeeper/zookeeper-3.4.12/zookeeper-3.4.12.tar.gz
下载安装包上传到linux服务器中
# 2.解压缩zookeeper安装包
- tar -zxvf zookeeper-3.4.12.tar.gz
# 3.重命名安装目录(名字可改可不改,这里是为了后续用起来方便)
- mv zookeeper-3.4.12 zk
# 4.配置zoo.cfg配置文件
- 1.重命名zk的conf目录下的zoo_simple.cfg为zoo.cfg,然后修改里面的内容
cd /root/zk/conf
mv zoo_sample.cfg zoo.cfg
vim zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/zookeeper/zkdata
clientPort=2181
解释一下上面配置文件的内容:
tickTime: 集群结点之间的心跳时间,单位毫秒
initLimit:初始化限制,同步阶段的心跳次数,我们在刚刚搭建好集群的时候,集群结点之间为了发现彼此,我们
需要等待集群结点之间信息一致性的同步,initLimit表示同步次数,同步时间为同步次数 * 每次时
间,表示初始化集群时集群结点同步超时时间为几个心跳时间
syncLimit:同步限制,表示次数, 表示集群在运行过程中同步数据的超时时间为几个心跳时间
dataDir: 表示zk运行的过程中产生的数据存放在哪里
clientPort:表示zk服务监听的端口号
还有一些参数,不过默认已经注释掉了
#maxClientCnxns:表示客户端最大连接数,默认客户端最大连接数为60个
#autopurge.snapRetainCount:快照数量达到这个值就会对快照做一个合并,默认为3
#autopurge.purgeInterval:自动合并的时间,单位是小时,例如,两个小时快照达到3个以上做一次合并,默认为1
# 5.启动zk
- 在zk的目录下,运行bin目录下zkServer.sh
cd /root/zk
./bin/zkServer.sh start /root/zk/conf/zoo.cfg
# 6.使用jps查看启动是否成功
jps jps是jdk提供的查看java相关的进程,zookeeper是由java编写的,所以可以查看
显示:
3426 Jps
3387 QuorumPeerMain 这个就是zk的进程
# 7.启动客户端连接到zk
在zk的目录下,运行bin目录下zkCli.sh
cd /root/zk
./bin/zkCli.sh -server 192.168.204.132:2181
注:如果连接的是自己机器上的zookeeper,-server以及后面的ip和端口 可以不用加
注意:可以通过 ./bin/zkCli.sh help 查看客户端所有可以执行的指令
显示的内容最后一行显示大概如下说明安装成功:
[zk: 192.168.204.132:2181(CONNECTED) 0]
如果使用docker安装zookeeper,需要先安装docker(docker安装就不演示了)
# 1.获取zk的镜像
- docker pull zookeeper:3.4.14
# 2.启动zk服务
- docker run --name zk -p 2181:2181 -d zookeeper:3.4.14
基于容器启动的服务我们该如何操作呢
如果是通过java客户端连的话,我们只需要连接宿主机的2181就可以映射到容器内部的2181的zookeeper服务
如果我们要使用客户端操作的话,我们就得进入这个容器,使用下面命令进入容器
docker exec -it 51 bash ----- 51 表示容器id,需要根据自己的容器id进行设置
进入容器之后执行下面命令使用客户端操作
./bin/zkCli.sh ----- 因为连接的是自己机器上的zookeeper服务,所以-server不用加
# 1.ls path 查看特定节点(路径标识)下面的子节点(只显示子节点,不显示子孙结点等)
例如: ls /
# 2.create path data 创建一个节点。并给节点绑定数据(默认是持久性节点)
- create path data 创建持久节点(默认是持久节点)
- create -s path data 创建持久性顺序节点
- create -e path data 创建临时性节点(注意:临时节点不能含有任何子节点)
- create -e -s path data 创建临时顺序节点(注意:临时节点不能含有任何子节点)
例:create /node1 xiaoming
# 3.stat path 查看节点状态
例:stat /node1
# 4.set path data 修改节点数据
例:set /node1 xiaochen
# 5.ls2 path 查看当前节点的孩子节点以及当前节点的状态
例:ls2 /node1
# 6.history 展示历史做过哪些操作
# 7.get path 获得节点上绑定的数据信息
例:get /node1
# 8.delete path 删除节点(这个命令只能删除节点不含子节点的节点)
例:delete /node1
# 9.rmr path 递归删除节点(注意:会将当前节点以及节点所有子节点删除)
例:rmr /node1
# 10.quit 退出当前会话并断开连接(会话失效),注:连接断开不等于会话失效,
如果直接使用 Ctrl + c 断开连接,会话不会失效,而是会在到达一定时间失效
客户端可以监测znode节点的变化。Zonode节点的变化触发相应的事件,然后清除对该节点的监测。当监测一个znode节点时候,Zookeeper会发送通知给监测节点。**一个Watch事件是一个一次性的触发器,当被设置了Watch的数据和目录发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端以便通知它们。**监听器是一次性触发器,只要触发过一次,下一次就失效了。
# 1.ls /path true 监听节点目录的变化
例:ls /node1 true
# 2.get /path true 监听节点数据的变化。
例:get /node1 true
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.10version>
dependency>
private ZkClient zkClient;
// 获取zk客户端连接
@Before
public void Before(){
//参数1:服务器的ip和端口
//参数2:会话的超时时间
//参数3:回话的连接时间
//参数4:序列化方式
zkClient = new ZkClient("192.168.28.132:2181",30000,60000,new SerializableSerializer());
}
// 释放资源
@After
public void after(){
zkClient.close();
}
// 创建结点
@Test
public void testCreateNode(){
//第一种创建方式 返回创建节点的名称
String nodeName = zkClient.create("/node5","lisi", CreateMode.PERSISTENT);
zkClient.create("/node6","zhangsan", CreateMode.PERSISTENT_SEQUENTIAL);
zkClient.create("/node7","王五",CreateMode.EPHEMERAL);
zkClient.create("/node8","xiaozhang",CreateMode.EPHEMERAL_SEQUENTIAL);
//第二种创建方式 不会返回创建节点的名称
zkClient.createPersistent("/node1","持久数据");
zkClient.createPersistentSequential("/node1/aa","持久数据顺序节点");
zkClient.createEphemeral("/node2","临时节点");
zkClient.createEphemeralSequential("/node1/bb","临时顺序节点");
}
// 删除节点
@Test
public void testDeleteNode(){
//删除没有子节点的节点 返回值:是否删除成功
boolean delete = zkClient.delete("/node1");
//递归删除节点信息 返回值:是否删除成功
boolean recursive = zkClient.deleteRecursive("/node1");
}
// 查询结点
@Test
public void testFindNodes(){
//获取指定路径的节点信息 //返回值: 为当前节点的子节点信息
List<String> children = zkClient.getChildren("/");
for (String child : children) {
System.out.println(child);
}
}
// 获取节点的数据
@Test
public void testFindNodeData(){
Object readData = zkClient.readData("/node1");
System.out.println(readData);
}
// 获取数据以及当前节点的状态信息
@Test
public void testFindNodeDataAndStat(){
Stat stat = new Stat();
Object readData = zkClient.readData("/node1",stat);
System.out.println(readData);
System.out.println(stat.getCversion()); // 状态版本
System.out.println(stat.getCtime()); // 创建时间
System.out.println(stat.getCzxid()); // 创建id
}
// 修改节点数据
@Test
public void testUpdateNodeData(){
User user = new User(1, "小陈", 23, new Date()); // 注:这个对象需要实现Serializable接口
zkClient.writeData("/node1", user);
}
java中监听是永久监听
// 监听节点数据的变化
@Test
public void testWatchDataChange() throws IOException {
zkClient.subscribeDataChanges("/node1", new IZkDataListener() {
// 当前节点数据变化时触发这个方法
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("当前节点路径:" + dataPath);
System.out.println("当前节点变化后数据:" + data);
}
// 当前节点删除时触发这个方法
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println("当前节点路径:" + dataPath);
}
});
//阻塞客户端去监听
System.in.read();
}
java中监听是永久监听
// 监听节点目录的变化
@Test
public void testOnNodesChange() throws IOException {
zkClient.subscribeChildChanges("/node1", new IZkChildListener() {
//当节点的目录发生变化时,会自动调用这个方法
//参数1:父节点名称
//参数2:父节点中的所有子节点名称
public void handleChildChange(String nodeName, List<String> list) throws Exception {
System.out.println("父节点名称: " + nodeName);
System.out.println("发生变更后所有子节点名称:");
for (String name : list) {
System.out.println(name);
}
}
});
//阻塞java客户端去监听
System.in.read();
}
总测试代码:
public class TestZkClient {
private ZkClient zkClient;
// 1. 在zk中创建结点
@Test
public void testCreateNode(){
// 1. 持久结点 参数:创建的路径 数据 结点的类型
zkClient.create("/node1", "xiaochen", CreateMode.PERSISTENT);
// 2. 持久顺序结点
zkClient.create("/node1/names", "zhangsan", CreateMode.PERSISTENT_SEQUENTIAL);
// 3. 临时结点
zkClient.create("/node1/lists", "xiaoxiao", CreateMode.EPHEMERAL);
// 4. 临时顺序结点
zkClient.create("/node1/lists11", "xiaoming", CreateMode.EPHEMERAL_SEQUENTIAL);
}
// 2. 删除节点
@Test
public void testDeleteNode(){
//删除没有子节点的节点 返回值:是否删除成功
boolean delete = zkClient.delete("/node1");
//递归删除节点信息 返回值:是否删除成功
boolean recursive = zkClient.deleteRecursive("/node1");
}
// 3. 查询当前节点的所有子节点
@Test
public void testFindNodes(){
//获取指定路径的节点信息 //返回值: 为当前节点的子节点信息
List<String> children = zkClient.getChildren("/");
for (String child : children) {
System.out.println(child);
}
}
// 4. 获取指定节点的数据 注意:通过java客户端操作需要保证节点存储的数据和获取节点时数据序列化方式必须一致
// 如果在zk那边手动创建,序列化方式为字符串那种序列化,在java中是对象序列化方式
// 所以我们一定要保证获取指定节点的数据时该节点是通过java客户端创建的,这样序列化方式一致
@Test
public void testFindNodeData(){
Object readData = zkClient.readData("/node1");//一定要保证该节点创建和获取时序列化方式相同(通过java客户端创建和获取)
System.out.println(readData);
}
// 5. 获取指定节点数据以及节点的状态信息
@Test
public void testFindNodeDataAndStat(){
Stat stat = new Stat();
Object readData = zkClient.readData("/node1",stat);
System.out.println(readData);
System.out.println(stat.getCversion()); // 状态版本
System.out.println(stat.getCtime()); // 创建时间
System.out.println(stat.getCzxid()); // 创建id
}
// 6. 修改节点数据
@Test
public void testUpdateNodeData(){
User user = new User(1, "小陈", 23, new Date()); // 注:这个对象需要实现Serializable接口
zkClient.writeData("/node1", user);
User o = zkClient.readData("/node1");
System.out.println(o);
}
// 7. 监听节点的数据变化
@Test
public void testWatchDataChange() throws IOException {
zkClient.subscribeDataChanges("/node1", new IZkDataListener() {
// 当前节点数据变化时触发这个方法
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("当前节点路径:" + dataPath);
System.out.println("当前节点变化后数据:" + data);
}
// 当前节点删除时触发这个方法
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println("当前节点路径:" + dataPath);
}
});
//阻塞客户端
System.in.read();
}
// 8. 监听节点目录的变化
@Test
public void testOnNodesChange() throws IOException {
zkClient.subscribeChildChanges("/node1", new IZkChildListener() {
//当节点的目录发生变化时,会自动调用这个方法
//参数1:父节点名称
//参数2:父节点中的所有子节点名称
public void handleChildChange(String nodeName, List<String> list) throws Exception {
System.out.println("父节点名称: " + nodeName);
System.out.println("发生变更后所有子节点名称:");
for (String name : list) {
System.out.println(name);
}
}
});
//阻塞java客户端去监听
System.in.read();
}
// 初始化客户端对象,获取连接
@Before
public void before(){
// 获取连接 参数:zk服务器ip及端口 会话超时时间(单位毫秒) 连接超时时间(单位毫秒) 序列化方式(存储对象时需要序列化)
zkClient = new ZkClient("192.168.204.132:2181", 60000*30, 60000, new SerializableSerializer());
}
// 获取连接
public static void main(String[] args) {
System.out.println();
}
// 释放资源
@After
public void after(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
zkClient.close();
}
}
# 1.集群(cluster)
- 集合同一种软件服务的多个节点同时提供服务
# 2.集群解决问题
- 单节点的并发访问的压力问题
- 单节点故障问题(如硬件老化,自然灾害等)

集群搭建要求至少有三个服务,即最少三台服务器。这里为了方便,我在一台机器上启动三个zookeeper服务模拟三个服务器。
# 1.创建三个dataDir
[root@192 ~]# mkdir zkdata1 zkdata2 zkdata3
# 2.分别在三个dataDir目录下面myid文件,然后分别在它们的myid文件中写入 1 2 3
touch zkdata1/myid zkdata2/myid zkdata3/myid
echo "1" >> zkdata1/myid
echo "2" >> zkdata2/myid
echo "3" >> zkdata3/myid
myid的内容是服务器在集群中的标识 1 2 3
# 3.在/conf目录下创建三个zk配置文件,分别为 zoo.cfg, zoo.cfg, zoo.cfg
- vim zkdata1/zoo.cfg
- zoo1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/zkdata1
clientPort=3001
server.1=192.168.204.132:3002:3003
server.2=192.168.204.132:4002:4003
server.3=192.168.204.132:5002:5003
- vim zkdata2/zoo.cfg
- zoo2.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/zkdata2
clientPort=4001
server.1=192.168.204.132:3002:3003
server.2=192.168.204.132:4002:4003
server.3=192.168.204.132:5002:5003
- vim zkdata3/zoo.cfg
- zoo3.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/zkdata3
clientPort=5001
server.1=192.168.204.132:3002:3003
server.2=192.168.204.132:4002:4003
server.3=192.168.204.132:5002:5003
解释:
1.server.X :x为服务器的唯一标识。
2.192.168.204.132:服务器所在的ip地址
3.3002:数据同步使用的端口号,原子广播通信
4.3003:选举使用的端口号,心跳检查
# 4.分别启动各个zk服务器
进入 zookeeper 目录
cd /root/zk
- [root@c60 zk]# ./bin/zkServer.sh start /root/zkdata1/zoo.cfg
- [root@c60 zk]# ./bin/zkServer.sh start /root/zkdata2/zoo.cfg
- [root@c60 zk]# ./bin/zkServer.sh start /root/zkdata3/zoo.cfg
# 5.查看各个zk服务器的角色信息
- [root@c60 zookeeper]# ./bin/zkServer.sh status /root/zkdata1/zoo.cfg
# 6.客户端连接任意zk服务器进行节点操作
- [root@c60 zookeeper]# ./bin/zkCli.sh -server 192.168.204.132:3001
# 7.停止特定zk服务器
- [root@c60 zookeeper]# ./bin/zkServer.sh stop /usr/zkdata1/zoo.cfg
操作集群只需要把对应的客户端结点换成集群中任意一个节点就可以了,当然也建议书写的时候把所有结点都书写上。
// 获取zk客户端连接
@Before
public void Before(){
//参数1:服务器的ip和端口
//参数2:会话的超时时间
//参数3:回话的连接时间
//参数4:序列化方式
zkClient = new ZkClient("192.168.28.132:2181,192.168.28.133:2181",30000,60000,new SerializableSerializer());
}
然后接下来的操作和 java 操作zk单节点是一样的。