Zookeeper 学习笔记归纳

Zookeeper 是一个分布式协调服务,用于配置管理、命名、分布式同步和组服务。它解决了配置信息同步、主从节点选举、集群动态感知等问题。文章介绍了Zookeeper的使用,包括命令行客户端和Java API操作,以及内部机制如选举、数据存储和内部通信。此外,还强调了Zookeeper的注意事项,如数据存储限制和集群高可用性的重要性。

Zookeeper 学习笔记归纳01

1.zookeeper是什么?

  1. zookeeper是一个分布式协调服务工具,可以提供配置信息、名称、分布式同步、组服务等
  2. 官网https://zookeeper.apache.org/
  3. apache基金下的开源软件在这里插入图片描述
  4. 当前版本搜索https://mvnrepository.com/search?q=zookeeper
    在这里插入图片描述
    这里可以看到3.4.6使用次数较多,不过比较旧。只是研究学习,使用这个版本是足够的,如果是企业使用,采用较新的稳定版本更合适。后续会讲一下企业选择第三方库和第三方库版本的一些注意事项。
    在这里插入图片描述
  5. 官网中现有版本,
    3.4.6(本文采用的版本)
    在这里插入图片描述
    在这里插入图片描述
    3.6.1
    在这里插入图片描述

2. zookeeper解决了什么问题?

  1. ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
  2. 官网介绍,其实已经把功能和作用解释的比较清楚了,归纳起来就是可以往zookeeper写数据、读取数据、使用它做数据变化监控。稍后画图做解释。
  • 主节点选举
    在这里插入图片描述
  • 主从节点感知(状态通信)
    在这里插入图片描述
  • 配置文件同步(zookeeper可以进行少量数据存储,并且具备较高的信息同步和传输性能)
    在这里插入图片描述
  • 集群节点上下线动态感知
    在这里插入图片描述
  • 负载均衡,zookeeper如上图,也可以进行负载均衡。外部请求过来后,根据一定规则,让请求分发到集群中不同处理节点上。这样可以保证整体服务的可用。结合zookeeper的节点上下线动态感知,当集群遇到突发性流量时如天猫双十一,这时候集群节点会临时性扩充来应对爆发性请求流量。
  • 分布式锁
    在这里插入图片描述
  • 名称服务,例如请求过来后,根据负载均衡,返回相同域名对应的不同ip地址。
    在这里插入图片描述

3.zookeeper怎么使用

  1. zookeeper安装,zookeeper安装
    2.通过命令行客户端或者java api进行调用访问。这一点和java的文件IO流(文件可以使用操作系统的图形化界面和java api操作)、mysql(可以通过命令行客户端、图形化客户端如navicat、java api进行操作)、hdfs(命令行客户端、java api)。
  2. zookeeper的核心功能就是数据读写,同时具备很高的数据传输性能。zookeeper的数据是以node节点方式进行存储,跟linux的文件目录树一样,都是划分节点,。
  3. 状态和数据同步,就是对这个节点以及节点中数据进行同步。
  4. 数据读写,就是对这些节点以及节点中数据进行读写。

3.1命令行客户端

  1. 在zookeeper安装目录下的bin目录下,执行 以下shell脚本命令。注意2种方式,不管是对哪个zookeeper节点做操作,最后数据都会被同步到整个zookeeper的所有节点上去。
# 本地连接,就是在充当了zookeeper的节点服务器上,直接运行这个客户端程序
./zkCli.sh  
# 连接到指定的服务节点
bin/zkCli.sh  -server linux101:2181  

在这里插入图片描述
2. 创建一个节点,并设置数据为字符串,age18。注意路径必须是绝对路径,也就是以/目录为起始节点。
create /doit/zoo age18
在这里插入图片描述
这里创建节点,需要逐级创建,否则就会报中间节点不存在的错误,如下图
在这里插入图片描述
3. 查看文件节点 ,ls / 。ls之后跟一个节点的绝对路径,这里演示传的参数是根节点
在这里插入图片描述
ls2 /doit 这个指令返回信息会更加详细。信息详细程度类似stat指令
在这里插入图片描述

  1. 查看节点状态信息, stat /doit 。 注意stat后面跟的是一个节点的绝对路径,切记切记,不要给相对路径,就给绝对路径。这一点和hdfs中的数据访问一样,都给绝对路径。
    在这里插入图片描述
  2. 当前打开客户端的操作指令历史记录, history
    在这里插入图片描述
  3. 删除节点,delete /xixi 注意这里删除不能递归删除,也就类似linux中的,删除文件,而不能删除文件夹。递归删除需要使用后面的rmr指令
    在这里插入图片描述
    7.递归删除 rmr /xixi 这个指令类似linux的rm -r指令,递归删除文件夹,
    在这里插入图片描述
  4. 更新节点的数据,set /doit/zoo zooooo
    在这里插入图片描述
  5. 退出命令行quit,这里可以对比mysql的,也是quit。不过mysql还支持exit退出
    在这里插入图片描述

3.2 java api操作

  1. 使用idea 2020版本,创建maven项目
  2. 去maven repository 拷贝对应版本的pom配置信息
    在这里插入图片描述
  3. 粘贴配置信息,我的如下
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.6</version>
    <!-- 这一行注释掉<type>pom</type>-->
</dependency>
  1. 刷新maven,等待maven把jar包下载下来。下载速度慢的,请配置一下maven的本地仓库。具体配置可以参考我的另外一篇文章maven对比和使用简介
  2. 创建节点数据和判断节点是否存在
private static void zkCreate() {
        try {
            // 创建连接
            ZooKeeper zooKeeper = new ZooKeeper("linux100:2181", 2000, null);

            // zookeeper的操作,查看,创建,更新,删除,监控数据变化
            // 这里是创建一个节点,
            // 第一个参数是节点路径
            // 第二个是节点值
            // 第三个是节点访问权限控制,这里选择的是对所有人开放所以单词中有unsafe字样
            // 第四个是节点类型(zookeeper的节点有永久和临时,persistant就是永久,ephemeral就是短暂的,临时的节点)
            Stat exists = zooKeeper.exists("/keyboard", false);
            // 不存在才创建
            if(exists == null) {
                String result = zooKeeper.create("/keyboard", "dareu".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                System.out.println("result:" + result);
            } else {
                byte[] data = zooKeeper.getData("/keyboard", null, null);

                System.out.println("keyuboard value exists:" + new String(data));
            }

            Stat exists2 = zooKeeper.exists("/mouse", null);
            if(exists2 == null) {
                String result2 = zooKeeper.create("/mouse", "keve".getBytes(), ZooDefs.Ids.READ_ACL_UNSAFE, CreateMode.EPHEMERAL);
                System.out.println("result2:" + result2);
            }

            // 关闭连接,这里和io流、mysql、hdfs都是一样的代码套路
            zooKeeper.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  1. 获取节点数据,节点的子节点,节点的权限
private static void zkShow() {
        try {
            // 创建连接
            ZooKeeper zooKeeper = new ZooKeeper("linux100:2181", 2000, null);

            // 获取节点数据
            byte[] data = zooKeeper.getData("/keyboard", null, null);

            System.out.println("keyboard data:" + new String(data));

            // 获取节点权限
            List<ACL> acl = zooKeeper.getACL("/keyboard", new Stat());
            System.out.println("keyboard acl:" + acl);

            // 获取子节点
            List<String> children = zooKeeper.getChildren("/", null);
            System.out.println("root childrens:" + children);

            // 关闭
            zooKeeper.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

  1. 删除单节点
private static void zkDelete() {

        try {
            // 创建连接
            ZooKeeper zooKeeper = new ZooKeeper("linux100:2181", 2000, null);

            // 先创建节点和节点数据
            if(zooKeeper.exists("/rmrNode", false) == null) {
                String deletePath = zooKeeper.create("/rmrNode", "rmrData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }

            // 在一个节点下,创建多个子节点存储数据,方便后续做递归删除
            if(zooKeeper.exists("/rmrNode/deleteNode", false) == null) {
                String deletePath2 = zooKeeper.create("/rmrNode/deleteNode", "deleteData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            if(zooKeeper.exists("/rmrNode/deleteNode2", false) == null) {
                String deletePath3 = zooKeeper.create("/rmrNode/deleteNode2", "deleteData2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
            }
            if(zooKeeper.exists("/rmrNode/deleteNode3", false) == null) {
                String deletePath4 = zooKeeper.create("/rmrNode/deleteNode3", "deleteData2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
            }

            // 删除有递归删除和节点删除
            // delete 只能做节点删除,不能递归删除,否则会报错
            zooKeeper.delete("/rmrNode", -1);

            zooKeeper.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

在这里插入图片描述
需要修改为删除某个叶子节点,可以理解只能删除路径下最后的那个节点,不能删除中间节点

private static void zkDelete() {

        try {
            // 创建连接
            ZooKeeper zooKeeper = new ZooKeeper("linux100:2181", 2000, null);

            // 先创建节点和节点数据
            if(zooKeeper.exists("/rmrNode", false) == null) {
                String deletePath = zooKeeper.create("/rmrNode", "rmrData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }

            // 在一个节点下,创建多个子节点存储数据,方便后续做递归删除
            if(zooKeeper.exists("/rmrNode/deleteNode", false) == null) {
                String deletePath2 = zooKeeper.create("/rmrNode/deleteNode", "deleteData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            if(zooKeeper.exists("/rmrNode/deleteNode2", false) == null) {
                String deletePath3 = zooKeeper.create("/rmrNode/deleteNode2", "deleteData2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
            }
            if(zooKeeper.exists("/rmrNode/deleteNode3", false) == null) {
                String deletePath4 = zooKeeper.create("/rmrNode/deleteNode3", "deleteData2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
            }

            List<String> beforeDelete = zooKeeper.getChildren("/rmrNode", false);
            System.out.println("before delete:" + beforeDelete);

            // 删除有递归删除和节点删除
            // delete 只能做节点删除,不能递归删除,否则会报错
            zooKeeper.delete("/rmrNode/deleteNode", -1);

            List<String> afterDelete = zooKeeper.getChildren("/rmrNode", false);
            System.out.println("after delete:" + afterDelete);

            zooKeeper.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

可以看到那个deleteNode被删除掉了
在这里插入图片描述

  1. 递归删除节点以及所有节点下子节点
    没有提供现成API,需要跟java 的File操作一样,进行递归判断和删除
private static void zkRMR(String path) {
        // 参数异常处理
        if(path == null || path.length() == 0) {
            return;
        }

        // 不允许删除根节点
        if(path == "/") {
            return;
        }

        try {
            // 创建连接, 给出集群多个节点地址,这样就算一个节点宕机,也可以连接其他节点
            ZooKeeper zooKeeper = new ZooKeeper("linux100:2181,linux101:2181,linux102:2181", 2000, null);

            // 获取子节点
            List<String> children = zooKeeper.getChildren(path, false);
            // 检查子节点中信息,
            if(children != null && children.size() > 0) {
                for (String child : children) {
                    zkRMR(path + "/" + child);
                }
            }

            zooKeeper.delete(path, -1);

            // 关闭
            zooKeeper.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

4.zookeeper内部机制

作为高效的任务协调工具,zookeeper最为人知就是它的几个机制

4.1选举机制

  1. 选举主要是为了选出集群中的主节点leader,其他就是从节点follower
  2. Leader选举过程(以3个节点的集群为例):

zookeeper的进程在不同的工作模式下,有不同的通信端口(比如选举时,通过端口3888通信;作为leader或者follower接收客户端请求时通过端口2181;leader和follower之间通信用2888)
注意在zk集群安装的时候 会人为的为每台机器分配一个唯一的id。每个节点的配置文件中都有一个自己的独一无二的id
(1)集群初次启动时的选举流程
A、第一台机器(id=1)启动,发现没有leader,进入投票模式,投自己,并收到自己投这一票,得1票,不能当选leader(当leader的条件是,集群机器数量过半的票数)
B、第2台机器(id=2)启动,发现没有leader,进入投票模式,投自己(因为自己的id>1 收到的另一台机器的票的id)
C、第1台机器收到2的票,发现集群中有一个比自己id大的机器上线了,重新投票,投id=2
D、第2台收到的得票数为2票,过半数,自己当选,切换模式:Leader模式
E、第1台就发现有Leader存在了,自己切换模式:Follower
F、第3台启动,发现有Leader,自动进入Follower状态
如果每个节点是同时启动的zk 同时选举自己 ,同时广播 , 同时获取别人的广播,3机器会当选
(2)集群在运行过程中的选举流程
a.在某个时间点上,id=2机器挂了(leader),别的机器发现没有leader了,全体进入投票模式
b.先投自己,票中会携带(自己的id,自己的数据的版本号)
c.大家都投数据版本最新的节点做leader,如果有多个节点数据版本一样,则从中选id最大的那个作为投票目标!

  1. 从上可以看出,zookeeper集群,有3或者5台机器即可(注意是奇数台,方便选举)

4.2数据存储机制

  1. zookeeper是以节点方式进行数据存储,类似于linux的文件目录以及tree的结构。每个节点叫做znode,维护了节点的层级关系 ,不过真正的数据存储是以key:value的形式存储。
  2. zookeeper中对数据的存储采用key-value的形式,它的key有特别的格式——路径的形式!( /xixi/haha /xixi/haha2 /xixi)之所以采取这种形式,是因为zookeeper中的数据节点(znode)之间可以存在父子关系;
  3. zookeeper的数据节点分为以下类型:
    永久的znode:客户一旦创建这个znode,它就会被zookeeper一直保存,除非人为删除;java api
    的参数中的persistant
    短暂的znode:客户创建完这个znode后,如果客户断开与zookeeper的连接,则该数据马上会被zookeeper删除;java api中的ephemeral类型
    带序号的znode:客户创建一个key,zookeeper会为客户的key自动拼接一个递增的序号!可以类比mysql的主键,自增
    (客户创建的是/haha/x ,那么zookeeper真实生成的key为: /haha/x0000000000001
    如果客户继续创建 /zzz/y,那么zookeeper真实生成的key为:/zzz/y000000000002)
    另外: 永久的和短暂的,都可以跟“带序号的”特性进行组合!组合下来之后共有4种:
  • 永久不带序号
  • 永久且带序号的
  • 短暂不带序号
  • 短暂且带序号的

4.3内部通信机制

  1. 前文说过,zookeeper主要是以少量数据存储和读取,以及高速的数据同步传输为核心。
  2. 以此扩展开的节点间感知,动态上下线感知,负载均衡功能等都是基于对存储节点的创建,删除,更新的实时操作和监控为基础实现的。
  3. 服务器集群计算机之间通信,简单可以划分几个类型,一种是http协议,例如心跳包通信机制就是这种类型。HDFS的namenode和datanode之间心跳通信就是这种方式;一种是rpc,远程服务调用,速度和效率更快,zookeeper就是采用这种方式。

5.zookeeper注意事项

  1. zookeeper可以存储数据,但一般只存储少量如配置信息等数据
  2. zookeeper可以做的功能都是基于快速高效的分布式数据读写来实现的,所以zookeeper的集群高可用需要保证配置无误
  3. zookeeper可以扩展的功能较多,但只是整个java 生态体系中方案的一种,根据业务需要自行选择。适合自己的才是最好的,并不是一定要选某一种方案。
  4. zookeeper的数据存储可以有永久性和临时性存储,可以进行权限划分。基于此可以做更多功能扩展。
  5. zookeeper没有专门的日志目录,出现问题时,日志会出现在安装目录bin目录下,以.out格式显示,如图
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值