一:ZooKeeper的简介
1:什么是ZooKeeper
ZooKeeper是一个高效的分布式协调服务,它暴露了一些公用服务,比如命名/配置管理/同步控制/群组服务等。我们可以使用ZK来实现达成共识/集群管理/leader选举等。
ZooKeeper是一个高可用的分布式管理与协调框架,基于ZAB算法(原子消息广播协议)的实现。该框架能够很好地保证分布式环境中数据的一致性。也正是基于这样的特性,使得ZooKeeper成为了解决分布式一致性问题的利器。
顺序一致性:从一个客户端发起的事务请求,最终将会严格地按照其发起的顺序被应用到ZooKeeperr中去。
原子性:所有事务请求的处理结果在整个集群中的所有机器上的应用情况是一致的,也就是说要么整个集群所有的机器都成功应用了某一事务,要么都没有应用。
单一视图:无论客户端连接的是哪一个ZooKeeper服务器,其看到的服务器端数据模型都是一致的。
可靠性:一旦服务器成功应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务器端的状态将会被一致保留下来,除非有另外一个事务对其更改
实时性:通常所说的实时性就是指一旦事务被成功应用,那么客户端就能立刻从服务器上获取变更后的新数据
2:Zookeeper设计目标
目标一:简单的数据结构。ZooKeeper就是以简单的树形结构来进行相互协调的(也叫树形名字空间)
目标二:可以构建集群。一般zookeeper集群由一组机器构成,一般3~5台机器就可以组成一个ZooKeeper集群了。只要集群中超过半数以上的机器能够正常工作,那么整个集群就能够正常对外提供服务。
目标三:顺序访问。对于来自每一个客户端的每一个请求,ZooKeeper都会分配一个全局唯一的递增编号,这个编号反映了所有事务操作的先后顺序,应用程序可以使用ZooKeeper的这个特性来实现更好层次的同步。
目标四:高性能。由于ZooKeeper将全量数据存储在内存中,并直接服务与所有的非事务请求,因此尤其是在读操作为主的场景下性能非常突出。
3:ZooKeeper的数据模型
每个子目录项如NameService都被称为znode,这个znode是被它所在的路径唯一标识,如Server1这个znode的标识为/NameService/Service1
znode可以有子节点目录,并且每个znode可以存储数据,注意EPHEMERAL类型的目录节点不能有子节点目录。
znode是有版本的,每个znode中存储的数据可以有多个版本的,也就是一个访问路径中可以存储多份数据
znode可以是临时节点,一旦创建这个znode的客户端与服务器断开连接,这个znode将会自动删除,,Zookeeper的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为session,如果znode是临时节点,这个session失效,znode也就删除了。
znode的目录名可以自动编号,如App1已经存在,再创建的话将会自动命名为App2
znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是Zookeeper的核心特性,Zookeeper很多功能都是基于这个特性实现的
4:ZooKeeper的组成
Zookeeper server根据其身份特性分为三种:leader,Follower,Observer,其Follower和Observer又统称为leaner(学习者)。
4.1:leader:负责客户端的writer类型请求
4.2:Follower:负责客户端的reader类型请求,参与leader选举等
4.3:Observer:特殊的“Follower”,可以接受客户端的reader请求,但不参与选举
5:典型应用场景
ZooKeeper从设计模式角度来看,是一个基于观察者模式设计的分布式服务管理框架,它是一个基于观察这模式的分布式服务管理框架,它负责存储和管理大家都关系的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的哪些观察者做出相应的反应,从而实现集群中类似Master/Slave管理模式。
5.1:配置管理
配置管理:配置的管理在分布式应用环境中很常见,比如我们在平常的应用系统中,经常会碰到这样的需求:如机器的配置列表,运行时的开关配置,数据库配置信息等,这些全局配置信息通常具备以下三个特性
5.1.1:数据量比较小
5.1.2:数据内容在运行时动态大声变化
5.1.3:集群中各个集群共享信息,配置一致
5.2:集群管理
ZooKeeper不仅能够帮你维护当前的集群中机器的服务状态,而且能够帮你选出一个总管,让这个总管来管理集群,这就是zookeeper的另一个功能leader,并实现集群容错功能
5.2.1:希望知道当前集群中究竟有多少机器工作。
5.2.2:对集群中每天集群的运行时状态进行数据收集
5.2.3:对集群中每台机器进行上下线操作
5.3:发布与订阅
ZooKeeper是一个典型的发布/订阅模式的分布式数控管理与协调框架,开发人员可以使用它来进行分布式数据的分布和订阅
5.4:数据库切换
比如我们初始化ZooKeeper的时候读取其节点上的数据库配置文件,当配置一旦发生变更时,ZooKeeper就能帮助我们把变更的通知发送到各个客户端。
5.5:分布式日志的手机
我们可以做一个日志系统收集集群中所有的日志信息,进行统一管理
5.6:分布式锁,队列管理
ZooKeeper的特性就是在分布式场景下高可用,但是原生的API实现分布式功能非常困难,团队去实现也太浪费时间,即使实现了也未必稳定。那么采用第三方的客户端完美实现,比如Curator,它是Apache的顶级项目
6:zookeeper的开源框架应用
ZooKeeper的应用场景非常广泛,如Hadoop,Storm,消息中间件,RPC服务框架,数据库增量订阅与消费组件,分布式数据库同步系统。
二:ZooKeeper集群的搭建
需要先安装jdk,参考:
https://blog.csdn.net/qq_36297434/article/details/83928241
1:集群的搭建过程
下载地址:https://download.csdn.net/download/qq_36297434/11639588
1.1:准备三个节点
ZooKeeper服务器集群规模不小于三个节点,要求服务器之间系统时间保持一致,先安装jdk
1.2:进行解压
tar -xvf zookeeper-3.4.5.tar.gz
1.3:重命名
mv zookeeper-3.4.5 zookeeper
1.4:修改环境变量
vim /etc/profile
配置如下
export ZOOKEEPER_HOME=/opt/module/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
1.5:刷新
source /etc/profile
1.6:到zookeeper下修改配置文件
cd /opt/module/zookeeper/conf
mv zoo_sample.cfg zoo.cfg
1.7:修改conf,vim zoo.cfg
dataDir=/opt/module/zookeeper/data
server.0=192.168.206.11:2888:3888
server.1=192.168.206.12:2888:3888
server.2=192.168.206.13:2888:3888
1.8:服务器标识配置
mkdir /opt/module/zookeeper/data
cd /opt/module/zookeeper/data
vim myid //在myid中写0
1.9:复制zookeeper目录和profile到另外两台机器
1.10:把另外两台机器的myid文件中的值改为1和2
1.11:启动zookeeper
路径:/opt/module/zookeeper/bin
启动:zkServer.sh start //三台机器都要启动
重启: zkServer.sh restart
关闭: zkServer.sh stop
状态:zkServer.sh status
2:ZooKeeper操作shell
2.1:进入客户端
zkCli.sh
2.2:ZooKeeper的命令
查找:ls /
创建并赋值: create /name lwk
获取:get /name
设值:set /name qn
递归删除节点:rmr /name
删除某个节点:delete /name
注:创建节点有两点类型:短暂(ephemeral),持久(persistent)
2.2:退出客户端
quit
3:zoo.cfg配置详
tickTime:基本事件单元,以毫秒为单位。这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔 tickTime时间就会发送一个心跳。
dataDir:存储内存中数据库快照的位置,顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
initLimit:这个配置项是用来配置 Zookeeper 接受客户端初始化连接时最长能忍受多少个心跳时间间隔数,当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒。
syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10 秒
server.A = B:C:D :
A 表示这个是第几号服务器,
B 是这个服务器的 ip 地址;
C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;
D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader
3:eclipse的ZooKeeper插件
http://www.massedynamic.org/eclipse/updates/
三:Java原生API
1:jar包
需要jar包zookeeper.jar,客户端可以通过创建一个zookeeper实例来链接zookeeper服务器。
2:Zookeeper构造方法(一共四个构造方法),参数说明如下:
connectString:连接服务器列表,已“,”分割
sessionTimeout:心跳检测时间周期(ms)
wather:事件处理通知器
canBeResdOnly:标识当前会话是否支持只读
sessionId和sessionPassword:提供连接zookeeper的sessionId和密码,通过这两个确定唯一的客户端,目的是提供重复会话
3:客户端的建立
ZooKeeper客户端和服务器端的建立是一个异步的过程,也就是在程序中,我们程序方法在处理完客户端初始化后立即返回(也就是说程序继续往下执行,这样,大多数情况下我们并没有真正构建好一个可用会话,在会话的生命周期处于“CONNECTING”时才算真正建立会话,所以需要使用一个多线程的工具类)。
4:Watch事件
ZooKeeper有Watch事件,是一次性触发的,当Watch监视的数据发生变化时,通知设置了该Watch的client,即Watcher。
同样,其Watcher是监听数据发送了某些变化,那就一定会有对应的事件类型和状态类型。
事件类型(znode节点相关):
EventType.NodeCreated
EventType.NodeDataChanged
EventType.NodeChildrenChanged
EventType.NodeDelete
状态类型(跟客户端相关):
KeeperState.Disconnected
KeeperState.SyncConnected
KeeperState.AuthFailed
KeeperState.Expired
5:Watcher的特性
一次性:对于ZooKeeper的Watcher,你只需记住一点:ZooKeeper有Watcher事件,是一次性触发的,当Watcher监视的数据发生变化时,通知设置了该Watch的client,即watcher,由于ZooKeeper的监控都是一次性的,所以每次必须设置监控
客户端串行执行:客户端Watcher回调的过程是一个串行同步的过程,这为我们保证了顺序。
轻量:WatchedEvent是ZooKeeper整个Watcher机制的最小通知单元,整个结构包括三个部分:通知状态,事件类型和节点路径。也就是说Watcher通知非常的简单,只会告诉客户端发生了事件,而不会告知具体内容,需要客户端自己去获取。
6:创建节点
创建节点(znode)方法:create,提供了两套创建节点的方法,同步和异步创建节点方式
同步方式:
参数1:节点路径(名称),/nodeName(不允许递归创建节点)
参数2:节点内容,要求内容是字节数组(就是说不支持序列化方式,如果需要实现序列化,可使用相关序列化框架,如Hessian,Kryo)
参数3:节点权限,使用Ids.OPEN_ACL_UNSAFE开放权限即可。(这个参数一般在权限没有太高要求的场景下,没必要关注)
参数4:节点类型,创建节点的类型:CreateMode.*,提供四种节点类型
PERSISTENT(持久节点)
PERSISTENT_SEQUENTIAL(持久顺序节点)
EPHEMERAL(临时节点)
EPHEMERAL_SEQUENTIAL(临时顺序节点)
异步方式:
参数5:注册一个异步回调函数,要实现AsynCallBack.StringCallBack接口,重写processResult(int rc,String path,Object ctx,String name)方法,当节点创建完毕后执行此方法
rc:为服务端响应码0表示调用成功,-4表示端口连接,-110表示指定节点存在
path:接口调用时传入API的数据节点的路径参数
ctx:为调用接口传入API的stx值
name:实际在服务器端创建节点的名称
参数6:传递给回调函数的参数,一般为上下文(Context)信息
zookeeper.create("/name","lwk".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT,new MyCallBack(),"I am lwk.");
class MyCallBack implements AsyncCallback.StringCallback{
@Override
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println("Create path result: [" + rc + ", " + path + ", " + ctx + ", real path name: " + name);
}
}
7:核心代码
package lwk.zookeeper.watcher;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
/**
* Zookeeper Wathcher
* 本类就是一个Watcher类(实现了org.apache.zookeeper.Watcher类)
* @author(alienware)
* @since 2015-6-14
*/
public class ZooKeeperWatcher implements Watcher {
/** 定义原子变量 */
AtomicInteger seq = new AtomicInteger();
/** 定义session失效时间 */
private static final int SESSION_TIMEOUT = 10000;
/** zookeeper服务器地址 */
private static final String CONNECTION_ADDR = "192.168.1.121:2181,192.168.1.122:2181,192.168.1.123:2181";
/** zk父路径设置 */
private static final String PARENT_PATH = "/p";
/** zk子路径设置 */
private static final String CHILDREN_PATH = "/p/c";
/** 进入标识 */
private static final String LOG_PREFIX_OF_MAIN = "【Main】";
/** zk变量 */
private ZooKeeper zk = null;
/**用于等待zookeeper连接建立之后 通知阻塞程序继续向下执行 */
private CountDownLatch connectedSemaphore = new CountDownLatch(1);
/**
* 创建ZK连接
* @param connectAddr ZK服务器地址列表
* @param sessionTimeout Session超时时间
*/
public void createConnection(String connectAddr, int sessionTimeout) {
this.releaseConnection();
try {
//this表示把当前对象进行传递到其中去(也就是在主函数里实例化的new ZooKeeperWatcher()实例对象)
zk = new ZooKeeper(connectAddr, sessionTimeout, this);
System.out.println(LOG_PREFIX_OF_MAIN + "开始连接ZK服务器");
connectedSemaphore.await();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭ZK连接
*/
public void releaseConnection() {
if (this.zk != null) {
try {
this.zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 创建节点
* @param path 节点路径
* @param data 数据内容
* @return
*/
public boolean createPath(String path, String data, boolean needWatch) {
try {
//设置监控(由于zookeeper的监控都是一次性的所以 每次必须设置监控)
this.zk.exists(path, needWatch);
System.out.println(LOG_PREFIX_OF_MAIN + "节点创建成功, Path: " +
this.zk.create( /**路径*/
path,
/**数据*/
data.getBytes(),
/**所有可见*/
Ids.OPEN_ACL_UNSAFE,
/**永久存储*/
CreateMode.PERSISTENT ) +
", content: " + data);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 读取指定节点数据内容
* @param path 节点路径
* @return
*/
public String readData(String path, boolean needWatch) {
try {
System.out.println("读取数据操作...");
return new String(this.zk.getData(path, needWatch, null));
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* 更新指定节点数据内容
* @param path 节点路径
* @param data 数据内容
* @return
*/
public boolean writeData(String path, String data) {
try {
System.out.println(LOG_PREFIX_OF_MAIN + "更新数据成功,path:" + path + ", stat: " +
this.zk.setData(path, data.getBytes(), -1));
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除指定节点
*
* @param path
* 节点path
*/
public void deleteNode(String path) {
try {
this.zk.delete(path, -1);
System.out.println(LOG_PREFIX_OF_MAIN + "删除节点成功,path:" + path);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判断指定节点是否存在
* @param path 节点路径
*/
public Stat exists(String path, boolean needWatch) {
try {
return this.zk.exists(path, needWatch);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取子节点
* @param path 节点路径
*/
private List<String> getChildren(String path, boolean needWatch) {
try {
System.out.println("读取子节点操作...");
return this.zk.getChildren(path, needWatch);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 删除所有节点
*/
public void deleteAllTestPath(boolean needWatch) {
if(this.exists(CHILDREN_PATH, needWatch) != null){
this.deleteNode(CHILDREN_PATH);
}
if(this.exists(PARENT_PATH, needWatch) != null){
this.deleteNode(PARENT_PATH);
}
}
/**
* 收到来自Server的Watcher通知后的处理。
*/
@Override
public void process(WatchedEvent event) {
System.out.println("进入 process 。。。。。event = " + event);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (event == null) {
return;
}
// 连接状态
KeeperState keeperState = event.getState();
// 事件类型
EventType eventType = event.getType();
// 受影响的path
String path = event.getPath();
//原子对象seq 记录进入process的次数
String logPrefix = "【Watcher-" + this.seq.incrementAndGet() + "】";
System.out.println(logPrefix + "收到Watcher通知");
System.out.println(logPrefix + "连接状态:\t" + keeperState.toString());
System.out.println(logPrefix + "事件类型:\t" + eventType.toString());
if (KeeperState.SyncConnected == keeperState) {
// 成功连接上ZK服务器
if (EventType.None == eventType) {
System.out.println(logPrefix + "成功连接上ZK服务器");
connectedSemaphore.countDown();
}
//创建节点
else if (EventType.NodeCreated == eventType) {
System.out.println(logPrefix + "节点创建");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//更新节点
else if (EventType.NodeDataChanged == eventType) {
System.out.println(logPrefix + "节点数据更新");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//更新子节点
else if (EventType.NodeChildrenChanged == eventType) {
System.out.println(logPrefix + "子节点变更");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//删除节点
else if (EventType.NodeDeleted == eventType) {
System.out.println(logPrefix + "节点 " + path + " 被删除");
}
else ;
}
else if (KeeperState.Disconnected == keeperState) {
System.out.println(logPrefix + "与ZK服务器断开连接");
}
else if (KeeperState.AuthFailed == keeperState) {
System.out.println(logPrefix + "权限检查失败");
}
else if (KeeperState.Expired == keeperState) {
System.out.println(logPrefix + "会话失效");
}
else ;
System.out.println("--------------------------------------------");
}
/**
* <B>方法名称:</B>测试zookeeper监控<BR>
* <B>概要说明:</B>主要测试watch功能<BR>
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//建立watcher //当前客户端可以称为一个watcher 观察者角色
ZooKeeperWatcher zkWatch = new ZooKeeperWatcher();
//创建连接
zkWatch.createConnection(CONNECTION_ADDR, SESSION_TIMEOUT);
//System.out.println(zkWatch.zk.toString());
Thread.sleep(1000);
// 清理节点
zkWatch.deleteAllTestPath(false);
//-----------------第一步: 创建父节点 /p ------------------------//
if (zkWatch.createPath(PARENT_PATH, System.currentTimeMillis() + "", true)) {
Thread.sleep(1000);
//-----------------第二步: 读取节点 /p 和 读取/p节点下的子节点(getChildren)的区别 --------------//
// 读取数据
zkWatch.readData(PARENT_PATH, true);
// 读取子节点(监控childNodeChange事件)
zkWatch.getChildren(PARENT_PATH, true);
// 更新数据
zkWatch.writeData(PARENT_PATH, System.currentTimeMillis() + "");
Thread.sleep(1000);
// 创建子节点
zkWatch.createPath(CHILDREN_PATH, System.currentTimeMillis() + "", true);
//-----------------第三步: 建立子节点的触发 --------------//
// zkWatch.createPath(CHILDREN_PATH + "/c1", System.currentTimeMillis() + "", true);
// zkWatch.createPath(CHILDREN_PATH + "/c1/c2", System.currentTimeMillis() + "", true);
//-----------------第四步: 更新子节点数据的触发 --------------//
//在进行修改之前,我们需要watch一下这个节点:
Thread.sleep(1000);
zkWatch.readData(CHILDREN_PATH, true);
zkWatch.writeData(CHILDREN_PATH, System.currentTimeMillis() + "");
}
Thread.sleep(10000);
// 清理节点
zkWatch.deleteAllTestPath(false);
Thread.sleep(10000);
zkWatch.releaseConnection();
}
}
四:设置授权
1:ACL
ACL(Access Control List),zookeeper作为一个分布式协调框架,其内都是一些关乎分布式系统运行时状态的元数据,尤其是涉及到一些分布式锁,选举和协调等应用场景。我们需要有效的保证zookeeper中的数据安全,zookeeper提供了一套完善的ACL权限控制机制来保障数据安全。
2:三种授权模式
2.1:四种权限模式(scheme)
IP:IP模式通过IP地址粒度进行控制权限,例如配置了IP:192.168.1.107即表示权限控制都是针对这个IP地址的,同时也支持按网段分配,比如192.168.1.*。
Dlgest:Dlgest是最常用的权限控制模式,也更符合我们对权限控制的认识,其类似于“username password”形式的权限标识进行权限配置。zookeeper会对形成的权限标识先后进行两次编码处理,分别是SHA-1加密算法,BASE64编码。
World:World是一个最开放的权限控制模式。这种模式可以看作特殊的Dlgest,它仅仅是一个标识而已。
Super:超级用户模式,在超级用户模式下可以对zookeeper任意进行操作。
2.2:权限对象
指的是权限赋予的用户或者一个指定的实体,例如IP地址或者机器等。在不同的模式下,授权对象是不同的。这种模式和权限对象一一对应
2.3:权限
权限就是指那些通过权限检测后的可以被允许执行的操作。在zookeeper中,对数据的操作权限分为五大类
CREATE,DELETE,READ,WRITE,ADMIN
2.3:核心代码
package lwk.zookeeper.auth;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
/**
* Zookeeper 节点授权
* @author(alienware)
* @since 2015-6-14
*/
public class ZookeeperAuth implements Watcher {
/** 连接地址 */
final static String CONNECT_ADDR = "192.168.80.88:2181";
/** 测试路径 */
final static String PATH = "/testAuth";
final static String PATH_DEL = "/testAuth/delNode";
/** 认证类型 */
final static String authentication_type = "digest";
/** 认证正确方法 */
final static String correctAuthentication = "123456";
/** 认证错误方法 */
final static String badAuthentication = "654321";
static ZooKeeper zk = null;
/** 计时器 */
AtomicInteger seq = new AtomicInteger();
/** 标识 */
private static final String LOG_PREFIX_OF_MAIN = "【Main】";
private CountDownLatch connectedSemaphore = new CountDownLatch(1);
@Override
public void process(WatchedEvent event) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (event==null) {
return;
}
// 连接状态
KeeperState keeperState = event.getState();
// 事件类型
EventType eventType = event.getType();
// 受影响的path
String path = event.getPath();
String logPrefix = "【Watcher-" + this.seq.incrementAndGet() + "】";
System.out.println(logPrefix + "收到Watcher通知");
System.out.println(logPrefix + "连接状态:\t" + keeperState.toString());
System.out.println(logPrefix + "事件类型:\t" + eventType.toString());
if (KeeperState.SyncConnected == keeperState) {
// 成功连接上ZK服务器
if (EventType.None == eventType) {
System.out.println(logPrefix + "成功连接上ZK服务器");
connectedSemaphore.countDown();
}
} else if (KeeperState.Disconnected == keeperState) {
System.out.println(logPrefix + "与ZK服务器断开连接");
} else if (KeeperState.AuthFailed == keeperState) {
System.out.println(logPrefix + "权限检查失败");
} else if (KeeperState.Expired == keeperState) {
System.out.println(logPrefix + "会话失效");
}
System.out.println("--------------------------------------------");
}
/**
* 创建ZK连接
*
* @param connectString
* ZK服务器地址列表
* @param sessionTimeout
* Session超时时间
*/
public void createConnection(String connectString, int sessionTimeout) {
this.releaseConnection();
try {
zk = new ZooKeeper(connectString, sessionTimeout, this);
//添加节点授权
zk.addAuthInfo(authentication_type,correctAuthentication.getBytes());
System.out.println(LOG_PREFIX_OF_MAIN + "开始连接ZK服务器");
//倒数等待
connectedSemaphore.await();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭ZK连接
*/
public void releaseConnection() {
if (this.zk!=null) {
try {
this.zk.close();
} catch (InterruptedException e) {
}
}
}
/**
*
* <B>方法名称:</B>测试函数<BR>
* <B>概要说明:</B>测试认证<BR>
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
ZookeeperAuth testAuth = new ZookeeperAuth();
testAuth.createConnection(CONNECT_ADDR,2000);
List<ACL> acls = new ArrayList<ACL>(1);
for (ACL ids_acl : Ids.CREATOR_ALL_ACL) {
acls.add(ids_acl);
}
try {
zk.create(PATH, "init content".getBytes(), acls, CreateMode.PERSISTENT);
System.out.println("使用授权key:" + correctAuthentication + "创建节点:"+ PATH + ", 初始内容是: init content");
} catch (Exception e) {
e.printStackTrace();
}
try {
zk.create(PATH_DEL, "will be deleted! ".getBytes(), acls, CreateMode.PERSISTENT);
System.out.println("使用授权key:" + correctAuthentication + "创建节点:"+ PATH_DEL + ", 初始内容是: init content");
} catch (Exception e) {
e.printStackTrace();
}
// 获取数据
getDataByNoAuthentication();
getDataByBadAuthentication();
getDataByCorrectAuthentication();
// 更新数据
updateDataByNoAuthentication();
updateDataByBadAuthentication();
updateDataByCorrectAuthentication();
// 删除数据
deleteNodeByBadAuthentication();
deleteNodeByNoAuthentication();
deleteNodeByCorrectAuthentication();
//
Thread.sleep(1000);
deleteParent();
//释放连接
testAuth.releaseConnection();
}
/** 获取数据:采用错误的密码 */
static void getDataByBadAuthentication() {
String prefix = "[使用错误的授权信息]";
try {
ZooKeeper badzk = new ZooKeeper(CONNECT_ADDR, 2000, null);
//授权
badzk.addAuthInfo(authentication_type,badAuthentication.getBytes());
Thread.sleep(2000);
System.out.println(prefix + "获取数据:" + PATH);
System.out.println(prefix + "成功获取数据:" + badzk.getData(PATH, false, null));
} catch (Exception e) {
System.err.println(prefix + "获取数据失败,原因:" + e.getMessage());
}
}
/** 获取数据:不采用密码 */
static void getDataByNoAuthentication() {
String prefix = "[不使用任何授权信息]";
try {
System.out.println(prefix + "获取数据:" + PATH);
ZooKeeper nozk = new ZooKeeper(CONNECT_ADDR, 2000, null);
Thread.sleep(2000);
System.out.println(prefix + "成功获取数据:" + nozk.getData(PATH, false, null));
} catch (Exception e) {
System.err.println(prefix + "获取数据失败,原因:" + e.getMessage());
}
}
/** 采用正确的密码 */
static void getDataByCorrectAuthentication() {
String prefix = "[使用正确的授权信息]";
try {
System.out.println(prefix + "获取数据:" + PATH);
System.out.println(prefix + "成功获取数据:" + zk.getData(PATH, false, null));
} catch (Exception e) {
System.out.println(prefix + "获取数据失败,原因:" + e.getMessage());
}
}
/**
* 更新数据:不采用密码
*/
static void updateDataByNoAuthentication() {
String prefix = "[不使用任何授权信息]";
System.out.println(prefix + "更新数据: " + PATH);
try {
ZooKeeper nozk = new ZooKeeper(CONNECT_ADDR, 2000, null);
Thread.sleep(2000);
Stat stat = nozk.exists(PATH, false);
if (stat!=null) {
nozk.setData(PATH, prefix.getBytes(), -1);
System.out.println(prefix + "更新成功");
}
} catch (Exception e) {
System.err.println(prefix + "更新失败,原因是:" + e.getMessage());
}
}
/**
* 更新数据:采用错误的密码
*/
static void updateDataByBadAuthentication() {
String prefix = "[使用错误的授权信息]";
System.out.println(prefix + "更新数据:" + PATH);
try {
ZooKeeper badzk = new ZooKeeper(CONNECT_ADDR, 2000, null);
//授权
badzk.addAuthInfo(authentication_type,badAuthentication.getBytes());
Thread.sleep(2000);
Stat stat = badzk.exists(PATH, false);
if (stat!=null) {
badzk.setData(PATH, prefix.getBytes(), -1);
System.out.println(prefix + "更新成功");
}
} catch (Exception e) {
System.err.println(prefix + "更新失败,原因是:" + e.getMessage());
}
}
/**
* 更新数据:采用正确的密码
*/
static void updateDataByCorrectAuthentication() {
String prefix = "[使用正确的授权信息]";
System.out.println(prefix + "更新数据:" + PATH);
try {
Stat stat = zk.exists(PATH, false);
if (stat!=null) {
zk.setData(PATH, prefix.getBytes(), -1);
System.out.println(prefix + "更新成功");
}
} catch (Exception e) {
System.err.println(prefix + "更新失败,原因是:" + e.getMessage());
}
}
/**
* 不使用密码 删除节点
*/
static void deleteNodeByNoAuthentication() throws Exception {
String prefix = "[不使用任何授权信息]";
try {
System.out.println(prefix + "删除节点:" + PATH_DEL);
ZooKeeper nozk = new ZooKeeper(CONNECT_ADDR, 2000, null);
Thread.sleep(2000);
Stat stat = nozk.exists(PATH_DEL, false);
if (stat!=null) {
nozk.delete(PATH_DEL,-1);
System.out.println(prefix + "删除成功");
}
} catch (Exception e) {
System.err.println(prefix + "删除失败,原因是:" + e.getMessage());
}
}
/**
* 采用错误的密码删除节点
*/
static void deleteNodeByBadAuthentication() throws Exception {
String prefix = "[使用错误的授权信息]";
try {
System.out.println(prefix + "删除节点:" + PATH_DEL);
ZooKeeper badzk = new ZooKeeper(CONNECT_ADDR, 2000, null);
//授权
badzk.addAuthInfo(authentication_type,badAuthentication.getBytes());
Thread.sleep(2000);
Stat stat = badzk.exists(PATH_DEL, false);
if (stat!=null) {
badzk.delete(PATH_DEL, -1);
System.out.println(prefix + "删除成功");
}
} catch (Exception e) {
System.err.println(prefix + "删除失败,原因是:" + e.getMessage());
}
}
/**
* 使用正确的密码删除节点
*/
static void deleteNodeByCorrectAuthentication() throws Exception {
String prefix = "[使用正确的授权信息]";
try {
System.out.println(prefix + "删除节点:" + PATH_DEL);
Stat stat = zk.exists(PATH_DEL, false);
if (stat!=null) {
zk.delete(PATH_DEL, -1);
System.out.println(prefix + "删除成功");
}
} catch (Exception e) {
System.out.println(prefix + "删除失败,原因是:" + e.getMessage());
}
}
/**
* 使用正确的密码删除节点
*/
static void deleteParent() throws Exception {
try {
Stat stat = zk.exists(PATH_DEL, false);
if (stat == null) {
zk.delete(PATH, -1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
五:ZkClient
ZkClient是在原生API的接口上进行了封装,简化了zookeeper的复杂性
1:创建客户端的方法:ZkClient(Arguments)
参数1:zkServers zookeeper服务器的地址,用“,”分割
参数2:session Timeout 超时会话,为毫秒,默认为30000ms
参数3:connectionTimeout 连接超时会话
参数4:IZkConnection接口的实现类
参数5:zkSeriallzer 自定义序列化实现
2:核心代码
2.1:基础API
package lwk.zkclient.base;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.ZkConnection;
public class ZkClientBase {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181";
/** session超时时间 */
static final int SESSION_OUTTIME = 5000;//ms
public static void main(String[] args) throws Exception {
ZkClient zkc = new ZkClient(new ZkConnection(CONNECT_ADDR), 5000);
//1. create and delete方法
zkc.createEphemeral("/temp");
zkc.createPersistent("/super/c1", true);
Thread.sleep(10000);
zkc.delete("/temp");
zkc.deleteRecursive("/super");
//2. 设置path和data 并且读取子节点和每个节点的内容
zkc.createPersistent("/super", "1234");
zkc.createPersistent("/super/c1", "c1内容");
zkc.createPersistent("/super/c2", "c2内容");
List<String> list = zkc.getChildren("/super");
for(String p : list){
System.out.println(p);
String rp = "/super/" + p;
String data = zkc.readData(rp);
System.out.println("节点为:" + rp + ",内容为: " + data);
}
//3. 更新和判断节点是否存在
zkc.writeData("/super/c1", "新内容");
System.out.println(zkc.readData("/super/c1"));
System.out.println(zkc.exists("/super/c1"));
//4.递归删除/super内容
zkc.deleteRecursive("/super");
}
}
2.2:Watcher
package lwk.zkclient.watcher;
import java.util.List;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.ZkConnection;
public class ZkClientWatcher1 {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181";
/** session超时时间 */
static final int SESSION_OUTTIME = 5000;//ms
public static void main(String[] args) throws Exception {
ZkClient zkc = new ZkClient(new ZkConnection(CONNECT_ADDR), 5000);
//对父节点添加监听子节点变化。只监听父节点和子节点的删除和数据变化
zkc.subscribeChildChanges("/super", new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("parentPath: " + parentPath);
System.out.println("currentChilds: " + currentChilds);
}
});
Thread.sleep(3000);
zkc.createPersistent("/super");
Thread.sleep(1000);
zkc.createPersistent("/super" + "/" + "c1", "c1内容");
Thread.sleep(1000);
zkc.createPersistent("/super" + "/" + "c2", "c2内容");
Thread.sleep(1000);
zkc.delete("/super/c2");
Thread.sleep(1000);
zkc.deleteRecursive("/super");
Thread.sleep(Integer.MAX_VALUE);
}
}
package lwk.zkclient.watcher;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.ZkConnection;
public class ZkClientWatcher2 {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.171:2181,192.168.1.172:2181,192.168.1.173:2181";
/** session超时时间 */
static final int SESSION_OUTTIME = 5000;//ms
public static void main(String[] args) throws Exception {
ZkClient zkc = new ZkClient(new ZkConnection(CONNECT_ADDR), 5000);
zkc.createPersistent("/super", "1234");
//对父节点添加监听子节点变化。只监听父节点和子节点的添加和删除
zkc.subscribeDataChanges("/super", new IZkDataListener() {
@Override
public void handleDataDeleted(String path) throws Exception {
System.out.println("删除的节点为:" + path);
}
@Override
public void handleDataChange(String path, Object data) throws Exception {
System.out.println("变更的节点为:" + path + ", 变更内容为:" + data);
}
});
Thread.sleep(3000);
zkc.writeData("/super", "456", -1);
Thread.sleep(1000);
zkc.delete("/super");
Thread.sleep(Integer.MAX_VALUE);
}
}