(4)zookeeper原生java api基本使用介绍

本节介绍

本节里面,将会利用zookeeper官方的原生java api进行连接,然后演示一些创建、删除、修改、查询节点的操作。其实有两个比较好用的第三方客户端zkclient和curator,可以弥补原生api的很多不足,使用也比较简便,但是这两个东西也都是在原生api的基础上封装的,所以了解原生api的使用方法还是很有必要的,后面有时间的话会跟大家介绍一下另外两个客户端。

zookeeper 原生api 使用介绍

先给大家贴一张图,里面介绍了zookeeper原生java api的一些基础用法,可以先简单了解一下,下面会写一个demo程序对这几个方法进行逐个介绍。

(4)zookeeper原生java api基本使用介绍_第1张图片

准备

创建一个maven工程zkTest,然后添加zookeeper、log4j的maven依赖。


            org.apache.zookeeper
            zookeeper
            3.4.6
        


            log4j
            log4j
            1.2.15

将下面的log4j.properties配置文件放到src/main/resources下面即可

log4j.rootLogger = info,stdout,D
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =  %d %t [%c{2}] %L [%-5p] %m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ../logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = info
log4j.appender.D.DatePattern= '_'yyyy-MM-dd'.log'
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %d %t [%c{2}] %L [%-5p] %m%n

 zookeeper客户端处理类

package com.wkp.test.zookeeper.base;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
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;

public class ZookeeperBase {
    
    private static Logger logger=Logger.getLogger(ZookeeperBase.class);

    private ZooKeeper zookeeper;
    private String connectString;//连接zk服务端的ip:port,如果多个格式为 ip:port,ip:port......
    private int sessionTimeout;//客户端连接超时时间  单位 ms
    /** 信号量,阻塞程序执行,用于等待zookeeper连接成功,发送成功信号 */
    private CountDownLatch countDown=new CountDownLatch(1);
    
    public ZookeeperBase(String connectString,int sessionTimeout) throws IOException{
        this.connectString=connectString;
        this.sessionTimeout=sessionTimeout;
        
        zookeeper=new ZooKeeper(this.connectString, this.sessionTimeout, new Watcher() {
            
            public void process(WatchedEvent event) {
                //影响的路径
                String path = event.getPath();
                //获取事件的状态
                KeeperState state = event.getState();
                //获取事件的类型
                EventType type = event.getType();
                if(KeeperState.SyncConnected.equals(state)){
                    if(EventType.None.equals(type)){
                        //连接建立成功,则释放信号量,让阻塞的程序继续向下执行
                        countDown.countDown();
                        logger.info("zk建立连接成功========");
                    }
                }
            }
        });
    }
    
    public ZooKeeper getZkClient() throws InterruptedException{
        //计数器到达0之前,一直阻塞,只有当信号量被释放,才会继续向下执行
        countDown.await();
        return zookeeper;
    }
    
    public void closeClient() throws InterruptedException{
        if(zookeeper!=null){
            zookeeper.close();
            logger.info("zk关闭连接成功=======");
        }
    }
    
    /**
     * 创建节点
     * @param path 节点path
     * @param data 节点数据
     * @return
     * @throws InterruptedException
     * @throws KeeperException
     */
    public String createNode(String path,String data) throws InterruptedException, KeeperException{
        ZooKeeper zk = this.getZkClient();
        String str = zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        return str;
    }
    
    /**
     * 更新节点数据
     * @param path 节点path
     * @param data 节点数据
     * @return
     * @throws InterruptedException
     * @throws KeeperException
     */
    public Stat updateNode(String path,String data) throws InterruptedException, KeeperException{
        ZooKeeper zk = this.getZkClient();
        Stat stat = zk.setData(path, data.getBytes(),-1);
        return stat;
    }
    
    /**
     * 获得节点数据
     * @param path 节点path
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */
    public String getData(String path) throws KeeperException, InterruptedException{
        ZooKeeper zk = this.getZkClient();
        byte[] data = zk.getData(path, false, null);
        return new String(data);
    }
    
    /**
     * 获取当前节点的子节点(不包含孙子节点)
     * @param path 父节点path
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */
    public List getChildren(String path) throws KeeperException, InterruptedException{
        ZooKeeper zk = this.getZkClient();
        List list = zk.getChildren(path, false);
        return list;
    }
    
    /**
     * 判断节点是否存在
     * @param path
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */
    public Stat exists(String path) throws KeeperException, InterruptedException{
        ZooKeeper zk = this.getZkClient();
        Stat stat = zk.exists("/", false);
        return stat;
    }
    
    /**
     * 删除节点
     * @param path
     * @throws InterruptedException
     * @throws KeeperException
     */
    public void deleteNode(String path) throws InterruptedException, KeeperException{
        ZooKeeper zk = this.getZkClient();
        zk.delete(path, -1);
        logger.info("删除 "+path+" 节点成功");
    }
    
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        ZookeeperBase base = new ZookeeperBase("192.168.74.4:2181,192.168.74.5:2181,192.168.74.6:2181", 2000);
        ZooKeeper zkClient = base.getZkClient();
        System.out.println("sessionId:"+zkClient.getSessionId()+",sessionTimeOut:"+zkClient.getSessionTimeout());
        base.closeClient();
    }
}

1、建立客户端连接

ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)

 我们看到Zookeeper的构造方法需要三个参数,第一个connectString是连接字符串,格式为"ip:port,ip:port......",例如上面代码中的"192.168.74.4:2181,192.168.74.5:2181,192.168.74.6:2181",sessionTimeout为连接超时时间,单位毫秒,watcher是一个监听对象,zookeeper中对数据的变更操作都会有对应的事件,如果对节点注册了监听,当节点状态发生变化时就会被注册的监听对象监控到(关于zookeeper的事件监听机制下一节会有专门的介绍)。

我们看到代码中用到了一个多线程辅助类CountDownLatch,CountDownLatch是一个同步辅助类,构造方法初始化计数器值为1,当CountDownLatch值为0之前,其await()方法会一直阻塞,countDown()方法会将计数器的值减1(关于CountDownLatch的详细使用方法可以自行查询一下)。我们看到获取zookeeper客户端对象的方法中调用了CountDownLatch的await方法,就会一直等待watcher对象中监听的连接事件被监听到了,并且调用countDown方法后await方法才会被放行,这时候才可以获取到zookeeper客户端对象。至于为什么要使用这个同步计数器呢,其实是因为java中new Zookeeper创建对象立马就会返回了,而客户端连接到服务端是耗时的,这个时候并没有真正的连接成功,如果这个时候拿zk客户端对象去做操作会报错,所以我们要等待连接建立成功的时候才能使用客户端对象。

通过zkClient.getSessionId()可以获取到当前连接对象的ID,运行main方法会看到控制台出现下面结果(去掉了环境变量的无用日志输出)

2018-09-16 16:06:33,215 main [zookeeper.ZooKeeper] 438 [INFO ] Initiating client connection, connectString=192.168.74.4:2181,192.168.74.5:2181,192.168.74.6:2181 sessionTimeout=2000 watcher=com.wkp.test.zookeeper.base.ZookeeperBase$1@15c8132a
2018-09-16 16:06:33,342 main-SendThread(192.168.74.6:2181) [zookeeper.ClientCnxn] 975 [INFO ] Opening socket connection to server 192.168.74.6/192.168.74.6:2181. Will not attempt to authenticate using SASL (unknown error)
2018-09-16 16:06:33,347 main-SendThread(192.168.74.6:2181) [zookeeper.ClientCnxn] 852 [INFO ] Socket connection established to 192.168.74.6/192.168.74.6:2181, initiating session
2018-09-16 16:06:33,431 main-SendThread(192.168.74.6:2181) [zookeeper.ClientCnxn] 1235 [INFO ] Session establishment complete on server 192.168.74.6/192.168.74.6:2181, sessionid = 0x265e106a0720002, negotiated timeout = 4000
2018-09-16 16:06:33,438 main-EventThread [base.ZookeeperBase] 45 [INFO ] zk建立连接成功========
sessionId:172791579301511170,sessionTimeOut:4000
2018-09-16 16:06:33,462 main [zookeeper.ZooKeeper] 684 [INFO ] Session: 0x265e106a0720002 closed
2018-09-16 16:06:33,463 main-EventThread [zookeeper.ClientCnxn] 512 [INFO ] EventThread shut down
2018-09-16 16:06:33,464 main [base.ZookeeperBase] 61 [INFO ] zk关闭连接成功=======

2、创建节点

我们调用createNode方法创建节点,path为 "/testRoot",value为 "rootValue"

        ZookeeperBase base = new ZookeeperBase("192.168.74.4:2181,192.168.74.5:2181,192.168.74.6:2181", 2000);
        String result = base.createNode("/testRoot", "rootValue");
        System.out.println(result);
        base.closeClient();

然后会看到控制台输出了刚才创建的节点路径 "/testRoot",假如我们再运行一次上面的创建节点的程序的话,会报org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode = NodeExists for /testRoot,由此可见不能重复创建节点,否则会报节点已经存在的异常。

创建节点的方法create(String path, byte[] data, List<ACL> acl, CreateMode createMode),需要接收四个参数,path是节点路径,data是节点值,ACL是zookeeper的权限相关的,我们查看上面createNode()方法中传的是Ids.OPEN_ACL_UNSAFE,表示开放所有权限(具体的下一节做详细介绍),createMode表示节点的类型,我们查看CreateMode的源码发现其是一个枚举类,里面定义了四种节点类型,从前到后分别是持久化节点、持久化有序节点、临时节点、临时有序节点。

(4)zookeeper原生java api基本使用介绍_第2张图片

接下来我们尝试创建一个a节点下的b节点即 "/a/b",此时a节点还没有创建

        ZookeeperBase base = new ZookeeperBase("192.168.74.4:2181,192.168.74.5:2181,192.168.74.6:2181", 2000);
        String result = base.createNode("/a/b", "test");
        System.out.println(result);
        base.closeClient();

此时控制台会报org.apache.zookeeper.KeeperException$NoNodeException: KeeperErrorCode = NoNode for /a/b,然后我们尝试给testRoot节点创建一个子节点 children 即 "/testRoot/children",

ZookeeperBase base = new ZookeeperBase("192.168.74.4:2181,192.168.74.5:2181,192.168.74.6:2181", 2000);
        String result = base.createNode("/testRoot/children", "childrenValue");
        System.out.println(result);

经运行发现children节点创建成功,由此可见:zookeeper的原生java api是不会自动递归创建节点的,即当父节点不存在事不可以创建子节点(下面的方法我没有写具体的调用,您可以自己参照上面代码调用验证哦)

3、更新节点

更新节点数据调用的方法是 setData(String path, byte[] data, int version),前两个参数跟前面创建节点的一样这里就不解释了,最后一个version参数代表希望变更的节点的版本号(前面的章节有介绍过,随着变更节点版本号也会变更),如果版本号与节点实际版本号不对应会报异常(org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for /testRoot/children),如果version传递值为 "-1",则表示忽略版本号限制。

4、判断节点是否存在

  判断节点是否存在调用 exists(String path, boolean watch) 方法,如果存在会返回对应节点,第二个参数是一个布尔类型,true表示需要对当前节点进行监听,false表示不需要监听,具体的会在下一节介绍。

5、获取节点数据

获取节点数据方法getData(String path, boolean watch, Stat stat)

6、获取当前节点的所有子节点数据

调用方法getChildren(String path, boolean watch)会返回所有子节点数据的集合,注意是返回所有子节点的数据,不会返回孙子节点的数据,如果想获取当前节点下的所有子节点、孙子节点、重孙子节点......需要自己写程序进行递归获取。

7、删除节点

删除节点调用方法 delete(String path, int version) ,注意删除节点是不能递归删除节点,即如果当前节点有子节点,必须先把子节点删除,才可以删除父节点,否则会报 org.apache.zookeeper.KeeperException$NotEmptyException: KeeperErrorCode = Directory not empty for /testRoot

下一节会介绍一下zookeeper的监听机制即本节中的watcher,还会对zookeeper的ACL做个简单介绍。

你可能感兴趣的:(ZooKeeper)