image.png

1. ZK 简介

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


2. ZK 数据模型

2.1 模型结构

image.png

2.2 模型的特点

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

3. 节点的分类

3.1 持久节点(PERSISTENT)

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

3.2 持久顺序节点(PERSISTENT_SEQUENTIAL)

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

3.3 临时节点(EPHEMERAL)

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

3.4 临时顺序节点(EPHEMERAL_SEQUENTIAL)

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


4. 安装

4.1 Linux 安装

  • 安装 JDK 并下载 ZK 安装包

下载链接:https://dlcdn.apache.org/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz

  • 解压缩 ZK

    1. tar -vxf apache-zookeeper-3.6.3-bin.tar
  • 移动安装目录

    1. sudo mv apache-zookeeper-3.6.3-bin /usr/loacl
  • 配置 zoo.cfg 配置文件,修改 ZK 的 conf 目录下的 zoo_simple.cfg,修改完后,重命名为 zoo.cfg

改名: cp zoo_simple.cfg zoo.cfg

  1. tickTime=2000
  2. initLimit=10
  3. syncLimit=5
  4. dataDir=/usr/local/apache-zookeeper-3.6.3-bin/zkdata
  5. clientPort=2181
  • 启动 ZK,进入到 bin 目录

    1. ./zkServer.sh start ../conf/zoo.cfg
  • 连接到 ZK 客户端 ```bash ./zkCli.sh -server ip:2181

查看节点 [zookeeper] 只有唯一一个节点

ls /

  1. 注意:可以通过 ./zkCli.sh help 查看客户端所有可以执行的命令。
  2. <a name="OBiDP"></a>
  3. ## 4.2 Docker 安装
  4. - 获取 ZK 镜像
  5. ```powershell
  6. docker pull zookeeper:3.6.3
  • 启动 ZK 服务

    1. docker run --name zk -p 2181:2181 -d zookeeper:3.6.3

    4.3 配置文件详细说明

  • tickTime:集群节点之间的心跳间隔(默认时间为 2s)

  • initLimit:初始化集群时集群节点同步超时次数为 10,默认的超时时间为 20 s。
  • syncLimit:集群在运行过程中同步数据发送请求和认证次数默认为 5次,超时时间为 10 s。
  • dataDir:默认数据存储位置。
  • clientPort:ZK 启动占用的端口。
  • autopurge.purgeInterval:是否开启快照存储。如果为 0,则不不开启快照存储。默认单位为小时,只要一个小时内业务次数超过 3 次,那么就会保存这个快照。

5. 客户端基本命令

  • 查看根目录节点

    1. ls /
  • 查看指定目录下的子节点

    1. ls path
  • 创建一个节点,并给节点绑定数据(默认是持久性节点) ```markdown

  1. 创建持久性节点(默认是持久节点) create path data

  2. 创建持久性顺序节点 create -s path data

  3. 创建临时性节点(注意:临时节点下面不能有子节点) create -e path data

  4. 创建临时性持久节点(注意:临时节点下面不能有子节点) create -e -s path data ```

  • 获取节点数据

    1. get path
  • 修改节点数据

    1. set path data
  • 查看节点状态

    1. stat path
  • 查看节点下孩子和当前节点的状态

    1. ls2 path
  • 查看操作历史

    1. history
  • 删除节点(注意:删除节点不能含有子节点)

    1. delete path
  • 递归删除节点(注意:会将当前节点下的所有节点删除)

    1. rmr path
  • 退出当前会话(会话失效)

    1. quit

6. 节点监听机制(watch)

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

  • 监听节点目录的变化

    1. ls /path true
  • 监听节点数据的变化

    1. get /path true

    注意⚠️:

  • 在新版本的 zookeeper 中,我们可以使用 ls path 或者 get path 添加参数 -w 的方式进行实现一次性监听。

  • 使用 addWatch 命令能持久监听 path 和 data 的变化。

7. Java 操作 ZK

7.1 创建项目引入依赖

  1. <dependency>
  2. <groupId>com.101tec</groupId>
  3. <artifactId>zkclient</artifactId>
  4. <version>0.10</version>
  5. </dependency>

7.2 ZK 相关 API

  • 创建节点

    1. /**
    2. * 用于测试: zk 创建节点
    3. */
    4. @Test
    5. public void testCreateNode() {
    6. // 1. 持久节点 返回创建的节点名称
    7. String persistentNodeName = zkClient.create("/node1", "xiaoliao", CreateMode.PERSISTENT);
    8. System.out.println("persistentNodeName = " + persistentNodeName);
    9. zkClient.createPersistent("/node2", "baiyi");
    10. // 2. 创建持久顺序节点
    11. zkClient.create("/node1/node2", "baiyi", CreateMode.PERSISTENT_SEQUENTIAL);
    12. zkClient.createPersistentSequential("/node1/node3", "baiyi");
    13. // 3. 创建临时节点
    14. zkClient.create("/node1/e1", "test1", CreateMode.EPHEMERAL);
    15. zkClient.createEphemeral("/node1/e2", "test");
    16. // 4. 创建临时顺序节点
    17. zkClient.create("/node1/es1", "es1", CreateMode.EPHEMERAL_SEQUENTIAL);
    18. zkClient.createEphemeralSequential("/node1/es2", "es2");
    19. }
  • 删除节点

    1. /**
    2. * 用于测试: zk 删除节点
    3. */
    4. @Test
    5. public void testDeleteNode() {
    6. // 注意:使用该方法进行删除节点时,节点下不能有子节点
    7. boolean delete = zkClient.delete("/node1");
    8. System.out.println("delete = " + delete);
    9. // 注意:使用该方法可以递归进行删除节点
    10. boolean delete1 = zkClient.deleteRecursive("/node1");
    11. System.out.println("delete1 = " + delete1);
    12. }
  • 查看节点的子节点

    1. /**
    2. * 用于测试: zk 查询当前节点下的所有子节点
    3. */
    4. @Test
    5. public void testFindNodes() {
    6. // 获取指定路径的节点信息 返回值为当前节点的所有子节点
    7. List<String> children = zkClient.getChildren("/");
    8. children.forEach(System.out::println);
    9. }
  • 获取节点的数据

    1. /**
    2. * 用于测试: 查看 zk 某节点的数据
    3. * 注意:通过 java 客户端操作必须保证节点存储的数据和获取节点时序列化方式必须要一致
    4. */
    5. @Test
    6. public void testFindNodeData() {
    7. Object data = zkClient.readData("/node2");
    8. System.out.println("data = " + data);
    9. }

    注意:如果出现 org.I0Itec.zkclient.exception.ZkMarshallingError: java.io.StreamCorruptedException: invalid stream header: 7869616F 这个异常,那就是创建节点时的序列化和获取节点数据的序列化方式不一致导致的。

  • 查看节点状态信息

    1. /**
    2. * 用于测试: 查看 zk 节点状态信息
    3. */
    4. @Test
    5. public void testFindNodeDataAndStat() {
    6. Stat stat = new Stat();
    7. Object data = zkClient.readData("/node2", stat);
    8. System.out.println("data = " + data);
    9. System.out.println("stat = " + stat);
    10. System.out.println("stat.version = " + stat.getVersion());
    11. }
  • 修改节点的数据

    1. /**
    2. * 用于测试: zk 修改数据
    3. */
    4. @Test
    5. public void testUpdateNodeData() {
    6. zkClient.writeData("/node2", new User("baiyi", 18, new Date()));
    7. User user = zkClient.readData("/node2");
    8. System.out.println("user = " + user);
    9. }
  • 监听节点数据变化

    1. /**
    2. * 用于测试: 检测 zK 节点数据变化
    3. */
    4. @Test
    5. public void testWatchNodeDataOnChange() throws IOException {
    6. zkClient.subscribeDataChanges("/node2", new IZkDataListener() {
    7. /**
    8. * 当数据发生变化时,触发当前方法进行相关业务操作
    9. * @param s 节点名称
    10. * @param o 修改后的节点数据
    11. * @throws Exception
    12. */
    13. @Override
    14. public void handleDataChange(String s, Object o) throws Exception {
    15. System.out.println("update nodeName = " + s);
    16. System.out.println("update data = " + o);
    17. }
    18. /**
    19. * 当节点被删除时,触发当前方法进行相关业务操作
    20. * @param s 节点名称
    21. * @throws Exception
    22. */
    23. @Override
    24. public void handleDataDeleted(String s) throws Exception {
    25. System.out.println("update nodeName = " + s);
    26. }
    27. });
    28. // 让程序进入阻塞状态,让当前方法不会退出
    29. System.in.read();
    30. }

    注意:

  1. 使用 Java 进行监听,这是永久操作的,不会像 终端操作 一样,只能监听一次。
  2. 使用 Java 程序进程监听,只能监听到由于 Java 程序进行修改数据的变化,无法监听到 终端修改数据的变化。
  • 监听节点的目录变化
    1. /**
    2. * 用于测试: 监控 zk 节点发生变化
    3. */
    4. @Test
    5. public void testWatchNodeChildrenOnChange() throws IOException {
    6. zkClient.subscribeChildChanges("/node1", (s, list) -> {
    7. System.out.println("this parent nodeName = " + s);
    8. for (String childrenName : list) {
    9. System.out.println("childrenName = " + childrenName);
    10. }
    11. });
    12. System.in.read();
    13. }

8. ZK 集群

8.1 集群(Cluster)

集群概念:集合同一种软件服务的多个节点同时提供服务。
集群解决了什么问题?

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

    8.2 集群架构

    image.png

  • leader:领导,集群中的主节点。

  • following:仲裁节点,除了主节点以外的子节点。

ZK 集群保证数据一致性是通过 ZK 底层的 ZAB 原子广播协议。每个节点都不对数据进行确认,直至所有节点都成功写完数据之后,才会返回 数据写入完成。如果期间有一个节点出现了问题,那么直接就提示 数据写入失败。节点之间的通信是通过 心跳💗 的方式进行。

8.3 集群搭建

这里为了创建方便,只使用了一台机器进行创建三个 ZK 节点进行搭建 ZK 集群。

  • 创建三个 dataDir 目录

    1. mkdir zkdata1 zkdata2 zkdata3
  • 分别在这三个 dataDir 目录下放置 myid 文件

    1. touch ./zkdata1/myid

    myid 的内容是 服务器 表示为 1 2 3 节点,内容为 int 数字。

    1. echo "1" >> zkdata1/myid
  • 在 /conf 目录下创建三个 zk 配置文件,分别是 zoo1.cfg, zoo2.cfg, zoo3.cfg ```markdown

  • zoo1.cfg

    1. tickTime=2000

    initLimit=10 syncLimit=5 dataDir=/usr/zookeeper/zkdata1 clientPort=2181 server.1=192.168.0.220:2888:3888 server.2=192.168.0.221:2888:3888 server.3=192.168.0.222:2888:3888

  • zoo2.cfg

    1. tickTime=2000

    initLimit=10 syncLimit=5 dataDir=/usr/zookeeper/zkdata2 clientPort=2181 server.1=192.168.0.220:2888:3888 server.2=192.168.0.221:2888:3888 server.3=192.168.0.222:2888:3888

  • zoo3.cfg

    1. tickTime=2000

    initLimit=10 syncLimit=5 dataDir=/usr/zookeeper/zkdata3 clientPort=2181 server.1=192.168.0.220:2888:3888 server.2=192.168.0.221:2888:3888 server.3=192.168.0.222:2888:3888 ```


9. Java 操作 ZK 集群

这个操作集群和操作单个节点都是异曲同工的事情。只需要对某一个节点进行操作即可。其他节点会对其数据进行同步。
但是建议还是把所有的 ZK 集群的节点 IP 都写上。