概念:一个分布式协调技术,高性能的,开源的分布式系统协调服务。
zookeeper=类似unix文件系统+通知机制+Znode节点
下载地址:https://zookeeper.apache.org/releases.html
功能:
- 统一命名服务
- 配置管理
- Java操作API
一、安装
bin目录下
启动:./zkServer.sh start
暂停:./zkServer.sh stop
客户端: ./zkCli.sh
退出:quit
注意:运行需要Java环境。
1、helloWorld
文件系统:所使用的数据模型风格很像文件系统的目录树结构,简单来说,有点类似windows中注册表的结构。
有名称,有树节点,有Key(键)/Value(值)对的关系。
可以看做一个树形结构的数据库,分布在不同的机器上做名称管理。
ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。
很显然zookeeper集群自身维护了一套数据结构。这个存储结构是一个树形结构,每一个znode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。
二、数据模型Znode节点深入
1、znode的数据模型
Znode维护了一个stat结构,这个stat包含数据变化的版本号、访问控制列表变化、还有时间戳。版本号和时间戳一起,可让Zookeeper验证缓存和协调更新。每次znode的数据发生了变化,版本号就增加。
ZooKeeper的结构体
- czxid- 引起这个znode创建的zxid,创建节点的事务的zxid(ZooKeeper Transaction Id)
- 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
2、znode中的存在类型
- PERSISTENT-持久化目录节点 create 默认
- PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点 create -s
- EPHEMERAL-临时目录节点 create -e
- EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点 create -s -e
PERSISTENT-持久化节点:创建这个节点的客户端在与zookeeper服务的连接断开后,这个节点也不会被删除
PERSISTENT_SEQUENTIAL-持久化顺序编号节点:当客户端请求创建这个节点A后,zookeeper会根据parent-znode的zxid状态,为这个A节点编写一个全目录唯一的编号(这个编号只会一直增长)。当客户端与zookeeper服务的连接断开后,这个节点也不会被删除。
EPHEMERAL-临时目录节点:创建这个节点的客户端在与zookeeper服务的连接断开后,这个节点(还有涉及到的子节点)就会被删除。
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点:当客户端请求创建这个节点A后,zookeeper会根据parent-znode的zxid状态,为这个A节点编写一个全目录唯一的编号(这个编号只会一直增长)。当创建这个节点的客户端与zookeeper服务的连接断开后,这个节点被删除。
三、基础命令和Java客户端操作
1、zkCli的常用命令操作
- ls
- ls2 查看当前节点数据并能看到更新次数等数据
- stat 查看节点状态
- set
- get
- create 默认无参持久化 --s含有序列 -e临时 可以同时用
- delete 删除无子节点的目录
- rmr 递归删除
2、四字命令
概念:zookeeper支持某些特定的四字命令,他们大多是用来查询ZK服务的当前状态及相关信息的,
通过telnet或nc向zookeeper提交相应命令,如:echo ruok | nc 127.0.0.1 2181
常用命令:
3、Java客户端操作
1、导入JAR包
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
2、log4j
<?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:\atguigu4XML.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>
<!-- <filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="levelMin" value="info" />
<param name="levelMax" value="info" />
<param name="AcceptOnMatch" value="true" />
</filter> -->
</appender>
<!--
<logger name="com.atguigu" >
<level value="info" />
<appender-ref ref="log.console" />
<appender-ref ref="log.file" />
</logger>
<logger name="com.atguigu.dao" >
<level value="debug" />
<appender-ref ref="log.console" />
<appender-ref ref="log.file" />
</logger>-->
<!-- -->
<root>
<level value="info"/>
<appender-ref ref="log.console"/>
<appender-ref ref="log.file"/>
</root>
</log4j:configuration>
3、java代码API操作zookeeper
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
/**
* @Description:
* @Auther: Tang
* @Date: 2020/9/27 0027 下午 20:45
*/
public class WatchMore {
private static final String CONNECTSTRING = "39.99.144.224:2181";
private static final String PATH = "/tang";
private static final int SESSION_TIMEOUT=20*1000;
/**
* 创建连接
* @return
* @throws IOException
*/
public ZooKeeper startZK() throws IOException {
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
@Override
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);
}
/**
* 获取zNode
* @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;
}
public static void main(String[] args) throws Exception {
WatchMore watchMore = new WatchMore();
ZooKeeper zk = watchMore.startZK();
if (zk.exists(PATH,false)==null){
watchMore.createZNode(zk,PATH,"helloZK");
String zNode = watchMore.getZNode(zk, PATH);
System.out.println("zNode:"+zNode);
}else{
System.out.println("this zNode is created");
}
watchMore.stopZK(zk);
}
}
四、通知机制
1、通知机制:
客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
2、观察者的功能watcher:
ZooKeeper 支持watch(观察)的概念。客户端可以在每个znode结点上设置一个观察。如果被观察服务端的znode结点有变更,那么watch就会被触发,这个watch所属的客户端将接收到一个通知包被告知结点已经发生变化,把相应的事件通知给设置过Watcher的Client端。(一句话:异步回调的触发机制)
3、watch事件理解
- 一次触发:当数据有了变化时zkserver向客户端发送一个watch,它是一次性的动作,即触发一次就不再有效
- 发往客户端:Watches是异步发往客户端的,Zookeeper提供一个顺序保证:在看到watch事件之前绝不会看到变化,这样不同客户端看到的是一致性的顺序。
- 为数据设置watch:节点有不同的改动方式。可以认为ZooKeeper维护两个观察列表:数据观察和子节点观察。getData()和exists()设置数据观察。getChildren()设置子节点观察。此外,还可以认为不同的返回数据有不同的观察。getData()和exists()返回节点的数据,而getChildren()返回子节点列表。所以,setData()将为znode触发数据观察。成功的create()将为新创建的节点触发数据观察,为其父节点触发子节点观察。成功的delete()将会为被删除的节点触发数据观察以及子节点观察(因为节点不能再有子节点了),为其父节点触发子节点观察。
- 时序性和一致性:Watches是在client连接到Zookeeper服务端的本地维护,这可让watches成为轻量的,可维护的和派发的。当一个client连接到新server,watch将会触发任何session事件,断开连接后不能接收到。当客户端重连,先前注册的watches将会被重新注册并触发。、
4、多次观察代码
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
/**
* @Description:
* @Auther: Tang
* @Date: 2020/9/27 0027 下午 20:45
*/
public class WatchMore {
private static final String CONNECTSTRING = "39.99.144.224:2181";
private static final String PATH = "/tang";
private static final int SESSION_TIMEOUT=20*1000;
private ZooKeeper zk;
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() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
}
/**
* 关闭连接
* @throws InterruptedException
*/
public void stopZK() throws InterruptedException {
if (zk!=null){
zk.close();
}
}
/**
* 创建zNode节点
* @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);
}
/**
* 获取zNode
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public String getZNode(String path) throws Exception {
String result = "";
byte[] data = zk.getData(path, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
triggerValue(path);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
result = new String(data);
return result;
}
private void triggerValue(String path) throws KeeperException, InterruptedException {
byte[] data = zk.getData(path, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
triggerValue(path);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
String result = new String(data);
System.out.println("triggerValue:"+result);
}
public static void main(String[] args) throws Exception {
WatchMore watchMore = new WatchMore();
ZooKeeper zk = watchMore.startZK();
watchMore.setZk(zk);
if (zk.exists(PATH,false)==null){
watchMore.createZNode(PATH,"AAA");
String zNode = watchMore.getZNode(PATH);
System.out.println("zNode:"+zNode);
}else{
System.out.println("this zNode is created");
}
Thread.sleep(Long.MAX_VALUE);
}
}
五、ZooKeeper集群
1、伪分布式单机配置
说明
initLimit 是Zookeeper用它来限定集群中的Zookeeper服务器连接到Leader的时限
syncLimit 限制了follower服务器与leader服务器之间请求和应答之间的时限
配置步骤
-
zookeeper-3.4.9.tar.gz解压后拷贝到/myzookeeper目录下并重新名为zk01,再复制zk01形成zk02、zk03,共计3份
-
进入zk01/02/03分别新建文件夹(mydata,mylog)
-
分别进入zk01-zk03各自的conf文件夹 新建zoo.cfg
-
编辑zoo.cfg
设置自己的数据和log路径
dataDir=/myzookeeper/zk01/mydatadataLogDir=/myzookeeper/zk01/mylog
修改各自的clientPort
在最后面添加server的列表
-
在各自mydata下面创建myid的文件,在里面写入server的数字
-
分别启动三个服务器
-
zkCli连接server,带参数指定-server
./zkCli.sh -server 127.0.0.1:21932191/2192/2193任意用客户端链接一台,会发现只需要有一个改变了,整个集群的内容自动一致性同步。