开源客户端Curator

Curator是一套开源的ZooKeeper客户端框架,本文会学习并记录一些API。

Curator使用

创建Curator客户端的Maven依赖,不要使用高版本的

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

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

创建会话

//使用CuratorFrameworkFactory这个工厂类的静态方法来创建客户端
static CuratorFramework newClient(
    String connectString,  //ZooKeeper服务器列表,可以同时连接多台机器
                           //使用','隔开
    int sessionTimeoutMs,  //会话超时时间, 单位毫秒
    int connectTimeoutMs,  //连接创建超时时间,单位毫秒
    RetryPolicy retryPolicy  //重试策略的接口
);

// RetryPolicy接口的一个实现类
ExponentialBackoffRetry(
    int baseSleepTimeMs,  //初始Sleep时间
    int maxRetries,  //最大重试次数
    int maxSleepMs    //最大sleep时间
);

创建节点

CuratorFramework client = CuratorFrameworkFactory.newClient(...);
client.start();

String path = "/zk-book"

//创建一个节点,初始内容为空
//如果没有设置节点属性,Curator默认创建的是持久节点
client.create().forPath(path)

//创建一个节点,附带初始内容
client.crete().forPath(path, "hello".getBytes());

//创建一个临时节点,初始内容为空
client.create().withNode(CreateMode.EPHEMERAL).forPath(path);

//创建一个临时节点,并自动递归创建父节点
client.create().createParentsIfNeeded()
        .withMode(CreateMode.EPHEMERAL)
        .forPath(path);

在使用ZooKeeper的时候,会碰到NoNodeException异常,其中一个可能的原因就是视图对一个不存在的父节点创建字节点,调用createParentsIfNeed()可以自动递归创建所有需要的父节点。
ZooKeeper中规定所有非叶节点必须为持久节点,调用createParentsIfNeeded方法之后,只有path参数对应的数据节点是临时节点,其余父节点均为持久节点。

删除节点

//删除一个节点
client.delete().forPath();

//删除一个节点,并递归删除其所有子节点
client.delete().deletingChildrenIfNeeded().forPath(path);

//删除一个节点,强制指定版本进行删除
client.delete().withVersion(version).forPath(path);

//删除一个节点,保证可以被强制删除
client.delete().guaranteed().forPath(path);

guaranteed()这个API在客户端会话有效的阶段,会持续进行节点删除操作,直至删除成功。
在ZooKeeper客户端使用过程中,可能会碰到这样的问题:客户端执行一个删除节点操作,但由于一些网络原因,导致删除节点失败。Curator引入一个重试机制:guaranteed方法,当客户端碰到网络异常的时候,会记录下这次失败的操作,只要客户端会话有效,那么会保证节点被删除。

读取数据

//读取一个节点的数据内容, 返回的结果是byte[]
client.getData().forPath(path);

//读取一个节点的数据内容,同时获取到该节点的stat
client.getData().storingStatIn(stat).forPath(path);

更新数据

//更新一个节点的数据内容,返回一个stat对象
client.setData().forPath(path);

//更新一个节点的数据内容,强制指定版本进行更新
//withVersion的API就是用来实现CASd的,version通常是从一个旧的stat对象中获取
client.setData().withVersion(version).forPath(path);

事件监听

  • NodeCache

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

//Cache实现对服务端事件的监听
public NodeCache(
                CuratorFramework client,  //Curator客户端实例
                String path,    //数据节点的节点路径
                boolean dataIsCompressed   //是否进行数据压缩
                );

//回调voidnodeChanged方法一旦注册的事件发生
puiblic interface NodeCacheListener{
    public void voidnodeChanged() throws Exception;
}
  • PahtChildrenCache

用于监听指定ZooKeeper数据节点的子节点变化情况,节点本身的数据变化并不会触发异步调用。
子节点的变化包括新增字节点(CHILD_ADDED)、字节点数据变更(CHILD_UPDATED)和字节点删除(CHILD_REMOVED)三类。

public PathChildrenCache(
                        client, //Curator客户端实例
                        path, //数据节点的节点路径
                        dataIsCompressed, //是否进行数据压缩
                        cacheData, //true,用于缓存节点的内容
                        executorService //专门的线程池处理事件通知                                          
                    );

public interface PathChildrenCacheListenner{
    public void childEvent(CuratorFramework client, PathChidrenCacheEvent event) throws Execption;
}

Master选举

适用场景:对于一个复杂的认为,仅需要从集群中选举一台进行处理即可。
思路:选择一个根节点,例如/my_root,多台机器同时向该节点创建一个子节点/my_root/me_lock,利用ZooKeeper的特性,最终只用一台及其能够创建成功,成功的那台机器就作为Master。

public Class LeaderSelector(
                            CuratorFramework client,
                            String path,
                            LeaderSelectorListener listener //监听器
                        );
//选举成功之后的回调函数
public interface LeaderSelectorListener extends ConnectonStateListener{
    public void takLeadership(CuratorFramework client) thorws Exception;
}

//自定义实现适配器
public abstract LeaderSelectorListenerAdapter implements LeaderSelectorListener{
    @Override
    public void statChange(CuratorFramework client, ConnectionState newState){
        //TODO
    }
}

分布式锁

在分布式环境,为了保证数据的一致性,经常在某个程序的某个运行点(例如,减库存操作或生成流水线号)需要进行同步控制。

public InterProcessMutex(
                        CrutorFramework client,
                        String lock_path
                        );
//两个核心API,锁的获取与释放
public void acquire() throws Exception;
public void release() throws Exception;
    public static void testDistributeLockSample(){
        String path = "/curator_recipes_lock_path";
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1")
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();

        client.start();
        InterProcessMutex lock = new InterProcessMutex(client, path);
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for(int i = 0; i < 30; ++i){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        countDownLatch.await(); //主线程等待所有子线程创建完成
                    }catch (Exception e) { e.printStackTrace(); }

                    try{
                        lock.acquire(); //加锁
                    }catch (Exception e){ e.printStackTrace(); }

                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
                    String orderNo = sdf.format(new Date());
                    System.out.println("生成的订单号: " + orderNo);

                    try{
                        lock.release(); //释放锁
                    }catch (Exception e) { e.printStackTrace();}
                }
            }).start();
        }

        countDownLatch.countDown();
    }

分布式计数器

分布式计数器的一个典型场景是统计系统的在线人数。基于ZooKeeper的分布式计数器的实现思路:指定一个ZooKeeper数据节点作为计数器,多个应用实例在分布式锁的控制下,通过更新该数据节点的内容来实现计数功能。

  • 分布式AtomicInteger
public DistributedAtomicInteger(
                            CuratoFramework client,
                            String path,
                            RetryPolicy policy
                            );
        try {
            DistributedAtomicInteger atomicInteger = new DistributedAtomicInteger(
                    client, path, new ExponentialBackoffRetry(3, 1000));
            AtomicValue<Integer> rc = atomicInteger.add(8);
            System.out.println("Result: " + rc.succeeded() + ", value: " + rc.postValue());
        }catch (Exception e){
            e.printStackTrace();
        }
  • 分布式Barrier

public DistributedDoubleBarrier(
                        CuratorFramework client,
                        String path,
                        int nums
                        );
//程序将阻塞于以下两个方法
//直到达到nums的数量
public void enter();
public void leave();
    public static void testDistributedDoubleBarrierSample(){
        String barrier_path = "/curator_recipes_barrier_path";
        try{
            for(int i = 0; i < 5; ++i){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            CuratorFramework client = CuratorFrameworkFactory.builder()
                                    .connectString("127.0.0.1:2181")
                                    .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                                    .build();

                            client.start();
                            DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(client, barrier_path, 5);
                            Thread.sleep(Math.round(Math.random() * 3000));
                            System.out.println(Thread.currentThread().getName() + "进入Barrier");
                            barrier.enter();

                            System.out.println("启动:...");
                            Thread.sleep(Math.round(Math.random() * 3000));

                            barrier.leave();
                            System.out.println("退出");
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

输出结果

Thread-3进入Barrier
Thread-4进入Barrier
Thread-2进入Barrier
Thread-0进入Barrier
Thread-1进入Barrier
启动:...
启动:...
启动:...
启动:...
启动:...
退出
退出
退出
退出
退出

上面展示的DistributedDoubleBarrier与JDK中的CyclicBarrier有着非常类似的实现,它们都指定了进入Barrier的成员数阈值,例如上面程序的”5”。每个线程都会阻塞于enter和leave方法,一旦准备进入Barrier的成员达到5个之后,所有的成员会宏观上会变为非阻塞。

工具类

  • ZKPaths:构建ZNode路径、递归创建和删除节点;
  • EnsurePath:提供了一种能够确保数据节点存在的机制,节点若不存在,创建数据节点,反之,什么也不做。
    public static void testUtilsSample(){
        try{
            String path = "/curator_zkpath_sample";
            CuratorFramework client = CuratorFrameworkFactory.newClient(
                    "127.0.0.1:2181",
                    5000,
                    3000,
                    new ExponentialBackoffRetry(1000, 3)
            );
            client.start();


            ZooKeeper zooKeeper = client.getZookeeperClient().getZooKeeper();
            System.out.println(ZKPaths.fixForNamespace(path, "/sub"));  // 输出/curator_zkpath_sample/sub
            System.out.println(ZKPaths.makePath(path, "/sub")); //输出/curator_zkpath_sample/sub

            System.out.println(ZKPaths.getNodeFromPath("/curator_zkpath_sample/sub2")); //输出sub2

            ZKPaths.PathAndNode pn = ZKPaths.getPathAndNode(path + "/sub"); 
            System.out.println(pn.getPath());   // 输出/curator_zkpath_sample
            System.out.println(pn.getNode());   //  输出sub

            String dir1 = path + "/child1";
            String dir2 = path + "/child2";
            ZKPaths.mkdirs(zooKeeper, dir1);
            ZKPaths.mkdirs(zooKeeper, dir2);    

            System.out.println(ZKPaths.getSortedChildren(zooKeeper, path)); // 输出[child1, child2]


            client.usingNamespace("zk-book");
//            EnsurePath ..被弃用
        }catch (Exception e){
            e.printStackTrace();
        }
    }

输出结果

生成的订单号: 19:43:04|781
生成的订单号: 19:43:05|312
生成的订单号: 19:43:05|689
生成的订单号: 19:43:05|723
生成的订单号: 19:43:05|976
生成的订单号: 19:43:06|193
生成的订单号: 19:43:06|470
生成的订单号: 19:43:06|637
生成的订单号: 19:43:06|776
生成的订单号: 19:43:06|889
生成的订单号: 19:43:07|042
生成的订单号: 19:43:07|180
生成的订单号: 19:43:07|359
生成的订单号: 19:43:07|434
生成的订单号: 19:43:07|509
生成的订单号: 19:43:07|582
生成的订单号: 19:43:07|653
生成的订单号: 19:43:07|697
生成的订单号: 19:43:07|707
生成的订单号: 19:43:07|756
生成的订单号: 19:43:07|836
生成的订单号: 19:43:07|926
生成的订单号: 19:43:08|011
生成的订单号: 19:43:08|087
生成的订单号: 19:43:08|170
生成的订单号: 19:43:08|191
生成的订单号: 19:43:08|213
生成的订单号: 19:43:08|229
生成的订单号: 19:43:08|237
生成的订单号: 19:43:08|243

猜你喜欢

转载自blog.csdn.net/Pierce_Liu/article/details/80512947