1.什么是Zookeeper?
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在zookeeper-3.4.3\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。
1.2Zookeeper数据结构
1、层次化的目录结构,类似于树状结构。
如图:
2、每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识 ,名称不可重复。
3、节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)
1.3节点类型
1.3.1 PERSISTENT ,持久节点,持久存储在硬盘中,只有删除操作,即使关闭客户端也不影响数据。
1.3.2 PERSISTENT_SEQUENTIAL()
这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。
EPHEMERAL,临时节点
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。
EPHEMERAL_SEQUENTIAL临时顺序节点
结合EPHEMERAL和SEQUENTIAL, 可以用来实现分布式锁
1.4Java操作Zookeeper
我创建的是SpringBoot项目,先在windows上搭建Zookepper,网上也很多教程,https://www.cnblogs.com/zlslch/p/8561791.html,安装Zookeeper可视化工具便于查看节点信息没我用的是ZooInspector。需要的评论联系我。
1.4.1 Maven添加依赖
org.apache.zookeeper
zookeeper
3.4.6
```
1.4.2使用main函数创建持久节点
package com.cqr.demo.com.cqr.demo.test;
import org.apache.zookeeper.*;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
@ProjectName: car-zook
@Package: com.cqr.demo.com.cqr.demo.test
@ClassName: Test001
@Author: Cqr
@Description: ${description}
@Date: 2019/4/4 20:45
@Version: 1.0
*/
public class Test001
{
//连接地址
private static final String ADDRES = “127.0.0.1:2181”;
//session 会话
private static final int SESSION_OUTTIME = 2000;
//信号量,阻塞程序执行,用户等待zookeeper连接成功,发送成功信号,因为Zookeeper走的是自己的线程,如果不使用信号量,就不会先连接ZooKeeper,而是先走main线程的代码,开始CountDownLatch的值为1,只有启动连接点后,countDownLatch.countDown()后,CountDownLatch变成0,这时阻塞通过,执行下面的代码。
private static final CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws IOException,Exception {
ZooKeeper zk =new ZooKeeper(ADDRES, SESSION_OUTTIME, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
//获取事件状态
Event.KeeperState keeperState=watchedEvent.getState();
//获取事件类型
Event.EventType eventType=watchedEvent.getType();
if(Event.KeeperState.SyncConnected ==keeperState){
if (Event.EventType.None == eventType) {
countDownLatch.countDown();
System.out.println("zk 启动连接...");
}
}
}
});
// 进行阻塞
countDownLatch.await();
String result = zk.create("/cqy_test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
System.out.println(result);
zk.close();
}
}
执行之后在可视工具之中就会发现增加了一个节点cqy_test
如图
1.5Zookeeper中的事件监听Watcher
在ZooKeeper中,接口类Watcher用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含KeeperState和EventType两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法:process(WatchedEvent event)。
在 ZooKeeper 中,引入了 Watcher 机制来实现这种分布式的通知功能。ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务器的一些特定事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
送上别人的图
Watcher通知状态与事件类型一览
当我们写一个类实现Watcher时,会重写一个回调方法process(WatchedEvent event),当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调,从而实现对事件的处理。
WatchedEvent包含每一事件的三种基本属性:通知状态、事件类型、节点路径。Zookeeper使用Watcher对象来封装服务器端事件,并传递给Watcher,从而方便回调方法process对服务器事件进行处理。
服务端在生成WatchedEvent事件之后,会调用getWrapper方法将自己包装成一个可序列化的WatcherEvent事件,以便通过网络传输到客户端。客户端在接收到服务端的这个事件对象后,首先会将WatcherEvent还原成一个WatchedEvent事件,并传递给process方法处理,回调方法process根据入参就能够解析出完整的服务端事件了。
package com.cqr.demo.com.cqr.demo.test;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
/**
* @ProjectName: car-zook
* @Package: com.cqr.demo.com.cqr.demo.test
* @ClassName: ZKCliWatcher
* @Author: XW
* @Description: ${description}
* @Date: 2019/4/8 16:58
* @Version: 1.0
*/
public class ZKCliWatcher implements Watcher {
// 会话超时时间
private static final int SESSIONTIME = 2000;
// 信号量,让zk在连接之前等待,连接成功后才能往下走.
private static final CountDownLatch countDownLatch = new CountDownLatch(1);
private ZooKeeper zk;
/*
* 创建连接
* */
public void createConnection(String address,int sessionTimeOut){
try {
zk = new ZooKeeper(address, sessionTimeOut,this);
countDownLatch.await();
}catch (Exception e){
e.printStackTrace();
}
}
public boolean createPath(String path, String data) {
try {
this.exists(path, true);
this.zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println( "节点创建成功, Path:" + path + ",data:" + data);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
public boolean updateNode(String path,String data) throws KeeperException, InterruptedException {
exists(path, true);
this.zk.setData(path, data.getBytes(), -1);
return false;
}
/**
* 判断指定节点是否存在
*
*
*
* */
public Stat exists(String path, boolean watch) {
try {
return this.zk.exists(path, watch);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public void process(WatchedEvent watchedEvent) {
//获取事件状态
Event.KeeperState keeperState=watchedEvent.getState();
//获取事件类型
Event.EventType eventType=watchedEvent.getType();
//zookeepe路径
String path = watchedEvent.getPath();
System.out.println("进入到 process()方法,keeperState:" + keeperState + ", eventType:" + eventType + ", path:" + path);
if (Event.KeeperState.SyncConnected == keeperState) {
if (Event.EventType.None == eventType) {
// 如果建立建立成功,让后程序往下走
System.out.println("zk 建立连接成功!");
countDownLatch.countDown();
} else if (Event.EventType.NodeCreated == eventType) {
System.out.println( "事件通知,新增node节点" + path);
} else if (Event.EventType.NodeDataChanged == eventType) {
System.out.println( "事件通知,当前node节点" + path + "被修改....");
}
else if (Event.EventType.NodeDeleted == eventType) {
System.out.println("事件通知,当前node节点" + path + "被删除....");
}
}
}
public static void main(String[] args) throws KeeperException, InterruptedException {
ZKCliWatcher zkClientWatcher = new ZKCliWatcher();
zkClientWatcher.createConnection("127.0.0.1:2181", SESSIONTIME);
// boolean createResult = zkClientWatcher.createPath("/p15", "pa-644064");
//zkClientWatcher.updateNode("/pa2","7894561");
zkClientWatcher.createPath("/kl","lk");
}
}