ZooKeeper

2021年02月12日 21:30 · 阅读(171) ·

开发环境

名称 版本 描述
操作系统 Windows 10 X64
CentOS CentOS Linux release 7.8.2003 (Core)
Linux 3.10.0-1127.el7.x86_64
JDK JDK1.8(jdk-8u151-windows-x64)
Zookeeper 3.4.11 运行在虚拟机上,IP 地址:192.168.114.128
IntelliJ IDEA IntelliJ IDEA 2018.3
Maven 3.6.0 .

安装 JDK1.8

Zookeeper 依赖 jdk,我们需要先安装 jdk

安装 JDK1.8

nc 命令的安装

在安装 CentOS 系统时,如果选择了图形化界面,默认是安装了 nc 命令的。

如果选择了最小化安装,则不会安装 nc 命令。

1.CentOS6.8 下面 nc 命令的安装

(1)路径:/media/CentOS_6.8_Final/Packages

(2)命令:rpm -ivh nc-1.84-24.el6.x86_64.rpm

2.CentOS7 下面 nc 命令的安装

(1)路径:/run/media/root/CentOS 7 x86_64/Packages

(2)命令:rpm -ivh nmap-ncat-6.40-7.el7.x86_64

Zookeeper 简介

ZooKeeper 顾名思意:动物园管理员

它是拿来管大象(Hadoop)、蜜蜂(Hive)、小猪(Pig)的管理员, Apache Hbase 和 Apache Solr 以及阿里的 Dubbo 等项目中都采用到了 Zookeeper。

一句话:ZooKeeper 是一个分布式协调技术、高性能的,开源的分布式系统的协调(Coordination)服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用程序一致性和分布式协调技术服务的软件

设计模式来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应,从而实现集群中类似 Master/Slave 管理模式。

一句话:zookeeper = 类似 unix 文件系统 + 通知机制 + Znode节点

作用:服务注册 + 分布式系统的一致性通知协调

Zookeeper 下载

下载地址:https://zookeeper.apache.org/

Zookeeper 特性

1.统一命名服务(Name Service 如 Dubbo 服务注册中心)

Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是阿里巴巴 SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

在 Dubbo 实现中:
(1)服务提供者在启动的时候,向 ZK 上的指定节点 /dubbo/${serviceName}/providers 目录下写入自己的 URL 地址,这个操作就完成了服务的发布。

(2)服务消费者启动的时候,订阅 /dubbo/${serviceName}/providers 目录下的提供者 URL 地址, 并向 /dubbo/${serviceName} /consumers 目录下写入自己的 URL 地址。

(3)注意,所有向 ZK 上注册的地址都是临时节点,这样就能够保证服务提供者和消费者能够自动感应资源的变化。另外,Dubbo 还有针对服务粒度的监控,方法是订阅 /dubbo/${serviceName} 目录下所有提供者和消费者的信息。

2.配置管理(Configuration Management 如淘宝开源配置管理框架 Diamond)

在大型的分布式系统中,为了服务海量的请求,同一个应用常常需要多个实例。

如果存在配置更新的需求,常常需要逐台更新,给运维增加了很大的负担同时带来一定的风险(配置会存在不一致的窗口期,或者个别节点忘记更新)。

zookeeper 可以用来做集中的配置管理,存储在 zookeeper 集群中的配置,如果发生变更会主动推送到连接配置中心的应用节点,实现一处更新处处更新的效果

现在把这些配置全部放到 zookeeper 上去,保存在 Zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中就好。

3.Java 操作 API

Zookeeper 安装配置

Linux 下安装

1.官网下载安装包,本次版本 zookeeper-3.4.11.tar.gz

2.进入 /opt 目录,新建 mkdir myzookeeper 目录

3.拷贝 zookeeper-3.4.11.tar.gz/opt/myzookeeper 目录下并解压

  1. tar -zxvf zookeeper-3.4.11.tar.gz

4.解压后删除 zookeeper-3.4.11.tar.gz

  1. rm -rvf zookeeper-3.4.11.tar.gz

5.进入 conf 文件夹,拷贝 zoo_sample.cfg 改为 zoo.cfg

  1. cd /opt/myzookeeper/zookeeper-3.4.11/conf
  2. cp zoo_sample.cfg zoo.cfg

6.zoo.cfg 配置文件解读

配置项 描述
tickTime tickTime:通信心跳数,Zookeeper 服务器心跳时间,单位毫秒 。

Zookeeper 使用的基本时间, 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳,时间单位为毫秒。

它用于心跳机制,并且设置最小的 session 超时时间为两倍心跳时间(session 的最小超时时间是 2 * tickTime)
initLimit initLimit 这个配置项是用来配置 Zookeeper 接收 Follower 客户端。

(这里所说的客户端不是用户链接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 leader 的 Follower 服务器,Follower 在启动过程中,会从 Leader 同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader 允许 Follower 在 initLimit时间内完成这个工作)

初始化连接是最长能忍受多少个心跳的时间间隔数。

当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端返回的信息,那么表明这个客户端连接失败。总的时间长度就是 10 * 2000 = 20 秒
syncLimit syncLimit:LF 同步通信时限

集群中 Leader 与 Follower 之间的最大响应时间单位。

在运行过程中,Leader 负责与 ZK 集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。

假如响应超过 syncLimit * tickTime(假设 syncLimit = 5 ,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是5 * 2000 = 10 秒),Leader 认为 Follwer 死掉,从服务器列表中删除 Follwer。

在运行过程中,Leader 负责与 ZK 集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。

如果 L 发出心跳包在 syncLimit 之后,还没有从 F 那收到响应,那么就认为这个 F 已经不在线了。
dataDir dataDir:数据文件目录 + 数据持久化路径。

保存内存数据库快照信息的位置,如果没有其他说明,更新的事务日志也保存到数据库。
clientPort clientPort:客户端连接端口。

监听客户端连接的端口。

Zookeeper 服务开启 + 停止

(1)进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin

(2)启动 ./zkServer.sh start

  1. [root@luoma bin]# ./zkServer.sh start
  2. ZooKeeper JMX enabled by default
  3. Using config: /opt/myzookeeper/zookeeper-3.4.11/bin/../conf/zoo.cfg
  4. Starting zookeeper ... STARTED

使用 nc 命令查看是否启动

  1. echo ruok | nc 127.0.0.1 2181

出现了 imok,表示已经成功启动

  1. [root@luoma bin]# echo ruok | nc 127.0.0.1 2181
  2. imok[root@luoma bin]#

(3)停止 ./zkServer.sh stop

  1. [root@luoma bin]# ./zkServer.sh stop
  2. ZooKeeper JMX enabled by default
  3. Using config: /opt/myzookeeper/zookeeper-3.4.11/bin/../conf/zoo.cfg
  4. Stopping zookeeper ... STOPPED

Zookeeper 客户端连接

1.连接

进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin

连接 ./zkCli.sh

2.查看+获得 zookeeper 服务器上的数据存储信息

  1. #查看根目录的节点内容
  2. ls /
  3. #查看根目录 zookeeper 的节点内容
  4. ls /zookeeper
  5. #查看目录 /zookeeper/quota 的节点内容
  6. ls /zookeeper/quota
  7. #获取 /zookeeper 节点内容
  8. get /zookeeper
  1. [zk: localhost:2181(CONNECTED) 0] ls /
  2. [zookeeper]
  3. [zk: localhost:2181(CONNECTED) 1] ls /zookeeper
  4. [quota]
  5. [zk: localhost:2181(CONNECTED) 2] ls /zookeeper/quota
  6. []
  7. [zk: localhost:2181(CONNECTED) 3] get /zookeeper
  8. cZxid = 0x0
  9. ctime = Thu Jan 01 08:00:00 CST 1970
  10. mZxid = 0x0
  11. mtime = Thu Jan 01 08:00:00 CST 1970
  12. pZxid = 0x0
  13. cversion = -1
  14. dataVersion = 0
  15. aclVersion = 0
  16. ephemeralOwner = 0x0
  17. dataLength = 0
  18. numChildren = 1

3.退出客户端连接 quit

常用命令

一句话:和 redis 的 KV 键值对类似,只不过 key 变成了一个路径节点值,v 就是 data

Zookeeper 表现为一个分层的文件系统目录树结构。

不同于文件系统之处在于:zk 节点可以有自己的数据,而 unix 文件系统中的目录节点只有子节点

一个节点对应一个应用/服务,节点存储的数据就是应用需要的配置信息。

命令 描述
help 查看帮助信息
ls 使用 ls 命令来查看当前 znode 中所包含的内容。
ls2 查看当前节点数据并能看到更新次数等数据
stat 查看节点状态
set set 节点路径名称 节点值
get 获得节点的值
create 普通创建

-s 含有序列

-e 临时(重启或者超时消失)
delete 删除无子节点的目录
rmr 递归删除

四字命令

zookeeper 支持某些特定的四字命令,他们大多是用来查询 ZK 服务的当前状态及相关信息的,通过 telnet 或 nc 向 zookeeper 提交相应命令,如:echo ruok | nc 127.0.0.1 2181

  1. [root@luoma bin]# echo ruok | nc 127.0.0.1 2181
  2. imok[root@luoma bin]#

运行公式:echo 四字命令 | nc 主机IP zookeeper端口

命令 描述
ruok ruok:测试服务是否处于正确状态。如果确实如此,那么服务返回“imok”,否则不做任何相应
stat stat:输出关于性能和连接的客户端的列表
conf conf:输出相关服务配置的详细信息
cons cons:列出所有连接到服务器的客户端的完全的连接 /会话的详细信息。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息
dump dump:列出未经处理的会话和临时节点
envi envi:输出关于服务环境的详细信息(区别于 conf 命令)
reqs reqs:列出未经处理的请求
wchs wchs:列出服务器 watch 的详细信息
wchc wchc:通过 session 列出服务器 watch 的详细信息,它的输出是一个与 watch 相关的会话的列表
wchp wchp:通过路径列出服务器 watch 的详细信息。它输出一个与 session 相关的路径

文件系统

Zookeeper 维护一个类似文件系统的数据结构

所使用的数据模型风格很像文件系统的目录树结构,简单来说,有点类似 Windows 中注册表的结构。

1.有名称。
2.有树节点。
3.有 Key(键)/Value(值)对的关系,

可以看做一个树形结构的数据库,分布在不同的机器上做名称管理。

ZNode节点

ZooKeeper 数据模型的结构与 Unix 文件系统很类似,整体上可以看作是一棵树,每个节点称做一个 ZNode。

很显然 Zookeeper 集群自身维护了一套数据结构。这个存储结构是一个树形结构,其上的每一个节点,我们称之为”ZNode”,每一个 znode 默认能够存储 1MB 的数据,每个 ZNode 都可以通过其路径唯一标识。

ZNode 的数据模型

ZNode 维护了一个 stat 结构,这个 stat 包含数据变化的版本号、访问控制列表变化、还有时间戳。版本号和时间戳一起,可让 Zookeeper 验证缓存和协调更新。每次 ZNode 的数据发生了变化,版本号就增加。

例如,无论何时客户端检索数据,它也一起检索数据的版本号。并且当客户端执行更新或删除时,客户端必须提供他正在改变的 ZNode 的版本号。如果它提供的版本号和真实的数据版本号不一致,更新将会失败。

ZooKeeper 的 Stat 结构体

属性 描述
czxid 引起这个 znode 创建的 zxid,创建节点的事务的 zxid(ZooKeeper Transaction Id)

每次修改 ZooKeeper 状态都会收到一个 zxid 形式的时间戳,也就是 ZooKeeper事务 ID。

事务 ID 是 ZooKeeper 中所有修改总的次序。每个修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么 zxid1 在 zxid2 之前发生。
ctime znode 被创建的毫秒数(从 1970 年开始)
mzxid znode 最后更新的 zxid
mtime znode 最后修改的毫秒数(从 1970 年开始)
pZxid znode 最后更新的子节点 zxid
cversion znode 子节点变化号,znode 子节点修改次数
dataversion znode 数据变化号
aclVersion znode 访问控制列表的变化号
ephemeralOwner 如果是临时节点,这个是 znode 拥有者的 session id。

如果不是临时节点则是 0。
dataLength znode 的数据长度
numChildren znode 子节点数量

总结:zookeeper 内部维护了一套类似 UNIX 的树形数据结构:由 znode 构成的集合,znode 的集合又是一个树形结构,每一个 znode 又有很多属性进行描述。Znode = path + data + Stat

ZNode 中的存在类型

类型 描述
PERSISTENT-持久化目录节点 客户端与 zookeeper 断开连接后,该节点依旧存在
PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点 客户端与 zookeeper 断开连接后,该节点依旧存在,只是 Zookeeper 给该节点名称进行顺序编号
EPHEMERAL-临时目录节点 客户端与 zookeeper 断开连接后,该节点被删除
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点 客户端与 zookeeper 断开连接后,该节点被删除,只是 Zookeeper 给该节点名称进行顺序编号

持久/临时

znode 是由客户端创建的,它和创建它的客户端的内在联系,决定了它的存在性。

类型 描述
PERSISTENT-持久化节点 创建这个节点的客户端在与 zookeeper 服务的连接断开后,这个节点也不会被删除(除非您使用 API 强制删除)
PERSISTENT_SEQUENTIAL-持久化顺序编号节点 当客户端请求创建这个节点A后,zookeeper 会根据 parent-znode 的 zxid 状态,为这个 A 节点编写一个全目录唯一的编号(这个编号只会一直增长)。

当客户端与 zookeeper 服务的连接断开后,这个节点也不会被删除。
EPHEMERAL-临时目录节点 创建这个节点的客户端在与 zookeeper 服务的连接断开后,这个节点(还有涉及到的子节点)就会被删除。
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点 当客户端请求创建这个节点 A 后,zookeeper 会根据 parent-znode 的 zxid 状态,为这个 A 节点编写一个全目录唯一的编号(这个编号只会一直增长)。当创建这个节点的客户端与 zookeeper 服务的连接断开后,这个节点被删除

另外,无论是 EPHEMERAL 还是 EPHEMERAL_SEQUENTIAL 节点类型,在 zookeeper 的client异常终止后,节点也会被删除。

Java 客户端操作

新建 Maven 项目 ZooKeeperDemo

如果不会可以参考 IntelliJ IDEA 2018.3 + Maven 3.6.0 创建项目

项目结构

Maven 工程和配置 POM

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>ZooKeeperDemo</groupId>
  7. <artifactId>ZooKeeperDemo</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <properties>
  10. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  11. </properties>
  12. <dependencies>
  13. <!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
  14. <dependency>
  15. <groupId>com.101tec</groupId>
  16. <artifactId>zkclient</artifactId>
  17. <version>0.10</version>
  18. </dependency>
  19. <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
  20. <dependency>
  21. <groupId>org.apache.zookeeper</groupId>
  22. <artifactId>zookeeper</artifactId>
  23. <version>3.4.9</version>
  24. </dependency>
  25. <dependency>
  26. <groupId>log4j</groupId>
  27. <artifactId>log4j</artifactId>
  28. <version>1.2.17</version>
  29. </dependency>
  30. <dependency>
  31. <groupId>junit</groupId>
  32. <artifactId>junit</artifactId>
  33. <version>4.9</version>
  34. <scope>test</scope>
  35. </dependency>
  36. </dependencies>
  37. </project>

log4j.xml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  3. <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  4. <appender name="log.console" class="org.apache.log4j.ConsoleAppender">
  5. <layout class="org.apache.log4j.PatternLayout">
  6. <param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %5p (%C{1}:%M) - %m%n" />
  7. </layout>
  8. <!--
  9. <filter class="org.apache.log4j.varia.LevelRangeFilter">
  10. <param name="levelMin" value="debug" />
  11. <param name="levelMax" value="warn" />
  12. <param name="AcceptOnMatch" value="true" />
  13. </filter>-->
  14. </appender>
  15. <appender name="log.file" class="org.apache.log4j.DailyRollingFileAppender">
  16. <param name="File" value="D:\\Java\\2021\\Log\\Log4XML.log" />
  17. <param name="Append" value="true" />
  18. <param name="DatePattern" value="'.'yyyy-MM-dd" />
  19. <layout class="org.apache.log4j.PatternLayout">
  20. <param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %5p (%C{1}:%M) - %m%n" />
  21. </layout>
  22. </appender>
  23. <root>
  24. <level value="info" />
  25. <appender-ref ref="log.console" />
  26. <appender-ref ref="log.file" />
  27. </root>
  28. </log4j:configuration>

ZKHelper.java

  1. package com.luoma;
  2. import org.apache.zookeeper.*;
  3. import org.apache.zookeeper.data.Stat;
  4. import java.io.IOException;
  5. public class ZKHelper {
  6. private static final String CONNECTSTRING = "192.168.114.128:2181";
  7. private static final int SESSION_TIMEOUT = 20 * 1000;
  8. /**
  9. * 启动连接
  10. * @return
  11. * @throws IOException
  12. */
  13. public ZooKeeper startZK() throws IOException {
  14. return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
  15. public void process(WatchedEvent watchedEvent) {
  16. }
  17. });
  18. }
  19. /**
  20. * 关闭连接
  21. * @param zk
  22. * @throws InterruptedException
  23. */
  24. public void stopZK(ZooKeeper zk) throws InterruptedException {
  25. if (zk != null){
  26. zk.close();
  27. }
  28. }
  29. /**
  30. * 新建一个 ZNode 节点
  31. * @param zk
  32. * @param path
  33. * @param data
  34. * @throws KeeperException
  35. * @throws InterruptedException
  36. */
  37. public void createZNode(ZooKeeper zk,String path,String data) throws KeeperException, InterruptedException {
  38. zk.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  39. }
  40. /**
  41. * 获取节点的值
  42. * @param zk
  43. * @param path
  44. * @return
  45. * @throws KeeperException
  46. * @throws InterruptedException
  47. */
  48. public String getZNode(ZooKeeper zk,String path) throws KeeperException, InterruptedException {
  49. String result = "";
  50. byte[] data = zk.getData(path,false,new Stat());
  51. result = new String(data);
  52. return result;
  53. }
  54. }

HelloZK.java

  1. package com.luoma;
  2. import org.apache.zookeeper.ZooKeeper;
  3. public class HelloZK {
  4. private static final String PATH ="/NodeLuomaRoot";
  5. public static void main(String[] args) {
  6. try{
  7. ZKHelper zkHelper = new ZKHelper();
  8. ZooKeeper zk = zkHelper.startZK();
  9. if (zk.exists(PATH,false) == null){
  10. zkHelper.createZNode(zk,PATH,"NodeLuomaRoot-DATA");
  11. String znode = zkHelper.getZNode(zk,PATH);
  12. System.out.println("zNode = "+ znode);
  13. }else{
  14. System.out.println("this zNode is created");
  15. }
  16. zkHelper.stopZK(zk);
  17. }catch (Exception e){
  18. }
  19. }
  20. }

测试结果

1.运行 HelloZK main 方法,控制台部分信息如下

  1. 15:05:06,216 INFO (ClientCnxn$SendThread:logStartConnect) - Opening socket connection to server 192.168.114.128/192.168.114.128:2181. Will not attempt to authenticate using SASL (unknown error)
  2. 15:05:06,218 INFO (ClientCnxn$SendThread:primeConnection) - Socket connection established to 192.168.114.128/192.168.114.128:2181, initiating session
  3. 15:05:06,354 INFO (ClientCnxn$SendThread:onConnected) - Session establishment complete on server 192.168.114.128/192.168.114.128:2181, sessionid = 0x1000077d8b50004, negotiated timeout = 20000
  4. zNode = NodeLuomaRoot-DATA
  5. 15:05:07,185 INFO (ZooKeeper:close) - Session: 0x1000077d8b50004 closed
  6. 断开与目标 VM 的连接,地址:'127.0.0.1:28390', transport: 'socket'

2.进入 Zookeeper 客户端

进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin

连接 ./zkCli.sh

(1)查看根目录数据 ls /

  1. [zk: localhost:2181(CONNECTED) 2] ls /
  2. [zookeeper, NodeLuomaRoot]

(2)获取 NodeLuomaRoot 节点数据,get /NodeLuomaRoot

  1. [zk: localhost:2181(CONNECTED) 0] get /NodeLuomaRoot
  2. NodeLuomaRoot-DATA
  3. cZxid = 0x9
  4. ctime = Sat Feb 13 15:04:58 CST 2021
  5. mZxid = 0x9
  6. mtime = Sat Feb 13 15:04:58 CST 2021
  7. pZxid = 0x9
  8. cversion = 0
  9. dataVersion = 0
  10. aclVersion = 0
  11. ephemeralOwner = 0x0
  12. dataLength = 18
  13. numChildren = 0

通知机制

通知机制:客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper 会通知客户端。

ZooKeeper 支持 watch(观察)的概念。

客户端可以在每个 znode 结点上设置一个观察。如果被观察服务端的 znode 结点有变更,那么 watch 就会被触发,这个 watch 所属的客户端将接收到一个通知包被告知结点已经发生变化,把相应的事件通知给设置过 Watcher 的 Client 端。

Zookeeper 里的所有读取操作:getData(),getChildren() 和 exists() 都有设置 watch 的选项

一句话:异步回调的触发机制

watch 事件理解

名称 描述
1.一次触发 当数据有了变化时 zkserver 向客户端发送一个 watch,它是一次性的动作,即触发一次就不再有效,类似一次性纸杯。

只监控一次。

如果想继续 Watch 的话,需要客户端重新设置 Watcher。因此如果你得到一个 watch 事件且想在将来的变化得到通知,必须新设置另一个 watch
2.发往客户端 Watches 是异步发往客户端的,Zookeeper 提供一个顺序保证:在看到 watch 事件之前绝不会看到变化,这样不同客户端看到的是一致性的顺序。

在(导致观察事件被触发的)修改操作的成功返回码到达客户端之前,事件可能在去往客户端的路上,但是可能不会到达客户端。

观察事件是异步地发送给观察者(客户端)的。

ZooKeeper 会保证次序:在收到观察事件之前,客户端不会看到已经为之设置观察的节点的改动。

网络延迟或者其他因素可能会让不同的客户端在不同的时间收到观察事件和更新操作的返回码。

这里的要点是:不同客户端看到的事情都有一致的次序。
3.为数据设置 watch 节点有不同的改动方式。

可以认为 ZooKeeper 维护两个观察列表:数据观察和子节点观察。

getData()和exists()设置数据观察。

getChildren()设置子节点观察。

此外,还可以认为不同的返回数据有不同的观察。

getData()和exists()返回节点的数据,而getChildren()返回子节点列表。

所以,setData()将为znode触发数据观察。

成功的 create() 将为新创建的节点触发数据观察,为其父节点触发子节点观察。

成功的 delete() 将会为被删除的节点触发数据观察以及子节点观察(因为节点不能再有子节点了),为其父节点触发子节点观察。


观察维护在客户端连接到的 ZooKeeper 服务器中。这让观察的设置、维护和分发是轻量级的。

客户端连接到新的服务器时,所有会话事件将被触发。

同服务器断开连接期间不会收到观察。客户端重新连接 时,如果需要,先前已经注册的观察将被重新注册和触发。

通常这都是透明的。有一种情况下观察事件将丢失:对还没有创建的节点设置存在观察,而在断开连接期 间创建节点,然后删除。
4.时序性和一致性 Watches 是在 client 连接到 Zookeeper 服务端的本地维护,这可让 watches 成为轻量的,可维护的和派发的。

当一个 client 连接到新 server,watch 将会触发任何 session 事件,断开连接后不能接收到。

当客户端重连,先前注册的 watches 将会被重新注册并触发。


关于 watches,Zookeeper 维护这些保证:
(1)Watches 和其他事件、watches 和异步恢复都是有序的。Zookeeper 客户端保证每件事都是有序派发。
(2)客户端在看到新数据之前先看到 watch 事件。
(3)对应更新顺序的 watches 事件顺序由 Zookeeper 服务所见。

一次触发

ZKHelper

  1. package com.luoma;
  2. import org.apache.zookeeper.*;
  3. import org.apache.zookeeper.data.Stat;
  4. import java.io.IOException;
  5. public class ZKHelper {
  6. //定义常量
  7. private static final String CONNECTSTRING = "192.168.114.128:2181";
  8. private static final int SESSION_TIMEOUT = 50 * 1000;
  9. //定义实例变量
  10. private ZooKeeper zk = null;
  11. //setter---getter
  12. public ZooKeeper getZk()
  13. {
  14. return zk;
  15. }
  16. public void setZk(ZooKeeper zk)
  17. {
  18. this.zk = zk;
  19. }
  20. /**
  21. * 启动连接
  22. * @return
  23. * @throws IOException
  24. */
  25. public ZooKeeper startZK() throws IOException {
  26. return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
  27. public void process(WatchedEvent watchedEvent) {
  28. }
  29. });
  30. }
  31. /**
  32. * 关闭连接
  33. * @param zk
  34. * @throws InterruptedException
  35. */
  36. public void stopZK() throws InterruptedException {
  37. if (zk != null){
  38. zk.close();
  39. }
  40. }
  41. /**
  42. * 新建一个 ZNode 节点
  43. * @param zk
  44. * @param path
  45. * @param data
  46. * @throws KeeperException
  47. * @throws InterruptedException
  48. */
  49. public void createZNode(String path,String data) throws KeeperException, InterruptedException {
  50. zk.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  51. }
  52. /**
  53. * 获取节点的值
  54. * @param path
  55. * @return
  56. * @throws KeeperException
  57. * @throws InterruptedException
  58. */
  59. public String getZNode(String path) throws KeeperException, InterruptedException
  60. {
  61. byte[] byteArray = zk.getData(path,new Watcher() {
  62. @Override
  63. public void process(WatchedEvent event)
  64. {
  65. try
  66. {
  67. triggerValue(path);
  68. }catch (KeeperException | InterruptedException e) {
  69. e.printStackTrace();
  70. }
  71. }
  72. }, new Stat());
  73. return new String(byteArray);
  74. }
  75. /**
  76. * 触发修改
  77. * @param path
  78. * @return
  79. * @throws KeeperException
  80. * @throws InterruptedException
  81. */
  82. public String triggerValue(String path) throws KeeperException, InterruptedException
  83. {
  84. byte[] byteArray = zk.getData(path,false, new Stat());
  85. String retValue = new String(byteArray);
  86. System.out.println("**************triggerValue: " + retValue);
  87. return retValue;
  88. }
  89. }

HelloZK

  1. package com.luoma;
  2. import org.apache.zookeeper.KeeperException;
  3. import java.io.IOException;
  4. public class HelloZK {
  5. private static final String PATH ="/NodeLuomaRootLeft";
  6. public static void main(String[] args) throws IOException, KeeperException, InterruptedException
  7. {
  8. ZKHelper zkHelper = new ZKHelper();
  9. zkHelper.setZk(zkHelper.startZK());
  10. if(zkHelper.getZk().exists(PATH, false) == null)
  11. {
  12. zkHelper.createZNode(PATH,"NodeLuomaRootLeft-Data");
  13. System.out.println("**********************>: "+zkHelper.getZNode(PATH));
  14. Thread.sleep(Long.MAX_VALUE);
  15. }else{
  16. System.out.println("i have znode");
  17. }
  18. }
  19. }

测试

1.运行 HelloZK 的 main 方法,控制台输出了

  1. **********************>: NodeLuomaRootLeft-Data

2.进入 Zookeeper 客户端

进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin

连接 ./zkCli.sh

修改 NodeLuomaRootLeft 节点值 set /NodeLuomaRootLeft NodeLuomaRootLeft-DataV001

  1. [zk: localhost:2181(CONNECTED) 3] set /NodeLuomaRootLeft NodeLuomaRootLeft-DataV001
  2. cZxid = 0xc
  3. ctime = Sat Feb 13 15:57:21 CST 2021
  4. mZxid = 0xd
  5. mtime = Sat Feb 13 16:01:00 CST 2021
  6. pZxid = 0xc
  7. cversion = 0
  8. dataVersion = 1
  9. aclVersion = 0
  10. ephemeralOwner = 0x0
  11. dataLength = 26
  12. numChildren = 0

3.设置成功后, HelloZK 的 main 方法,控制台输出了

  1. **************triggerValue: NodeLuomaRootLeft-DataV001

多次触发

ZKHelper

  1. package com.luoma;
  2. import org.apache.zookeeper.*;
  3. import org.apache.zookeeper.data.Stat;
  4. import java.io.IOException;
  5. public class ZKHelper {
  6. //定义常量
  7. private static final String CONNECTSTRING = "192.168.114.128:2181";
  8. private static final int SESSION_TIMEOUT = 50 * 1000;
  9. //定义实例变量
  10. private ZooKeeper zk = null;
  11. private String lastValue = "";
  12. //setter---getter
  13. public ZooKeeper getZk()
  14. {
  15. return zk;
  16. }
  17. public void setZk(ZooKeeper zk)
  18. {
  19. this.zk = zk;
  20. }
  21. /**
  22. * 启动连接
  23. * @return
  24. * @throws IOException
  25. */
  26. public ZooKeeper startZK() throws IOException {
  27. return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
  28. public void process(WatchedEvent watchedEvent) {
  29. }
  30. });
  31. }
  32. /**
  33. * 关闭连接
  34. * @param zk
  35. * @throws InterruptedException
  36. */
  37. public void stopZK() throws InterruptedException {
  38. if (zk != null){
  39. zk.close();
  40. }
  41. }
  42. /**
  43. * 新建一个 ZNode 节点
  44. * @param zk
  45. * @param path
  46. * @param data
  47. * @throws KeeperException
  48. * @throws InterruptedException
  49. */
  50. public void createZNode(String path,String data) throws KeeperException, InterruptedException {
  51. zk.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  52. }
  53. /**
  54. * 获取节点的值
  55. * @param path
  56. * @return
  57. * @throws KeeperException
  58. * @throws InterruptedException
  59. */
  60. public String getZNode(String path) throws KeeperException, InterruptedException
  61. {
  62. byte[] byteArray = zk.getData(path,new Watcher() {
  63. @Override
  64. public void process(WatchedEvent event)
  65. {
  66. try
  67. {
  68. triggerValue(path);
  69. }catch (KeeperException | InterruptedException e) {
  70. e.printStackTrace();
  71. }
  72. }
  73. }, new Stat());
  74. return new String(byteArray);
  75. }
  76. /**
  77. * 触发修改
  78. * @param path
  79. * @return
  80. * @throws KeeperException
  81. * @throws InterruptedException
  82. */
  83. public boolean triggerValue(String path) throws KeeperException, InterruptedException {
  84. byte[] byteArray = zk.getData(path, new Watcher() {
  85. @Override
  86. public void process(WatchedEvent event) {
  87. try {
  88. triggerValue(path);
  89. } catch (KeeperException | InterruptedException e) {
  90. e.printStackTrace();
  91. }
  92. }
  93. }, new Stat());
  94. String newValue = new String(byteArray);
  95. if (lastValue.equals(newValue)) {
  96. System.out.println("there is no change~~~~~~~~");
  97. return false;
  98. } else {
  99. System.out.println("lastValue: " + lastValue + "\t" + "newValue: " + newValue);
  100. this.lastValue = newValue;
  101. return true;
  102. }
  103. }
  104. }

HelloZK

  1. package com.luoma;
  2. import org.apache.zookeeper.KeeperException;
  3. import java.io.IOException;
  4. public class HelloZK {
  5. private static final String PATH ="/NodeLuomaRootRight";
  6. public static void main(String[] args) throws IOException, KeeperException, InterruptedException
  7. {
  8. ZKHelper zkHelper = new ZKHelper();
  9. zkHelper.setZk(zkHelper.startZK());
  10. if(zkHelper.getZk().exists(PATH, false) == null)
  11. {
  12. zkHelper.createZNode(PATH,"NodeLuomaRootRight-Data");
  13. System.out.println("**********************>: "+zkHelper.getZNode(PATH));
  14. Thread.sleep(Long.MAX_VALUE);
  15. }else{
  16. System.out.println("i have znode");
  17. }
  18. }
  19. }

测试

1.运行 HelloZK 的 main 方法,控制台输出了

  1. **********************>: NodeLuomaRootRight-Data

2.进入 Zookeeper 客户端

进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin

连接 ./zkCli.sh

修改 NodeLuomaRootRight 节点值 set /NodeLuomaRootRight NodeLuomaRootRight-DataV001

  1. [zk: localhost:2181(CONNECTED) 4] set /NodeLuomaRootRight NodeLuomaRootRight-DataV001
  2. cZxid = 0x10
  3. ctime = Sat Feb 13 16:07:23 CST 2021
  4. mZxid = 0x11
  5. mtime = Sat Feb 13 16:08:00 CST 2021
  6. pZxid = 0x10
  7. cversion = 0
  8. dataVersion = 1
  9. aclVersion = 0
  10. ephemeralOwner = 0x0
  11. dataLength = 27
  12. numChildren = 0

3.设置成功后, HelloZK 的 main 方法,控制台输出了

  1. **********************>: NodeLuomaRootRight-Data
  2. lastValue: newValue: NodeLuomaRootRight-DataV001

4.再次修改 NodeLuomaRootRight 节点值,set /NodeLuomaRootRight NodeLuomaRootRight-DataV002

  1. [zk: localhost:2181(CONNECTED) 5] set /NodeLuomaRootRight NodeLuomaRootRight-DataV002
  2. cZxid = 0x10
  3. ctime = Sat Feb 13 16:07:23 CST 2021
  4. mZxid = 0x12
  5. mtime = Sat Feb 13 16:08:07 CST 2021
  6. pZxid = 0x10
  7. cversion = 0
  8. dataVersion = 2
  9. aclVersion = 0
  10. ephemeralOwner = 0x0
  11. dataLength = 27
  12. numChildren = 0

5.设置成功后, HelloZK 的 main 方法,控制台输出了

  1. **********************>: NodeLuomaRootRight-Data
  2. lastValue: newValue: NodeLuomaRootRight-DataV001
  3. lastValue: NodeLuomaRootRight-DataV001 newValue: NodeLuomaRootRight-DataV002

Zookeeper 集群

因为条件限制,我的 Zookeeper 是安装在虚拟机中的

名称 版本 描述
Zookeeper 3.4.11 运行在虚拟机上,IP 地址:192.168.114.128

所以这里集群效果是伪分布式单机配置

说明

服务器名称与地址:集群信息(服务器编号,服务器地址,LF通信端口,选举端口)

这个配置项的书写格式比较特殊,规则如下:server.N=YYY:A:B

名称 描述
N 表示服务器编号
YYY 表示服务器的IP地址
A A 为 LF 通信端口,表示该服务器与集群中的 leader 交换的信息的端口
B B 为选举端口,表示选举新 leader 时服务器间相互通信的端口(当 leader 挂掉时,其余服务器会相互通信,选择出新的 leader)

一般来说,集群中每个服务器的 A 端口都是一样,每个服务器的 B 端口也是一样。

下面是一个集群的例子:

  1. server.0=233.34.9.144:2008:6008
  2. server.1=233.34.9.145:2008:6008
  3. server.2=233.34.9.146:2008:6008
  4. server.3=233.34.9.147:2008:6008

但是当所采用的为伪集群时,IP 地址都一样,只能是 A 端口和 B 端口不一样。

下面是一个伪集群的例子:

  1. server.0=127.0.0.1:2008:6008
  2. server.1=127.0.0.1:2007:6007
  3. server.2=127.0.0.1:2006:6006
  4. server.3=127.0.0.1:2005:6005

另外还有两个配置项需要注意

名称 描述
initLimit initLimit 是 Zookeeper 用它来限定集群中的 Zookeeper 服务器连接到 Leader 的时限
syncLimit syncLimit 限制了 follower 服务器与 leader 服务器之间请求和应答之间的时限

1.拷贝不同的服务文件夹

zookeeper-3.4.9.tar.gz 解压后拷贝到 /opt/myzookeeper目录下并重新名为 zk01,再复制 zk01 形成 zk02、zk03,共计 3 份

  1. cd /opt/myzookeeper/
  2. cp -r zookeeper-3.4.11 zp01
  3. cp -r zp01 zp02
  4. cp -r zp01 zp03

查看结果

  1. [root@luoma myzookeeper]# pwd
  2. /opt/myzookeeper
  3. [root@luoma myzookeeper]# ls
  4. zk01 zk02 zk03 zookeeper-3.4.11

2.进入 zk01/02/03 分别新建文件夹

mydata
mylog

  1. cd /opt/myzookeeper/zk01
  2. mkdir mydata
  3. mkdir mylog
  4. cd /opt/myzookeeper/zk02
  5. mkdir mydata
  6. mkdir mylog
  7. cd /opt/myzookeeper/zk02
  8. mkdir mydata
  9. mkdir mylog

3.分别进入 zk01-zk03 各自的 conf 文件夹

复制 zoo_sample.cfg,命名 zoo.cfg

  1. cd /opt/myzookeeper/zk01/conf
  2. cp zoo_sample.cfg zoo.cfg
  3. cd /opt/myzookeeper/zk02/conf
  4. cp zoo_sample.cfg zoo.cfg
  5. cd /opt/myzookeeper/zk03/conf
  6. cp zoo_sample.cfg zoo.cfg

4.编辑 zoo.cfg

(1)zk01

  1. vim /opt/myzookeeper/zk01/conf/zoo.cfg

修改

  1. #设置自己的数据和log路径
  2. dataDir=/opt/myzookeeper/zk01/mydata
  3. dataLogDir=/opt/myzookeeper/zk01/mylog
  4. clientPort=2191
  5. #server的列表
  6. server.1=127.0.0.1:2991:3991
  7. server.2=127.0.0.1:2992:3992
  8. server.3=127.0.0.1:2993:3993

(2)zk02

  1. vim /opt/myzookeeper/zk02/conf/zoo.cfg

修改

  1. #设置自己的数据和log路径
  2. dataDir=/opt/myzookeeper/zk02/mydata
  3. dataLogDir=/opt/myzookeeper/zk02/mylog
  4. clientPort=2192
  5. #server的列表
  6. server.1=127.0.0.1:2991:3991
  7. server.2=127.0.0.1:2992:3992
  8. server.3=127.0.0.1:2993:3993

(3)zk03

  1. vim /opt/myzookeeper/zk03/conf/zoo.cfg

修改

  1. #设置自己的数据和log路径
  2. dataDir=/opt/myzookeeper/zk03/mydata
  3. dataLogDir=/opt/myzookeeper/zk03/mylog
  4. clientPort=2193
  5. #server的列表
  6. server.1=127.0.0.1:2991:3991
  7. server.2=127.0.0.1:2992:3992
  8. server.3=127.0.0.1:2993:3993

5.在各自 mydata 下面创建 myid 的文件,在里面写入 server 的数字

(1)zk01

  1. vim /opt/myzookeeper/zk01/mydata/myid

输入

  1. 1

验证

  1. [root@luoma conf]# cat /opt/myzookeeper/zk01/mydata/myid
  2. 1

(2)zk02

  1. vim /opt/myzookeeper/zk02/mydata/myid

输入

  1. 2

验证

  1. [root@luoma conf]# cat /opt/myzookeeper/zk02/mydata/myid
  2. 2

(3)zk03

  1. vim /opt/myzookeeper/zk03/mydata/myid

输入

  1. 3

验证

  1. [root@luoma conf]# cat /opt/myzookeeper/zk03/mydata/myid
  2. 3

6.分别启动三个服务器

(1)zk01

  1. cd /opt/myzookeeper/zk01/bin
  2. ./zkServer.sh start

(2)zk02

  1. cd /opt/myzookeeper/zk02/bin
  2. ./zkServer.sh start

(3)zk03

  1. cd /opt/myzookeeper/zk03/bin
  2. ./zkServer.sh start

7.zkCli 连接server,带参数指定-server

(1)zk01

  1. cd /opt/myzookeeper/zk01/bin
  2. ./zkCli.sh -server 127.0.0.1:2191

(2)zk02

  1. cd /opt/myzookeeper/zk02/bin
  2. ./zkCli.sh -server 127.0.0.1:2192

(3)zk03

  1. cd /opt/myzookeeper/zk03/bin
  2. ./zkCli.sh -server 127.0.0.1:2193

8.测试

需要测试 2191/2192/2193 任意用客户端连接一台,只需要有一个改变,整个集群的内容自动一致性同步。

(1)zk01 创建 create /LuomaRoot1 LuomaRoot1-Data-001

  1. [zk: 127.0.0.1:2191(CONNECTED) 1] create /LuomaRoot1 LuomaRoot1-Data-001
  2. Created /LuomaRoot1

(2)zk02 读取 get /LuomaRoot1

  1. [zk: 127.0.0.1:2192(CONNECTED) 0] get /LuomaRoot1
  2. LuomaRoot1-Data-001
  3. cZxid = 0x100000005
  4. ctime = Sat Feb 13 17:05:33 CST 2021
  5. mZxid = 0x100000005
  6. mtime = Sat Feb 13 17:05:33 CST 2021
  7. pZxid = 0x100000005
  8. cversion = 0
  9. dataVersion = 0
  10. aclVersion = 0
  11. ephemeralOwner = 0x0
  12. dataLength = 19
  13. numChildren = 0

创建一个 create /LuomaRoot1/Root1-C1 C1-Data-001

  1. [zk: 127.0.0.1:2192(CONNECTED) 1] create /LuomaRoot1/Root1-C1 C1-Data-001
  2. Created /LuomaRoot1/Root1-C1

(3)zk03 读取 get /LuomaRoot1/Root1-C1

  1. [zk: 127.0.0.1:2193(CONNECTED) 1] get /LuomaRoot1/Root1-C1
  2. C1-Data-001
  3. cZxid = 0x100000006
  4. ctime = Sat Feb 13 17:08:09 CST 2021
  5. mZxid = 0x100000006
  6. mtime = Sat Feb 13 17:08:09 CST 2021
  7. pZxid = 0x100000006
  8. cversion = 0
  9. dataVersion = 0
  10. aclVersion = 0
  11. ephemeralOwner = 0x0
  12. dataLength = 11
  13. numChildren = 0

集群测试完成。