Zookeeper使用-3.Api调用之使用Curator

192 阅读6分钟

上一篇文章使用了zookeeper原生包进行操作zk服务器,本篇使用Curator进行操作,Curator是NetFlix公司开源的一套Zookeeper的客户端框架,使用此框架,可以省去许多底层需要处理的细节。


一、使用Curator客户端

使用前我们从maven仓库中找到Curator的依赖包,只需要引入一个包即可

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.7.1</version>
</dependency>

1.创建会话

使用CuratorFrameworkFactory进行创建客户端连接对象。RetryPolicy为重试策略,默认有4种重试策略ExponentialBackOffRetry,RetryNTimes,RetryOneTime,RetryUntilElapsed

RetryPolicy接口中存在一个方法:
var1:已经重试次数
var2: 从第一次重试开始已经花费的时间
var4: 用于sleep指定时间

boolean allowRetry(int var1, long var2, RetrySleeper var4);

示例代码:

public CuratorFramework connectionZkWithFluent(){
    /**
     * 创建ExponentialBackoffRetry重试策略
     * baseSleepTimeMs:初始sleep时间
     * maxRetries:最大重试次数
     */
    RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
    //通过工厂进行创建client
    CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
            .connectString(host+":"+port)
            .sessionTimeoutMs(60000)
            .retryPolicy(retryPolicy)
            //为不同项目指定命名空间
            .namespace("base")
            .build();
    curatorFramework.start();
    System.out.println("启动连接成功");
    return curatorFramework;
}

2.创建节点

直接使用client进行创建,使用forPath进行规定节点位置

public void createNode() throws Exception {
    CuratorFramework client = connectionZkWithFluent();
    //创建一个不带数据的节点
    String node1 = client.create().forPath("/node1");
    System.out.println("创建节点Node1:"+node1);
    //创建一个带数据的节点
    String node2 = client.create().forPath("/node2","node2data".getBytes());
    System.out.println("创建节点Node2:"+node2);
    //创建一个带有节点类型的节点
    String node3 = client.create().withMode(CreateMode.PERSISTENT).forPath("/node3");
    System.out.println("创建节点Node3:"+node3);
    //创建一个自动补充父级的节点creatingParentsIfNeeded
    String node4 = client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/node4/node4-1/node4-2");
    System.out.println("创建节点Node4:"+node4);
}

3.删除节点

直接使用client进行调用,使用forPath进行规定节点位置

public void deleteNode() throws Exception {
    CuratorFramework client = connectionZkWithFluent();
    //只能删除叶子节点
    client.delete().forPath("/node1");
    //递归循环删除节点,删除节点下所有节点
    client.delete().deletingChildrenIfNeeded().forPath("/node4");
    //删除指定version的节点
    client.delete().withVersion(0).forPath("/node2");
    //在会话有效期内,后台会一直进行删除操作,直至删除成功.避免网络等原因没有调到或失败情况
    client.delete().guaranteed().forPath("/node3");
}

4.获取数据

直接使用client进行调用,使用forPath进行规定节点位置

public void getData() throws Exception {
    CuratorFramework client = connectionZkWithFluent();
    //获取数据,返回字节数组
    byte[] bytes = client.getData().forPath("/node1");
    //查找数据,并且返回对应的stat
    Stat stat = new Stat();
    byte[] node2s = client.getData().storingStatIn(stat).forPath("node2");
}

5.更新数据

直接使用client进行调用,使用forPath进行规定节点位置

public void updateData() throws Exception {
    CuratorFramework client = connectionZkWithFluent();
    //更新
    Stat stat = client.setData().forPath("/node1", "tom".getBytes());
    //指定版本更新
    Stat stat1 = client.setData().withVersion(0).forPath("node2", "jerry".getBytes());
}

6.异步调用

异步调用使用inBackground()并且传入回调处理的方法。并且支持传入单独的线程池用于处理回调方法,避免任务多时串行影响执行效率。

public void backgroud() throws Exception {
    CuratorFramework client = connectionZkWithFluent();
    ExecutorService pool = Executors.newFixedThreadPool(2);
    String s = client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).inBackground(new BackgroundCallback(){
        @Override
        public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
            System.out.println("code:"+curatorEvent.getResultCode() + "type:"+curatorEvent.getType());
            System.out.println("thread:"+Thread.currentThread().getName());
        }
        //此处支持传入线程池处理当前callback方法,避免长时间处理造成影响
    },pool).forPath("/node7");
    client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).inBackground(new BackgroundCallback(){
        @Override
        public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
            System.out.println("code:"+curatorEvent.getResultCode() + "type:"+curatorEvent.getType());
            System.out.println("thread:"+Thread.currentThread().getName());
        }
    }).forPath("/node");
}

二、常用场景使用

Curator不仅为开发者提供了便利的api,还提供一些复杂场景需要调用的接口,比如事件监听、Master选举、分布式锁、分布式计数器,都可以快速实现。这些接口在另外一个包中,我们可以通过maven中心仓库进行获取

<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-recipes</artifactId>
   <version>2.7.1</version>
</dependency>

1.事件监听

NodeCache

nodeCache用于监听ZK节点本身的变化。可以对不存在的节点进行监听,如果创建了节点,那么会进行通知处理;如果数据节点发生了变化也会通知;当节点被删除同样会被通知。同时使用提供的Cache是对原生Watcher的封装,无需手动反复注册watcher对象。
NodeCache对应的通知类是NodeCacheListener,需要实现listener进行监听。

示例代码:

/**
 * 使用nodecache
 */
public void addWatcher() throws Exception {
    CuratorFramework client = connectionZkWithFluent();
    String path = client.create().creatingParentsIfNeeded().forPath("/watcher/watcher2");
    final NodeCache nodeCache = new NodeCache(client,path);
    nodeCache.start();
    nodeCache.getListenable().addListener(new NodeCacheListener() {
        @Override
        public void nodeChanged() throws Exception {
            if(nodeCache.getCurrentData() == null){
                System.out.println("已经删除了当前节点:"+path);
                return;
            }
            System.out.println("监听内容nodechange:"+new String(nodeCache.getCurrentData().getData()));
        }
    });
    client.setData().forPath(path,"test".getBytes());
    Thread.sleep(3000);
    client.delete().deletingChildrenIfNeeded().forPath(path);
    Thread.sleep(5000);
    String s = client.create().creatingParentsIfNeeded().forPath("/watcher/watcher2");
    System.out.println("再次创建完成:"+s);
    Thread.sleep(5000);
}

示例中connectionZkWithFluent()方法为初始化client参数,在上一篇文章有介绍。

PathChildrenCache

PathChildrenCache用于监听节点下子节点的变化,包括增加、删除、修改数据变化。
PathChildrenCache构造函数中参数cacheDate参数为是否缓存节点数据,当为true时候,回调数据时会得到变化节点的数据。 PathChildrenCache对应的监听器接口是PathChildrenCacheListener,实现此listener即可实现监听的任务处理,监听事件类型包括三种,新增子节点、变更子节点、删除子节点。
示例代码:

/**
 * 使用pathChildrenCache
 * @throws Exception
 */
public void pathChildrenCache() throws Exception {
    CuratorFramework client = connectionZkWithFluent();
    client.delete().deletingChildrenIfNeeded().forPath("/watcher/watcher3");
    String path = client.create().creatingParentsIfNeeded().forPath("/watcher/watcher3");
    PathChildrenCache cache = new PathChildrenCache(client,path,true);
    cache.start();
    cache.getListenable().addListener(new PathChildrenCacheListener() {
        @Override
        public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
            System.out.println("类型:"+pathChildrenCacheEvent.getType()+" 数据:"+ new String(pathChildrenCacheEvent.getData().getData()));
        }
    });
    client.create().creatingParentsIfNeeded().forPath("/watcher/watcher3/watcher1");
    Thread.sleep(3000);
    client.create().creatingParentsIfNeeded().forPath("/watcher/watcher3/watcher2");
    Thread.sleep(3000);
    client.setData().forPath("/watcher/watcher3/watcher2","valuetest".getBytes());
    Thread.sleep(3000);
    client.delete().deletingChildrenIfNeeded().forPath("/watcher/watcher3/watcher1");
    Thread.sleep(3000);
}

注意: 此处监听子节点变化,仅支持当前节点的子节点,无法监听到子节点下级节点。无法对二级节点进行监听

2.Master选举

3.LeaderSelector

Curator中提供快速选举的能力,可以在集群环境中使用zk进行选举主服务进行操作数据。
原理:多个选举的master服务会抢着在path路径下进行创建节点,当创建成功后说明获取到master权限,选举成功,进行回调leaderSelectorListener进行通知,可以传入单独的线程池供回调方法使用。

使用提供的LeaderSelector进行选举。构造函数中包括:
client:当前客户端
path: 需要注册master的路径地址
leaderSelectorListener: 采用此监听器进行通知master服务,当前服务被选举成为master后,会调用takeLeadership方法进行处理。当takeLeadership方法执行完成后便失去master权利。

示例代码:

public void chooseMaster() throws InterruptedException {
        CuratorFramework client = connectionZkWithFluent();
        LeaderSelector selector = new LeaderSelector(client, "/master", new LeaderSelectorListener() {
            @Override
            public void takeLeadership(CuratorFramework curatorFramework) throws Exception {
                System.out.println(Thread.currentThread().getName() + "成为leader");
                Thread.sleep(6000);
                System.out.println(Thread.currentThread().getName() + "释放master");
            }

            @Override
            public void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {
//                System.out.println("状态变更connectionState={}",connectionState.CONNECTED.isConnected());
                System.out.println("状态变更" + connectionState.isConnected());
            }
        });
        //开启后可以进行重新排队,进行获取master权限
        selector.autoRequeue();
        selector.start();
        Thread.sleep(new Random().nextInt(10000000));
    }

代码中使用selector.autoRequeue()方法后,在失去master权利之后,会重新再次自动进行master选举。再次被选择为Master后将再次进行takeLeadership方法执行。

真实场景中自动排队使用场景较少,并不希望一直在自动选master,而是当集群中其他机器新增或掉线时候发起重新选举,所以可以使用PathChildrenCacheLeaderSelector一起来完成使用。

3.分布式锁

InterProcessMutex

使用InterProcessMutex工具类进行获取锁和释放锁,调用acquire()进行获取锁,调用release()进行释放锁。 实例代码:

public void getLock(){
    CuratorFramework client = connectionZkWithFluent();
    InterProcessMutex lock = new InterProcessMutex(client,"/lock");
    CountDownLatch count = new CountDownLatch(1);
    for(int i = 0 ; i <20;i++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    count.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    lock.acquire();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis());
                try {
                    lock.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    count.countDown();
}

以上使用Curator包常用的工具类都已经写完,如果有需要使用的地方可以直接复制进行验证使用。在挖掘技术路上还需要继续深耕。