开发环境
名称 | 版本 | 描述 |
---|---|---|
操作系统 | 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
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
目录下并解压
tar -zxvf zookeeper-3.4.11.tar.gz
4.解压后删除 zookeeper-3.4.11.tar.gz
rm -rvf zookeeper-3.4.11.tar.gz
5.进入 conf 文件夹,拷贝 zoo_sample.cfg 改为 zoo.cfg
cd /opt/myzookeeper/zookeeper-3.4.11/conf
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
[root@luoma bin]# ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /opt/myzookeeper/zookeeper-3.4.11/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
使用 nc 命令查看是否启动
echo ruok | nc 127.0.0.1 2181
出现了 imok
,表示已经成功启动
[root@luoma bin]# echo ruok | nc 127.0.0.1 2181
imok[root@luoma bin]#
(3)停止 ./zkServer.sh stop
[root@luoma bin]# ./zkServer.sh stop
ZooKeeper JMX enabled by default
Using config: /opt/myzookeeper/zookeeper-3.4.11/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
Zookeeper 客户端连接
1.连接
进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin
连接 ./zkCli.sh
2.查看+获得 zookeeper 服务器上的数据存储信息
#查看根目录的节点内容
ls /
#查看根目录 zookeeper 的节点内容
ls /zookeeper
#查看目录 /zookeeper/quota 的节点内容
ls /zookeeper/quota
#获取 /zookeeper 节点内容
get /zookeeper
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /zookeeper
[quota]
[zk: localhost:2181(CONNECTED) 2] ls /zookeeper/quota
[]
[zk: localhost:2181(CONNECTED) 3] get /zookeeper
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
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
[root@luoma bin]# echo ruok | nc 127.0.0.1 2181
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ZooKeeperDemo</groupId>
<artifactId>ZooKeeperDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="log.console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %5p (%C{1}:%M) - %m%n" />
</layout>
<!--
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="levelMin" value="debug" />
<param name="levelMax" value="warn" />
<param name="AcceptOnMatch" value="true" />
</filter>-->
</appender>
<appender name="log.file" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="D:\\Java\\2021\\Log\\Log4XML.log" />
<param name="Append" value="true" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %5p (%C{1}:%M) - %m%n" />
</layout>
</appender>
<root>
<level value="info" />
<appender-ref ref="log.console" />
<appender-ref ref="log.file" />
</root>
</log4j:configuration>
ZKHelper.java
package com.luoma;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
public class ZKHelper {
private static final String CONNECTSTRING = "192.168.114.128:2181";
private static final int SESSION_TIMEOUT = 20 * 1000;
/**
* 启动连接
* @return
* @throws IOException
*/
public ZooKeeper startZK() throws IOException {
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
public void process(WatchedEvent watchedEvent) {
}
});
}
/**
* 关闭连接
* @param zk
* @throws InterruptedException
*/
public void stopZK(ZooKeeper zk) throws InterruptedException {
if (zk != null){
zk.close();
}
}
/**
* 新建一个 ZNode 节点
* @param zk
* @param path
* @param data
* @throws KeeperException
* @throws InterruptedException
*/
public void createZNode(ZooKeeper zk,String path,String data) throws KeeperException, InterruptedException {
zk.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
/**
* 获取节点的值
* @param zk
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public String getZNode(ZooKeeper zk,String path) throws KeeperException, InterruptedException {
String result = "";
byte[] data = zk.getData(path,false,new Stat());
result = new String(data);
return result;
}
}
HelloZK.java
package com.luoma;
import org.apache.zookeeper.ZooKeeper;
public class HelloZK {
private static final String PATH ="/NodeLuomaRoot";
public static void main(String[] args) {
try{
ZKHelper zkHelper = new ZKHelper();
ZooKeeper zk = zkHelper.startZK();
if (zk.exists(PATH,false) == null){
zkHelper.createZNode(zk,PATH,"NodeLuomaRoot-DATA");
String znode = zkHelper.getZNode(zk,PATH);
System.out.println("zNode = "+ znode);
}else{
System.out.println("this zNode is created");
}
zkHelper.stopZK(zk);
}catch (Exception e){
}
}
}
测试结果
1.运行 HelloZK main 方法,控制台部分信息如下
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)
15:05:06,218 INFO (ClientCnxn$SendThread:primeConnection) - Socket connection established to 192.168.114.128/192.168.114.128:2181, initiating session
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
zNode = NodeLuomaRoot-DATA
15:05:07,185 INFO (ZooKeeper:close) - Session: 0x1000077d8b50004 closed
断开与目标 VM 的连接,地址:'127.0.0.1:28390', transport: 'socket'
2.进入 Zookeeper 客户端
进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin
连接 ./zkCli.sh
(1)查看根目录数据 ls /
[zk: localhost:2181(CONNECTED) 2] ls /
[zookeeper, NodeLuomaRoot]
(2)获取 NodeLuomaRoot 节点数据,get /NodeLuomaRoot
[zk: localhost:2181(CONNECTED) 0] get /NodeLuomaRoot
NodeLuomaRoot-DATA
cZxid = 0x9
ctime = Sat Feb 13 15:04:58 CST 2021
mZxid = 0x9
mtime = Sat Feb 13 15:04:58 CST 2021
pZxid = 0x9
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 18
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
package com.luoma;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
public class ZKHelper {
//定义常量
private static final String CONNECTSTRING = "192.168.114.128:2181";
private static final int SESSION_TIMEOUT = 50 * 1000;
//定义实例变量
private ZooKeeper zk = null;
//setter---getter
public ZooKeeper getZk()
{
return zk;
}
public void setZk(ZooKeeper zk)
{
this.zk = zk;
}
/**
* 启动连接
* @return
* @throws IOException
*/
public ZooKeeper startZK() throws IOException {
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
public void process(WatchedEvent watchedEvent) {
}
});
}
/**
* 关闭连接
* @param zk
* @throws InterruptedException
*/
public void stopZK() throws InterruptedException {
if (zk != null){
zk.close();
}
}
/**
* 新建一个 ZNode 节点
* @param zk
* @param path
* @param data
* @throws KeeperException
* @throws InterruptedException
*/
public void createZNode(String path,String data) throws KeeperException, InterruptedException {
zk.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
/**
* 获取节点的值
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public String getZNode(String path) throws KeeperException, InterruptedException
{
byte[] byteArray = zk.getData(path,new Watcher() {
@Override
public void process(WatchedEvent event)
{
try
{
triggerValue(path);
}catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
return new String(byteArray);
}
/**
* 触发修改
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public String triggerValue(String path) throws KeeperException, InterruptedException
{
byte[] byteArray = zk.getData(path,false, new Stat());
String retValue = new String(byteArray);
System.out.println("**************triggerValue: " + retValue);
return retValue;
}
}
HelloZK
package com.luoma;
import org.apache.zookeeper.KeeperException;
import java.io.IOException;
public class HelloZK {
private static final String PATH ="/NodeLuomaRootLeft";
public static void main(String[] args) throws IOException, KeeperException, InterruptedException
{
ZKHelper zkHelper = new ZKHelper();
zkHelper.setZk(zkHelper.startZK());
if(zkHelper.getZk().exists(PATH, false) == null)
{
zkHelper.createZNode(PATH,"NodeLuomaRootLeft-Data");
System.out.println("**********************>: "+zkHelper.getZNode(PATH));
Thread.sleep(Long.MAX_VALUE);
}else{
System.out.println("i have znode");
}
}
}
测试
1.运行 HelloZK 的 main 方法,控制台输出了
**********************>: NodeLuomaRootLeft-Data
2.进入 Zookeeper 客户端
进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin
连接 ./zkCli.sh
修改 NodeLuomaRootLeft
节点值 set /NodeLuomaRootLeft NodeLuomaRootLeft-DataV001
[zk: localhost:2181(CONNECTED) 3] set /NodeLuomaRootLeft NodeLuomaRootLeft-DataV001
cZxid = 0xc
ctime = Sat Feb 13 15:57:21 CST 2021
mZxid = 0xd
mtime = Sat Feb 13 16:01:00 CST 2021
pZxid = 0xc
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 26
numChildren = 0
3.设置成功后, HelloZK 的 main 方法,控制台输出了
**************triggerValue: NodeLuomaRootLeft-DataV001
多次触发
ZKHelper
package com.luoma;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
public class ZKHelper {
//定义常量
private static final String CONNECTSTRING = "192.168.114.128:2181";
private static final int SESSION_TIMEOUT = 50 * 1000;
//定义实例变量
private ZooKeeper zk = null;
private String lastValue = "";
//setter---getter
public ZooKeeper getZk()
{
return zk;
}
public void setZk(ZooKeeper zk)
{
this.zk = zk;
}
/**
* 启动连接
* @return
* @throws IOException
*/
public ZooKeeper startZK() throws IOException {
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
public void process(WatchedEvent watchedEvent) {
}
});
}
/**
* 关闭连接
* @param zk
* @throws InterruptedException
*/
public void stopZK() throws InterruptedException {
if (zk != null){
zk.close();
}
}
/**
* 新建一个 ZNode 节点
* @param zk
* @param path
* @param data
* @throws KeeperException
* @throws InterruptedException
*/
public void createZNode(String path,String data) throws KeeperException, InterruptedException {
zk.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
/**
* 获取节点的值
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public String getZNode(String path) throws KeeperException, InterruptedException
{
byte[] byteArray = zk.getData(path,new Watcher() {
@Override
public void process(WatchedEvent event)
{
try
{
triggerValue(path);
}catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
return new String(byteArray);
}
/**
* 触发修改
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public boolean triggerValue(String path) throws KeeperException, InterruptedException {
byte[] byteArray = zk.getData(path, new Watcher() {
@Override
public void process(WatchedEvent event) {
try {
triggerValue(path);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
String newValue = new String(byteArray);
if (lastValue.equals(newValue)) {
System.out.println("there is no change~~~~~~~~");
return false;
} else {
System.out.println("lastValue: " + lastValue + "\t" + "newValue: " + newValue);
this.lastValue = newValue;
return true;
}
}
}
HelloZK
package com.luoma;
import org.apache.zookeeper.KeeperException;
import java.io.IOException;
public class HelloZK {
private static final String PATH ="/NodeLuomaRootRight";
public static void main(String[] args) throws IOException, KeeperException, InterruptedException
{
ZKHelper zkHelper = new ZKHelper();
zkHelper.setZk(zkHelper.startZK());
if(zkHelper.getZk().exists(PATH, false) == null)
{
zkHelper.createZNode(PATH,"NodeLuomaRootRight-Data");
System.out.println("**********************>: "+zkHelper.getZNode(PATH));
Thread.sleep(Long.MAX_VALUE);
}else{
System.out.println("i have znode");
}
}
}
测试
1.运行 HelloZK 的 main 方法,控制台输出了
**********************>: NodeLuomaRootRight-Data
2.进入 Zookeeper 客户端
进入 cd /opt/myzookeeper/zookeeper-3.4.11/bin
连接 ./zkCli.sh
修改 NodeLuomaRootRight
节点值 set /NodeLuomaRootRight NodeLuomaRootRight-DataV001
[zk: localhost:2181(CONNECTED) 4] set /NodeLuomaRootRight NodeLuomaRootRight-DataV001
cZxid = 0x10
ctime = Sat Feb 13 16:07:23 CST 2021
mZxid = 0x11
mtime = Sat Feb 13 16:08:00 CST 2021
pZxid = 0x10
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 27
numChildren = 0
3.设置成功后, HelloZK 的 main 方法,控制台输出了
**********************>: NodeLuomaRootRight-Data
lastValue: newValue: NodeLuomaRootRight-DataV001
4.再次修改 NodeLuomaRootRight
节点值,set /NodeLuomaRootRight NodeLuomaRootRight-DataV002
[zk: localhost:2181(CONNECTED) 5] set /NodeLuomaRootRight NodeLuomaRootRight-DataV002
cZxid = 0x10
ctime = Sat Feb 13 16:07:23 CST 2021
mZxid = 0x12
mtime = Sat Feb 13 16:08:07 CST 2021
pZxid = 0x10
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 27
numChildren = 0
5.设置成功后, HelloZK 的 main 方法,控制台输出了
**********************>: NodeLuomaRootRight-Data
lastValue: newValue: NodeLuomaRootRight-DataV001
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 端口也是一样。
下面是一个集群的例子:
server.0=233.34.9.144:2008:6008
server.1=233.34.9.145:2008:6008
server.2=233.34.9.146:2008:6008
server.3=233.34.9.147:2008:6008
但是当所采用的为伪集群时,IP 地址都一样,只能是 A 端口和 B 端口不一样。
下面是一个伪集群的例子:
server.0=127.0.0.1:2008:6008
server.1=127.0.0.1:2007:6007
server.2=127.0.0.1:2006:6006
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 份
cd /opt/myzookeeper/
cp -r zookeeper-3.4.11 zp01
cp -r zp01 zp02
cp -r zp01 zp03
查看结果
[root@luoma myzookeeper]# pwd
/opt/myzookeeper
[root@luoma myzookeeper]# ls
zk01 zk02 zk03 zookeeper-3.4.11
2.进入 zk01/02/03 分别新建文件夹
mydata
mylog
cd /opt/myzookeeper/zk01
mkdir mydata
mkdir mylog
cd /opt/myzookeeper/zk02
mkdir mydata
mkdir mylog
cd /opt/myzookeeper/zk02
mkdir mydata
mkdir mylog
3.分别进入 zk01-zk03 各自的 conf 文件夹
复制 zoo_sample.cfg
,命名 zoo.cfg
cd /opt/myzookeeper/zk01/conf
cp zoo_sample.cfg zoo.cfg
cd /opt/myzookeeper/zk02/conf
cp zoo_sample.cfg zoo.cfg
cd /opt/myzookeeper/zk03/conf
cp zoo_sample.cfg zoo.cfg
4.编辑 zoo.cfg
(1)zk01
vim /opt/myzookeeper/zk01/conf/zoo.cfg
修改
#设置自己的数据和log路径
dataDir=/opt/myzookeeper/zk01/mydata
dataLogDir=/opt/myzookeeper/zk01/mylog
clientPort=2191
#server的列表
server.1=127.0.0.1:2991:3991
server.2=127.0.0.1:2992:3992
server.3=127.0.0.1:2993:3993
(2)zk02
vim /opt/myzookeeper/zk02/conf/zoo.cfg
修改
#设置自己的数据和log路径
dataDir=/opt/myzookeeper/zk02/mydata
dataLogDir=/opt/myzookeeper/zk02/mylog
clientPort=2192
#server的列表
server.1=127.0.0.1:2991:3991
server.2=127.0.0.1:2992:3992
server.3=127.0.0.1:2993:3993
(3)zk03
vim /opt/myzookeeper/zk03/conf/zoo.cfg
修改
#设置自己的数据和log路径
dataDir=/opt/myzookeeper/zk03/mydata
dataLogDir=/opt/myzookeeper/zk03/mylog
clientPort=2193
#server的列表
server.1=127.0.0.1:2991:3991
server.2=127.0.0.1:2992:3992
server.3=127.0.0.1:2993:3993
5.在各自 mydata 下面创建 myid 的文件,在里面写入 server 的数字
(1)zk01
vim /opt/myzookeeper/zk01/mydata/myid
输入
1
验证
[root@luoma conf]# cat /opt/myzookeeper/zk01/mydata/myid
1
(2)zk02
vim /opt/myzookeeper/zk02/mydata/myid
输入
2
验证
[root@luoma conf]# cat /opt/myzookeeper/zk02/mydata/myid
2
(3)zk03
vim /opt/myzookeeper/zk03/mydata/myid
输入
3
验证
[root@luoma conf]# cat /opt/myzookeeper/zk03/mydata/myid
3
6.分别启动三个服务器
(1)zk01
cd /opt/myzookeeper/zk01/bin
./zkServer.sh start
(2)zk02
cd /opt/myzookeeper/zk02/bin
./zkServer.sh start
(3)zk03
cd /opt/myzookeeper/zk03/bin
./zkServer.sh start
7.zkCli 连接server,带参数指定-server
(1)zk01
cd /opt/myzookeeper/zk01/bin
./zkCli.sh -server 127.0.0.1:2191
(2)zk02
cd /opt/myzookeeper/zk02/bin
./zkCli.sh -server 127.0.0.1:2192
(3)zk03
cd /opt/myzookeeper/zk03/bin
./zkCli.sh -server 127.0.0.1:2193
8.测试
需要测试 2191/2192/2193 任意用客户端连接一台,只需要有一个改变,整个集群的内容自动一致性同步。
(1)zk01 创建 create /LuomaRoot1 LuomaRoot1-Data-001
[zk: 127.0.0.1:2191(CONNECTED) 1] create /LuomaRoot1 LuomaRoot1-Data-001
Created /LuomaRoot1
(2)zk02 读取 get /LuomaRoot1
[zk: 127.0.0.1:2192(CONNECTED) 0] get /LuomaRoot1
LuomaRoot1-Data-001
cZxid = 0x100000005
ctime = Sat Feb 13 17:05:33 CST 2021
mZxid = 0x100000005
mtime = Sat Feb 13 17:05:33 CST 2021
pZxid = 0x100000005
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 19
numChildren = 0
创建一个 create /LuomaRoot1/Root1-C1 C1-Data-001
[zk: 127.0.0.1:2192(CONNECTED) 1] create /LuomaRoot1/Root1-C1 C1-Data-001
Created /LuomaRoot1/Root1-C1
(3)zk03 读取 get /LuomaRoot1/Root1-C1
[zk: 127.0.0.1:2193(CONNECTED) 1] get /LuomaRoot1/Root1-C1
C1-Data-001
cZxid = 0x100000006
ctime = Sat Feb 13 17:08:09 CST 2021
mZxid = 0x100000006
mtime = Sat Feb 13 17:08:09 CST 2021
pZxid = 0x100000006
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 0
集群测试完成。