Zookeeper

1.简介

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

  • dubbo框架 和springcloud 框架中的注册中心(zk ) AService BService
  • Hadoop和Hbase 组件中到的集群架构的集群管理者 ( zk:负责存储集群的信息。)
  • zk实现分布式锁

ZK内存数据模型

模型结构

img点击并拖拽以移动

模型的特点

  • 每个子目录如/node1都被称作一个znode(节点)。这个 znode 是被它所在的路径唯一标识
  • znode 可以有子节点目录,并且每个 znode 可以存储数据
  • znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据
  • znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端

节点的分类

持久节点(PERSISTENT)

是指在节点创建后,就一直存在,直到有删除操作来主动删除这个节点——不会因为创建该节点的客户端会话失效而消失

持久顺序节点(PERSISTENT_SEQUENTIAL)

这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。

临时节点(EPHEMERAL)

和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。

临时顺序节点(EPHEMERAL_SEQUENTIAL)

具有临时节点特点,额外的特性是,每个父节点会为他的第一级子节点维护一份时序。这点和刚才提到的持久顺序节点类似

安装

linux系统安装

# 1.安装jdk并配置环境变量 & 下载zk安装包
- 注:直接下载带bin的   apache-zookeeper-3.6.3-bin.tar.gz
# 2.下载安装包上传到linux服务中,并解压缩
-	tar -zxvf  apache-zookeeper-3.6.3-bin.tar.gz

# 3.重命名安装目录
-	mv apache-zookeeper-3.6.3-bin zk
# 4.配置zoo.cfg配置文件
-	1.修改zk的conf目录下的zoo_simple.cfg,修改完后,重命名为zoo.cfg
  tickTime=2000
  initLimit=10
  syncLimit=5
  dataDir=/home/files/zookeeper/zkdata
  clientPort=2181

# 5.启动zk
-	在zk的bin目录下,运行zkServer.sh
	./bin/zkServer.sh start /home/files/zk/conf/zoo.cfg
	
# 6.使用jps查看启动是否成功
-  jps命令用来查看java相关的进程
# 7.启动客户端连接到zk
- ./bin/zkCli.sh 
- 也可以连接指定服务器的ZK服务./bin/zkCli.sh -server 192.168.0.220:2181
	注意:可以通过  ./bin/zkCli.sh help 查看客户端所有可以执行的指令

Docker安装ZK

# 1.获取zk的镜像
- docker pull zookeeper:3.4.14

# 2.启动zk服务
- docker run --name zk -p 2181:2181 -d zookeeper:3.4.14

配置文件说明

tickTime=2000
# 集群节点之间心跳的时间
initLimit=10
# 初始化集群是节点同步超时时间(20s)
syncLimit=5
# 集群在运行过程中同步数据超时时间(10s)
dataDir=/home/files/zookeeper/zkdata
# 默认数据存储位置
clientPort=2181
# zk服务监听端口号
maxClientCnxns=60
#最大客户端连接数,线程池线程数量
autopurge.snapRetainCount=3
#没生成3个快照后进行一次合并(类似于redis的持久化)
autopurge.purgeInterval=1
#自动合并的时间,单位是小时,为1时表示在一小时之内生成扫三个快照的话就合并一次

客户端基本操作指令

# 1.ls path    							查看特定节点下面的子节点

# 2.create path data   			创建一个节点。并给节点绑定数据(默认是持久性节点)
  - create path data								  创建持久节点(默认是持久节点)
  - create -s path data 						  创建持久性顺序节点
  -	create -e path data 						  创建临时性节点(注意:临时节点不能含有任何子节点)
  -	create -e -s path data            创建临时顺序节点(注意:临时节点不能含有任何子节点)
 
# 3.stat path                查看当前节点状态
# 4.set path data            修改节点数据
# 5.ls2 path(ls+stat)        查看节点下孩子和当前节点的状态
# 6.history                  查看操作历史
# 7.get path                 获得节点上绑定的数据信息
# 8.delete path              删除节点(注意:删除节点不能含有子节点)
# 9.deleteall  path                递归删除节点(注意:会将当前节点下所有节点删除)
# 10.quit                    退出当前会话(会话失效)

查看节点

[zk: localhost:2181(CONNECTED) 4] ls /   #查看根节点下的子节点
[zookeeper] #默认子节点
[zk: localhost:2181(CONNECTED) 5] ls /zookeeper 
[config, quota]
[zk: localhost:2181(CONNECTED) 6] ls /zookeeper/quota 
[]
[zk: localhost:2181(CONNECTED) 7] 

创建节点

#在根节点下创建持久节点node1
[zk: localhost:2181(CONNECTED) 7] create /node1 zhangsan
Created /node1
[zk: localhost:2181(CONNECTED) 8] ls /
[node1, zookeeper]

#创建持久顺序节点
[zk: localhost:2181(CONNECTED) 9] create -s /node2 sovzn
Created /node20000000001
[zk: localhost:2181(CONNECTED) 10] create -s /node2 sovzn
Created /node20000000002
[zk: localhost:2181(CONNECTED) 11] create -s /node2 sovzn
Created /node20000000003
[zk: localhost:2181(CONNECTED) 12] ls /
[node1, node20000000001, node20000000002, node20000000003, zookeeper]
[zk: localhost:2181(CONNECTED) 13] 

#创建临时性节点
[zk: localhost:2181(CONNECTED) 13] create -e /node syc
Created /node
[zk: localhost:2181(CONNECTED) 14] ls /
[node, node1, node20000000001, node20000000002, node20000000003, zookeeper]

#创建临时顺序节点
[zk: localhost:2181(CONNECTED) 15] create -e -s  /nodes syc
Created /nodes0000000005
[zk: localhost:2181(CONNECTED) 16] ls /
[node, node1, node20000000001, node20000000002, node20000000003, nodes0000000005, zookeeper]

TIP

临时节点在会话结束后,ZK会将其自动删除,但是客户端断开连接并不是意味着会话结束,当客户端及时再次重连还是能访问到节点,会话结束失效有两种:一是会话超时,当会话达到超时时限后,会话会自动失效,二是执行命令:quit退出当前会话。

#当执行quit后再次连接客户端,发现刚创建的临时节点已经消失
[zk: localhost:2181(CONNECTED) 0] ls /
[node1, node20000000001, node20000000002, node20000000003, zookeeper]
[zk: localhost:2181(CONNECTED) 1] 

查看节点状态

[zk: localhost:2181(CONNECTED) 0] ls /
[node1, node20000000001, node20000000002, node20000000003, zookeeper]
[zk: localhost:2181(CONNECTED) 1] stat /node1
cZxid = 0x4   #事务ID
ctime = Wed Jul 07 16:24:11 CST 2021   #创建时间
mZxid = 0x4   
mtime = Wed Jul 07 16:24:11 CST 2021   #修改时间
pZxid = 0x4
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8   #存储的数据的长度
numChildren = 0   #所含有子节点的数量
[zk: localhost:2181(CONNECTED) 2] 

获取节点数据

[zk: localhost:2181(CONNECTED) 2] get /node1
zhangsan
[zk: localhost:2181(CONNECTED) 3] 

修改节点数据

[zk: localhost:2181(CONNECTED) 2] get /node1
zhangsan
[zk: localhost:2181(CONNECTED) 3] set /node1  shiyaochang
[zk: localhost:2181(CONNECTED) 4] get /node1
shiyaochang
[zk: localhost:2181(CONNECTED) 3] 

Watch节点监听机制

客户端可以监测znode节点的变化。Znode节点的变化触发相应的事件,然后清除对该节点的监测。当监测一个znode节点时候,Zookeeper会发送通知给监测节点。一个Watch事件是一个一次性的触发器,当被设置了Watch的数据和目录发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端以便通知它们。

# 1.ls /path true             监听节点目录的变化
# 2.get /path true			  监听节点数据的变化。   

注意:监听是一次性的,被触发一次后会消失,若想继续监听就要重新设置监听

测试监听机制:

客户端1:

[zk: localhost:2181(CONNECTED) 14] ls /
[node, zookeeper]
[zk: localhost:2181(CONNECTED) 15] ls /node true    #监听节点/node目录的变化
'ls path [watch]' has been deprecated. Please use 'ls [-w] path' instead.
[]
[zk: localhost:2181(CONNECTED) 16] get /node true   #监听节点/node数据的变化。   
'get path [watch]' has been deprecated. Please use 'get [-s] [-w] path' instead.
shiyaochang
[zk: localhost:2181(CONNECTED) 17] 

客户端2:

[zk: localhost:2181(CONNECTED) 3] set /node sovzn  #改变节点/node的绑定数据
[zk: localhost:2181(CONNECTED) 4] create /node/syc ssyc  #给节点/node创建子节点
Created /node/syc 

在客户端1会受到监听通知,提示节点发生了哪些改变:

WATCHER::

WatchedEvent state:SyncConnected type:NodeDataChanged path:/node #提示节点数据发生改变

WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/node #提示子节点发生改变

java操作ZK

导入依赖

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

具体操作

public class TestzkClient {
    private  ZkClient zkClient;
    @Before //初始化客户端对象
    public void before(){
        //获取连接   注意服务器防火墙和安全组开放端口
        zkClient = new ZkClient("1.117.24.236:2181",
                6000*30,6000,new SerializableSerializer());
       /* 参数说明:
        参数1:服务器的ip和端口
        参数2:会话的超时时间
        参数3:回话的连接时间
        参数4:对象序列化方式*/
        System.out.println(zkClient);
    }
    @After//释放资源,会话结束
    public void after() throws InterruptedException {
       // Thread.sleep(10000);线程等待,使线程等待10s,不然程
        // 序运行结束,客户端会话也会结束,无法及时获取到临时节点的数据信息
        zkClient.close();
    }

      //一.创建节点:======================================
    @Test
    public void testCreateNode(){
        //1.创建持久节点,这种创建方式是有返回值的,也可以不用接受这个返回值
        String sovzn = zkClient.create("/node1", "Sovzn", CreateMode.PERSISTENT);
        //2.创建持久顺序节点
        zkClient.create("/node1/name", "shiyaochang", CreateMode.PERSISTENT_SEQUENTIAL);
        //3.创建临时节点
        zkClient.create("/node1/list", "test", CreateMode.EPHEMERAL);
        //4.创建临时顺序节点
        zkClient.create("/node1/list11", "test11", CreateMode.EPHEMERAL_SEQUENTIAL);
        }
    //二.删除节点:======================================
    @Test
    public void testDeleteNode(){
        //boolean delete=zkClient.delete("/node1");若 /node1 下有子节点就无法删除
        //System.out.println(delete);
        //递归删除
        boolean result =zkClient.deleteRecursive("/node1");//会连同子节点一起删除
        System.out.println(result);
    }
    //三.操作节点:======================================
    @Test
    public void testFindNodes(){
        //1.查看节点的子节点
        //获取指定路径的节点信息  //返回值: 为当前节点的子节点信息
        List<String> children = zkClient.getChildren("/");
        for (String child : children) {
            System.out.println(child);
        }
        /* 注意:如果出现:org.I0Itec.zkclient.exception.ZkMarshallingError:        java.io.StreamCorruptedException:
        invalid stream header: 61616161 异常的原因是: 在shell中的数据序列化方式 和 java代码中使用的序列化方式不一致导致
         因此要解决这个问题只需要保证序列化一致即可  都使用相同端操作即可,创建节点时都通过java或者都通过客户端*/

        //2.获取节点的数据
        Object readData = zkClient.readData("/node1");
        System.out.println(readData);

        //3.获取数据以及当前节点的状态信息
        Stat stat = new Stat();
        Object readData1 = zkClient.readData("/node1",stat);
        System.out.println(readData1);
        System.out.println(stat);

        //4.修改节点数据
        zkClient.writeData("/node1","Data");
    }

      //三.节点监听:======================================

     //  注意:在java中,监听是永久监听,并不会因为监听被触发而到时监听失效
     //  且监听的对象也是java客户端,只要java客户端对节点进行操作才能监听到
     //  在Xshell中通过ZK的客户端对节点的操作时监听不到的

    //1.监听节点数据的变化
    @Test
    public  void testOnNodeDataChange() throws IOException {
        zkClient.subscribeDataChanges("/node1", new IZkDataListener() {
            //当节点的值在修改时,会自动调用这个方法  将当前修改节点的名字,和节点变化之后的数据传递给方法
            public void handleDataChange(String nodeName, Object result) throws Exception {
                System.out.println("当前节点路径:"+nodeName);
                System.out.println("变化后的数据:"+result);
            }
            //当节点的值被删除的时候,会自动调用这个方法,会将节点的名字以参数形式传递给方法
            public void handleDataDeleted(String nodename) throws Exception {
                System.out.println("节点的名字:"+nodename);
            }
        });
        //阻塞客户端,让程序一直运行,以便查看监听结果
        System.in.read();
    }
    //2.监听节点目录的变化
    @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);
                }
            }
        });
        //阻塞客户端
        System.in.read();
    }
}

ZK的集群

集群(cluster)

  • 集合同一种软件服务的多个节点同时提供服务
  • ZK集群中,在任意一个服务中对节点的操作,都会通过原子传播同步到其他服务,达到集群中每个服务都能共享数据的目的。且当主节点(leader)宕机后,会在仲裁节点中重新选出一个主节点。

集群解决的问题

  • 单节点的并发访问的压力问题
  • 单节点故障问题(如硬件老化,自然灾害等)

集群架构

img点击并拖拽以移动

  • leader:zk集群的主节点
  • following:仲裁节点,除主节点以外的其他节点

集群搭建

# 1.创建三个dataDir
     [root@VM-0-13-centos zookeeper]# mkdir zkdata1 zkdata2 zkdata3
     [root@VM-0-13-centos zookeeper]# pwd
     /home/files/zookeeper

# 2.分别在三个dataDir目录下面创建myid文件
    [root@VM-0-13-centos zookeeper]# touch zkdata1/myid zkdata2/myid zkdata3/myid
# 3.分别在三个myid文件写入id:1 、2 、 3
    [root@VM-0-13-centos zookeeper]# echo "1" >> zkdata1/myid
    [root@VM-0-13-centos zookeeper]# echo "2" >> zkdata2/myid
    [root@VM-0-13-centos zookeeper]# echo "3" >> zkdata3/myid
    [root@VM-0-13-centos zookeeper]# cat zkdata1/myid
    1
    [root@VM-0-13-centos zookeeper]# cat zkdata2/myid
    2
    [root@VM-0-13-centos zookeeper]# cat zkdata3/myid
    3
    [root@VM-0-13-centos zookeeper]# 
# 4.分别在三个dataDir下再创建三个zk配置文件,分别为 zoo1.cfg,zoo2.cfg,zoo3.cfg	
- [root@VM-0-13-centos zookeeper]# vim zkdata1/zoo1.cfg
- [root@VM-0-13-centos zookeeper]# vim zkdata2/zoo2.cfg
- [root@VM-0-13-centos zookeeper]# vim zkdata3/zoo3.cfg

-	zoo1.cfg
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/home/files/zookeeper/zkdata1
    clientPort=3001
    server.1=1.117.24.236:3002:3003
    server.2=1.117.24.236:4002:4003
    server.3=1.117.24.236:5002:5003
- zoo2.cfg
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/home/files/zookeeper/zkdata2
    clientPort=4001
    server.1=1.117.24.236:3002:3003
    server.2=1.117.24.236:4002:4003
    server.3=1.117.24.236:5002:5003
- zoo3.cfg
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir==/home/files/zookeeper/zkdata3
    clientPort=5001
    server.1=1.117.24.236:3002:3003
    server.2=1.117.24.236:4002:4003
    server.3=1.117.24.236:5002:5003

		解释:
    		1.server.X :x为服务器的唯一标识。
	    	2.192.168.0.220:服务器所在的ip地址
		    3.3002:数据同步使用的端口号
		    4.3003:选举使用的端口号
# 4.分别启动各个zk服务器
- [root@c60 zk]# ./bin/zkServer.sh start /home/files/zookeeper/zkdata1/zoo1.cfg
- [root@c60 zk]# ./bin/zkServer.sh start /home/files/zookeeper/zkdata1/zoo1.cfg
- [root@c60 zkr]# ./bin/zkServer.sh start /home/files/zookeeper/zkdata1/zoo1.cfg

# 5.查看各个zk服务器的角色信息
- [root@c60 zookeeper]# ./bin/zkServer.sh status /home/files/zookeeper/zkdata1/zoo1.cfg

# 6.客户端连接任意zk服务器进行节点操作
- [root@c60 zookeeper]# ./bin/zkCli.sh -server 1.117.24.236:3001

# 7.停止特定zk服务器
- [root@c60 zookeeper]# ./bin/zkServer.sh stop /home/files/zookeeper/zkdata1/zoo1.cfg

java 操作集群

我们只需要在获取客户端对象时,将集群的每个服务写入,这样,当集群中的一个服务宕机时,java会自动选择其中一个正常的服务进行操作。

ZKClient zkClient = new ZkClient("1.117.24.236:3001,1.117.24.236:4001,1.117.24.236:5001", 6000*30,6000,new SerializableSerializer());

源码地址:链接:https://pan.baidu.com/s/1j3IA2B7_TeuqzeEqYiJouA
提取码:r0r3