目录
Zookeeper的基本使用及概述(四) ------ zookeeper api的使用
之前对使用命令行操作zookeeper进行了说明明,这里介绍怎么使用java api来对zookeeper进行操作,以及对zookeeper原生api进行简单的封装。
1. 开发环境准备
Java版本: jdk 1.8
开发工具: idea 2019.3.4
maven: 3.6.1
zookeeper: 3.5.10
linux发行版:centos 7
创建maven工程引入Zookeeper依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.10</version>
</dependency>
2. 代码编写
(1) 编写 IZookpConnection接口
这个接口的主要目的是用来连接zookeeper服务并提供一些基本的API用以操作zookeeper。
/**
* @Author 笔墨画诗
* @Version 1.0.0
* @Create 2022/9/27 18:46
* @Desc Zookeeper连接以及基本操作api
*/
public interface IZookpConnection {
/**
* 获取zookeeper对象
* @return
* @throws IOException
*/
ZooKeeper getZooKeeper() throws IOException;
/**
* 创建数据节点
* @param path
* @param data
* @param mode
* @throws KeeperException
* @throws InterruptedException
*/
void create(String path, String data, CreateMode mode) throws KeeperException, InterruptedException;
/**
* 获取直系子节点列表
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
List<String> getChildrenNode(String path) throws KeeperException, InterruptedException;
/**
* 更新节点数据
* @param path
* @param data
* @throws KeeperException
* @throws InterruptedException
*/
void setData(String path, String data) throws KeeperException, InterruptedException;
/**
* 获取节点数据
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
String getData(String path) throws KeeperException, InterruptedException;
/**
* 删除节点
* @param path
* @throws KeeperException
* @throws InterruptedException
*/
void delete(String path) throws KeeperException, InterruptedException;
}
(2) 实现 IZookpConnection接口
对IZookpConnection接口进行实现。
/**
* @Author 笔墨画诗
* @Version 1.0.0
* @Create 2022/9/29 18:49
* @Desc 默认实现
*/
public class DefaultZookpConnectionImpl implements IZookpConnection {
public static final int DEFAULT_TIMEOUT = 100000; // 默认超时时间,一定要大于心跳时间
private ZooKeeper zooKeeper; // 实际操作zookeeper的对象
private String url; // 将要连接的服务器地址
private Watcher watcher; // 事件监听器
private int timeout; // 超时时间
public DefaultZookpConnectionImpl(String url, Watcher watcher){
this(url,watcher,DEFAULT_TIMEOUT);
}
public DefaultZookpConnectionImpl(String url, Watcher watcher,int timeout){
this.url = url;
this.watcher = watcher;
this.timeout = timeout;
connect();
}
@Override
public ZooKeeper getZooKeeper() {
return this.zooKeeper;
}
/**
* 连接zookeeper服务器
*/
private void connect() {
try {
this.zooKeeper = new ZooKeeper(url,timeout,watcher);
} catch (IOException e) {
throw new ZookpException("Zookeeper Connect Failed",e);
}
}
@Override
public void create(String path, String data, CreateMode mode) throws KeeperException, InterruptedException {
ArrayList<ACL> acl = ZooDefs.Ids.OPEN_ACL_UNSAFE;
/**
* path : 节点创建的路径
* data[] :节点创建要保存的数据,是个byte类型的
* acl : 节点创建的权限信息(4种类型)
* ANYONE_ID_UNSAFE : world模式设置id
* AUTH_IDS : 为auth模式设置的ID,代表了任何已经被确认的用户。它将被客户机验证的ID替换。
* OPEN_ACL_UNSAFE : 这是⼀个完全开放的ACL(常⽤)-->
* CREATOR_ALL_ACL : 此ACL授予创建者身份验证ID的所有权限
* mode :创建节点的类型(4种类型)
* PERSISTENT: 持久节点
* PERSISTENT_SEQUENTIAL:持久顺序节点
* EPHEMERAL: 临时节点
* EPHEMERAL_SEQUENTIAL: 临时顺序节点
* this.zookeeper.create(path,data,acl,mode);
*/
this.zooKeeper.create(path,data.getBytes(),acl,mode);
}
@Override
public List<String> getChildrenNode(String path) throws KeeperException, InterruptedException {
/**
* 获取子节点
* path: 节点路径
* watch: 是否监听 当节点数据发生变华会触发监听
* true: 表示开启监听
* false: 不开启监听
* zooKeeper.getChildren(path, true);
*/
List<String> childNodes = zooKeeper.getChildren(path, true);
return childNodes== null ? Collections.EMPTY_LIST : childNodes;
}
@Override
public void setData(String path, String data) throws KeeperException, InterruptedException {
zooKeeper.setData(path,data.getBytes(),-1); // -1表示使用最新的数据
}
@Override
public String getData(String path) throws KeeperException, InterruptedException {
/**
* 获取节点数据
* path: 节点路径
* watch: 是否监听 true 表示开启监听 false 不开启监听
* stat: 节点状态
* null: 表示获取最新数据
* zooKeeper.getData(path,false,null);
*/
byte[] data = zooKeeper.getData(path, false, null);
return new String(data,"utf-8");
}
@Override
public void delete(String path) throws KeeperException, InterruptedException {
/**
* 判断节点是否存在
* path: 节点路径
* watch: 是否监听 true 表示开启监听 false 不开启监听
* zooKeeper.exists(path, true);
*/
Stat exists = this.zooKeeper.exists(path, false);
if (exists == null){
throw new ZookpException("Node Path " + path + " is Not Exist");
}
this.zooKeeper.delete(path,-1);
}
}
(3) ZookpClient类
定义一个zookeeper客户端用以对外提供服务,实际上只是对zookeeper api的简单封装。
另外,ZooKeeper 客户端和服务端会话的建⽴是⼀个异步的过程,也就是说在程序中,构造⽅法会在处理完客户端初始化⼯作后⽴即返回,在⼤多数情况下,此时并没有真正建⽴好⼀个可⽤的会话,在会话的⽣命周期中处于“CONNECTING”的状态。 当该会话真正创建完毕后ZooKeeper服务端会向会话对应的客户端发送⼀个事件通知Watcher,以告知客户端,客户端只有在获取这个通知之后,才算真正建⽴了会话。
/**
* @Author 笔墨画诗
* @Version 1.0.0
* @Create 2022/9/27 18:25
* @Desc zookeeper 自定义客户端
*
*/
public class ZookpClient implements Watcher{
private IZookpConnection connection; // 连接zookeeper服务
private static String serverAddr; // 服务端地址
private static int timeout; // 超时时间
// 通过读取配置文件初始化参数
static {
InputStream rs = null;
try {
rs = ZookpClient.class.getClassLoader().getResourceAsStream("zookp_cfg.properties");
Properties properties = new Properties();
properties.load(rs);
serverAddr = properties.getProperty("serverAddr");
timeout = Integer.parseInt(properties.getProperty("timeout"));
} catch (IOException e) {
throw new ZookpException(e.getMessage(),e);
}finally {
try {
rs.close();
} catch (IOException e) {
throw new ZookpException(e.getMessage(),e);
}
}
}
public ZookpClient(){
if (serverAddr == null || "".equals(serverAddr)){
throw new ZookpException("Client Create Failed,URL is Empty or NULL!");
}
this.connection = new DefaultZookpConnectionImpl(serverAddr,this,timeout);
}
/**
* 创建节点, 当flag为true时,递归创建其父节点
* @param path
* @param data
* @param mode
* @param flag
*/
public void createNode(String path, String data, CreateMode mode,boolean flag){
System.out.println("Create Node Start!");
try {
this.connection.create(path,data,mode);
} catch (KeeperException e) {
String parentNode = path.substring(0, path.lastIndexOf(47));
if (!flag){
throw new ZookpException("Create Node Failed, Parent Node:" + parentNode + "is Not Exist",e);
}
createNode(parentNode,"",mode,flag);
createNode(path,data,mode,flag);
} catch (InterruptedException e) {
throw new ZookpException(e.getMessage(),e);
}
System.out.println("Create Node Done!");
}
/**
* 删除节点, 当flag为true时,遍历删除子节点
* @param path
* @param flag
* @throws KeeperException
* @throws InterruptedException
*/
public void deleteNode(String path,boolean flag) throws KeeperException, InterruptedException {
try {
connection.delete(path);
} catch (KeeperException e) {
if (!flag){
throw new ZookpException("Node Delete Failed,Path is" + path,e);
}
List<String> childrenNode = connection.getChildrenNode(path);
childrenNode.forEach(node->{
String nodePath = path + "/" + node;
try {
deleteNode(nodePath,flag);
} catch (KeeperException ex) {
throw new ZookpException("Node Delete Failed,Path is /" + node,ex);
} catch (InterruptedException ex) {
throw new ZookpException("Node Delete Failed,Path is /" + node,ex);
}
});
// 子节点删除完后,再执行删除父节点,还是递归
deleteNode(path,flag);
} catch (InterruptedException e) {
throw new ZookpException("Node Delete Failed,Path is" + path,e);
}
}
public void setNodeData(String path,String data) throws KeeperException, InterruptedException {
connection.setData(path,data);
}
public void getNodeData(String path,String data) throws KeeperException, InterruptedException {
connection.getData(path);
}
public List<String> getChildrenNode(String path) throws KeeperException, InterruptedException {
return connection.getChildrenNode(path);
}
@Override
public void process(WatchedEvent watchedEvent) {
// 当这个方法被触发时,才证明客户端会话连接成功
if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
System.out.println("Zookeeper Server is Connected!");
}
}
}
(4) 配置文件
在resources 目录下创建文件 zookp_cfg_properties,写入超时时间和服务器地址。
serverAddr=192.168.135.80:2181,192.168.135.80:2182,192.168.135.80:2183
timeout=300000
(5) 异常统一处理
/**
* @Author 笔墨画诗
* @Version 1.0.0
* @Create 2022/9/29 19:06
* @Desc 异常类
*/
public class ZookpException extends RuntimeException{
private static final long serialVersionUID = 8242584020618974429L;
public ZookpException() {
}
public ZookpException(String message, Throwable cause) {
super(message, cause);
}
public ZookpException(String message) {
super(message);
}
public ZookpException(Throwable cause) {
super(cause);
}
}
3 测试
接下来对测试一下自定义的zookeeper客户端。
编写测试类
/**
* @Author 笔墨画诗
* @Version 1.0.0
* @Create 2022/9/29 19:39
* @Desc
*/
public class ZookpTest {
private ZookpClient zookpClient = new ZookpClient();
private String testPath = "/ZookpClient/Test/Version/V1.0/Data";
@Test
public void createNodeTest() throws InterruptedException {
zookpClient.createNode(testPath,"test", CreateMode.PERSISTENT,true);
zookpClient.createNode("/ZookpClient/Demo","demo", CreateMode.PERSISTENT,false);
}
@Test
public void deleteNodeTest() throws InterruptedException, KeeperException {
zookpClient.deleteNode("/ZookpClient",true);
}
@Test
public void getChildrenNode() throws KeeperException, InterruptedException {
List<String> childrenNode = zookpClient.getChildrenNode("/ZookpClient");
childrenNode.forEach(node-> System.out.println("/ZookpClient 的直系子节点" + node));
}
@Test
public void getNodeDateTest() throws InterruptedException, KeeperException {
String data = zookpClient.getNodeData(testPath);
System.out.println(testPath + "节点的数据为: " + data);
}
@Test
public void setNodeDataTest() throws KeeperException, InterruptedException {
String data = zookpClient.getNodeData(testPath);
System.out.println(testPath + "节点的数据为: " + data);
zookpClient.setNodeData("/ZookpClient/Test/Version/V1.0/Data","Data");
String dataNew = zookpClient.getNodeData(testPath);
System.out.println(testPath + "更新后节点的数据为: " + dataNew);
}
}
(1) 测试创建节点
因为zookeeper在创建节点时,该节点的父节点必须存在,父节点不存在则会创建失败,具体如下:
[zk: localhost:2181(CONNECTED) 0] create /ZookpClient/Test/Version/V1.0/Data 000
Node does not exist: /ZookpClient/Test/Version/V1.0/Data
[zk: localhost:2181(CONNECTED) 1]
所以当flag参数为false时则会抛出异常,为true时就会使用递归的方式,去创建节点,即当发现父节点不存在时先去创建父节点
zookpClient.createNode(testPath,"test", CreateMode.PERSISTENT,true);
创建结果
在客户端查看创建结果,如下:
[zk: localhost:2181(CONNECTED) 1] ls /
[Seq_Demo0000000012, ZookpClient, zookeeper]
[zk: localhost:2181(CONNECTED) 2] ls -s /ZookpClient
[Demo, Test]cZxid = 0xd00000021
ctime = Fri Sep 30 11:12:35 CST 2022
mZxid = 0xd00000021
mtime = Fri Sep 30 11:12:35 CST 2022
pZxid = 0xd00000026
cversion = 2
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 2
[zk: localhost:2181(CONNECTED) 3] get /ZookpClient/Test/Version/V1.0/Data
test
[zk: localhost:2181(CONNECTED) 4] get /ZookpClient/Demo
demo
[zk: localhost:2181(CONNECTED) 5]
(2) 测试删除节点
zookeeper删除节点刚好和创建节点相反,必须删除所有的子节点,父节点才能被删除,如下:
[zk: localhost:2181(CONNECTED) 5] delete /ZookpClient
Node not empty: /ZookpClient
[zk: localhost:2181(CONNECTED) 6] ls /
[Seq_Demo0000000012, ZookpClient, zookeeper]
[zk: localhost:2181(CONNECTED) 7]
所以我们先遍历删除最后 一层的子节点,然后递归,同样的flag为false时还有子节点未被删除则抛出异常,为true时,则正常删除。
zookpClient.deleteNode("/ZookpClient",true);
删除结果
[zk: localhost:2181(CONNECTED) 7] ls /
[Seq_Demo0000000012, zookeeper]
[zk: localhost:2181(CONNECTED) 8]
(3) others
剩余的就不再赘述,直接看结果,因为刚刚创建的节点已经删除了,所以需要重新创建一次~