ZooKeeper学习笔记六 ZooKeeper开源客户端Curator

本文学习资源来自《从Paxos到ZooKeeper分布式一致性原理与实践》

Curator

Curator是Netflix公司开源的一套ZooKeeper客户端框架,作者是Jordan Zimmerman。
和ZkClient一样,Curator解决了很多ZooKeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等,目前已经成为了Apache的顶级项目。

除了封闭一些开发人员不需要特别关注的底层细节,Curator还在ZooKeeper原生API的基础上进行了包装,提供了一套易用性和可读性更强的Fluent风格的客户端API框架。

除此之外,Curator中还提供了ZooKeeper各种应用场景(Recipe,如共享锁服务、Master选举机制和分布式计数器等)的抽象封装。

官网文档:
http://curator.apache.org/getting-started.html

maven:

<dependency>
     <groupId>org.apache.zookeeper</groupId>
     <artifactId>zookeeper</artifactId>
     <version>3.4.9</version>
 </dependency>
 <dependency>
     <groupId>org.apache.curator</groupId>
     <artifactId>curator-framework</artifactId>
     <version>2.4.2</version>
 </dependency>

创建会话

package zookeeper;

import java.io.IOException;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;


public class Create_Session_Sample {
    public static void main(String[] args)throws IOException,InterruptedException{
     RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
     CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", 5000,3000,retryPolicy);
     client.start();
     Thread.sleep(Integer.MAX_VALUE);
    }
}

该例中,首先创建的一个名为ExponentialBackoffRetry的重试策略。
CuratorFrameworkFactory工厂在创建出一个客户端CuratorFramework实例之后,实质上并没有完成会话的创建,而是需要调用CuratorFramework的start()方法来完成会话的创建。

使用Fluent风格的API接口来创建会话
Curator提供的API接口在设计上最大的亮点在于其遵循了Fluent设计风格。

package zookeeper;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class Create_Session_Sample_fluent {
    public static void main(String[] args) throws Exception{
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
        CuratorFramework client = 
                CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(5000)
                .retryPolicy(retryPolicy)
                .build();
        client.start();
        Thread.sleep(Integer.MAX_VALUE);
    }
}

使用Curator创建含隔离命名空间的会话
为了实现不同的ZooKeeper业务之间的隔离,往往会为每个业务分配一个独立的命名空间,即指定一个ZooKeeper根路径。

CuratorFrameworkFactory.builder()
               .connectString("127.0.0.1:2181")
               .sessionTimeoutMs(5000)
               .retryPolicy(retryPolicy)
               .namespace("base")
               .build();

创建节点

package zookeeper;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;


public class Create_Node_Sample {
    static String path = "/zk-book/c1";
    static CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000)
            .retryPolicy(new ExponentialBackoffRetry(1000,3))
            .build();
    public static void main(String[] args) throws Exception{
        client.start();
        client.create()
        .creatingParentsIfNeeded()
        .withMode(CreateMode.EPHEMERAL)
        .forPath(path,"init".getBytes());
    }
}

删除节点

package zookeeper;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;


public class Del_Data_Sample {
    static String path = "/zk-book/c1";
    static CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000)
            .retryPolicy(new ExponentialBackoffRetry(1000,3))
            .build();
    public static void main(String[] args) throws Exception{
        client.start();
        client.create()
        .creatingParentsIfNeeded()
        .withMode(CreateMode.EPHEMERAL)
        .forPath(path,"init".getBytes());
        Thread.sleep(2000);
        Stat stat = new Stat();
        client.getData().storingStatIn(stat).forPath(path);
        client.delete().deletingChildrenIfNeeded()
            .withVersion(stat.getVersion()).forPath(path);
        Thread.sleep(2000);
    }
}

这里guaranteed()方法,会在失败时重新尝试。

读取数据

package zookeeper;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;


public class Get_Data_Sample {
    static String path = "/zk-book/c1";
    static CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000)
            .retryPolicy(new ExponentialBackoffRetry(1000,3))
            .build();
    public static void main(String[] args) throws Exception{
        client.start();
        client.create()
            .creatingParentsIfNeeded()
            .withMode(CreateMode.EPHEMERAL)
            .forPath(path,"init".getBytes());
        Thread.sleep(2000);
        Stat stat = new Stat();
        System.out.println(new String(client.getData().storingStatIn(stat).forPath(path)));
        Thread.sleep(2000);
    }
}

更新数据

package zookeeper;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;


public class Set_Data_Sample {
    static String path = "/zk-book/c1";
    static CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000)
            .retryPolicy(new ExponentialBackoffRetry(1000,3))
            .build();
    public static void main(String[] args) throws Exception{
        client.start();
        client.create()
            .creatingParentsIfNeeded()
            .withMode(CreateMode.EPHEMERAL)
            .forPath(path,"init".getBytes());
        Thread.sleep(2000);
        Stat stat = new Stat();
        System.out.println(new String(client.getData().storingStatIn(stat).forPath(path)));
        Thread.sleep(2000);

        try{
            client.setData().withVersion(stat.getVersion()).forPath(path);
        }catch(Exception e){
            System.out.println("Fail set node due to " + e.getMessage());
        }
    }
}

异步接口

Curator中引入了BackgroundCallback接口,用来处理异步接口调用之后服务端返回的结果信息,其接口定义如下:

public interface BackgroundCallback{
    /**
    * Called when the async background operation completes
    * @param client the client
    * @param event operation result details
    */ @throws Exception errors
   public void processResult(CuratorFramework client, CuratorEvent event) throws Exception;
}

代码示例:

package zookeeper;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;


public class Create_Node_Background_Sample {
    static String path = "/zk-book";
    static CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000)
            .retryPolicy(new ExponentialBackoffRetry(1000,3))
            .build();

    static CountDownLatch semaphore = new CountDownLatch(2);
    static ExecutorService tp = Executors.newFixedThreadPool(2);

    public static void main(String[] args) throws Exception{
        client.start();

        System.out.println("Main thread:" + Thread.currentThread().getName());
        client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback(){

            @Override
            public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                System.out.println("event[code:" + event.getResultCode() + ",type:" + event.getType() + "]");
                System.out.println("Thread of processResult:" + Thread.currentThread().getName());
                semaphore.countDown();
            }

        }).forPath(path,"init".getBytes());

        client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback(){

            @Override
            public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                System.out.println("event[code:"+event.getResultCode() + ",type:" + event.getType() + "]");
                System.out.println("Thread of processResult:" + Thread.currentThread().getName());
                semaphore.countDown();
            }

        }).forPath(path,"init".getBytes());
        semaphore.await();
        tp.shutdown();
    }
}

事件监听

Curator引入了Cache来实现对ZooKeeper央事件的监听 。同时Curator自动重复注册监听,从而大大简化了原生 API 开发的繁琐过程。
以下代码示例需要引入maven


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

NodeCache

NodeCache用于监听指定ZooKeeper数据节点本身的变化。

package zookeeper;


import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;


public class NodeCache_Sample {
    static String path = "/zk-book/nodecache";
    static CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000)
            .retryPolicy(new ExponentialBackoffRetry(1000,3))
            .build();


    public static void main(String[] args) throws Exception{
        client.start();

        System.out.println("Main thread:" + Thread.currentThread().getName());
        client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path,"init".getBytes());

        final NodeCache cache = new NodeCache(client,path,false);
        cache.start(true);
        cache.getListenable().addListener(new NodeCacheListener(){

            @Override
            public void nodeChanged() throws Exception {
                System.out.println("Node data update,new data:"+new String(cache.getCurrentData().getData()));
            }

        });
        client.setData().forPath(path,"u".getBytes());
        Thread.sleep(1000);
        client.delete().deletingChildrenIfNeeded().forPath(path);
        Thread.sleep(Integer.MAX_VALUE);
        cache.close();
    }
}

PathChildrenCache

用于监听指定ZooKeeper数据节点的子节点变化情况。

Master选举

分布式系统中,经常遇到一个场景,一个复杂的任务,只需要从集群中选举出一台进行处理即可。其思路:
选择一个根节点,如/master_select,多台机器同时向该节点创建一个子节点/master_select/lock,利用ZooKeeper的特性,最终只有一台机器能够创建成功,成功的那台机器就作为Master。

package zookeeper;


import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.retry.ExponentialBackoffRetry;


public class Recipes_MasterSelect {
    static String master_path = "/curator_recipes_master_path";
    static CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000)
            .retryPolicy(new ExponentialBackoffRetry(1000,3))
            .build();


    public static void main(String[] args) throws Exception{
        client.start();

        LeaderSelector selector = new LeaderSelector(client,master_path,new LeaderSelectorListenerAdapter(){

            @Override
            public void takeLeadership(CuratorFramework client) throws Exception {
                System.out.println("成为Master角色");
                Thread.sleep(3000);
                System.out.println("完成Master操作,释放Master权利");
            }
        });
        selector.autoRequeue();
        selector.start();
        Thread.sleep(Integer.MAX_VALUE);
        selector.close();
    }
}

该程序主要是创建了一个LeaderSelector实例,该实例负责封装所有和Master选举相关的逻辑,包括所有和ZooKeeper服务器的交互过程。其中master_path代表了一个Master选举的根节点,表明本次Master选举都是在该节点下进行的。
在创建 LeaderSelector实例的时候,还会创建一个监听器LeaderSelectorListenerAdapter,这需要开发人员自行实现,Curator会在成功获取Master权利的时候回调该监听器。
Curator会在竞争到Master后自动调用该方法。一旦执行完takeLeadership方法,Curator就会立即释放Master权利,然后重新开始新一轮的Master选举。

分布式锁

在分布式环境中,为了保证数据的一致性,经常在程序的某个运行点需要进行同步控制。例如“流水号生成”场景,普通的后台应用通常使用时间戳生成流水号,但是在用户量非常大的情况下,可能出现并发问题。

代码示例:

package zookeeper;


import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;


public class Recipes_Lock {
    static String lock_path = "/curator_recipes_lock_path";
    static CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("127.0.0.1:2181")
            .sessionTimeoutMs(5000)
            .retryPolicy(new ExponentialBackoffRetry(1000,3))
            .build();


    public static void main(String[] args) throws Exception{
        client.start();

        final InterProcessMutex lock = new InterProcessMutex(client,lock_path);
        final CountDownLatch down=new CountDownLatch(1);
        for(int i=0;i<30;i++){
            new Thread(new Runnable(){

                @Override
                public void run() {
                    try{
                        down.await();
                        lock.acquire();
                    }catch(Exception e){

                    }
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
                    String orderNo=sdf.format(new Date());
                    System.out.println("生成的订单号是:"+orderNo);
                    try{
                        lock.release();
                    }catch(Exception e1){

                    }

                }

            }).start();
        }
        down.countDown();
    }
}

输出:

生成的订单号是:16:39:00|076
生成的订单号是:16:39:00|110
生成的订单号是:16:39:00|132
生成的订单号是:16:39:00|184
生成的订单号是:16:39:00|249
生成的订单号是:16:39:00|285
生成的订单号是:16:39:00|316
生成的订单号是:16:39:00|349
生成的订单号是:16:39:00|390
生成的订单号是:16:39:00|406
生成的订单号是:16:39:00|440
生成的订单号是:16:39:00|471
生成的订单号是:16:39:00|494
生成的订单号是:16:39:00|517
生成的订单号是:16:39:00|538
生成的订单号是:16:39:00|556
生成的订单号是:16:39:00|588
生成的订单号是:16:39:00|607
生成的订单号是:16:39:00|623
生成的订单号是:16:39:00|643
生成的订单号是:16:39:00|663
生成的订单号是:16:39:00|693
生成的订单号是:16:39:00|706
生成的订单号是:16:39:00|735
生成的订单号是:16:39:00|749
生成的订单号是:16:39:00|762
生成的订单号是:16:39:00|786
生成的订单号是:16:39:00|803
生成的订单号是:16:39:00|837
生成的订单号是:16:39:00|864

其它一些应用场景

  • 分布式计数器
  • 分布式Barrier

工具类

ZKPaths

提供了一些简单的API来构建ZNode路径、递归创建和删除节点等。

EnsurePath

提供了一种能够确保数据节点存在的机制。

TestingServer

Curator提供的一种简易的ZooKeeper服务端

TestingCluster

模拟ZooKeeper集群环境的Curator工具类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程圈子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值