Curator

转载自: http://ifeve.com/zookeeper-leader/
               http://blog.csdn.net/dc_726/article/details/46475633
               http://macrochen.iteye.com/blog/1366136

       Curator是Netflix开源的一套ZooKeeper客户端框架,与Zookeeper提供的原生客户端相比,Curator的抽象层次更高,简化了Zookeeper客户端的开发量。
       Curator主要解决了三类问题:
       • 封装ZooKeeper client与ZooKeeper server之间的连接处理;
       • 提供了一套Fluent风格的操作API;
       • 提供ZooKeeper各种应用场景(recipe, 比如共享锁服务, 集群领导选举机制)的抽象封装.

Curator主要从以下几个方面降低了zk使用的复杂性:
       重试机制:提供可插拔的重试机制, 它将给捕获所有可恢复的异常配置一个重试策略, 并且内部也提供了几种标准的重试策略(比如指数补偿).
       连接状态监控: Curator初始化之后会一直的对zk连接进行监听, 一旦发现连接状态发生变化, 将作出相应的处理.
       zk客户端实例管理:Curator对zk客户端到server集群连接进行管理. 并在需要的情况, 重建zk实例, 保证与zk集群的可靠连接.
       各种使用场景支持:Curator实现zk支持的大部分使用场景支持(甚至包括zk自身不支持的场景), 这些实现都遵循了zk的最佳实践, 并考虑了各种极端情况.

Curator几个组成部分
       • Client: 是ZooKeeper客户端的一个替代品, 提供了一些底层处理和相关的工具方法.
       • Framework: 用来简化ZooKeeper高级功能的使用, 并增加了一些新的功能, 比如管理到ZooKeeper集群的连接, 重试处理
       • Recipes: 实现了通用ZooKeeper的recipe, 该组件基于Framework
       • Utilities:各种ZooKeeper的工具类
       • Errors: 异常处理, 连接, 恢复等.
       • Extensions: 对curator-recipes的扩展实现,拆分为 curator-:stuck_out_tongue_closed_eyes:iscovery和 curator-:stuck_out_tongue_closed_eyes:iscovery-server提供基于RESTful的Recipes WEB服务.

名称空间(Namespace)
       因为一个zk集群会被多个应用共享, 为了避免各个应用的zk patch冲突, Curator Framework内部会给每一个Curator Framework实例分配一个namespace(可选). 这样在create ZNode的时候都会自动加上这个namespace作为这个node path的root. 使用代码如下:
CuratorFramework client = CuratorFrameworkFactory.builder().namespace("MyApp") ... build();  
…  
client.create().forPath("/test", data);
// node was actually written to: "/MyApp/test" 

方法说明
       • create(): 发起一个create操作. 可以组合其他方法 (比如mode 或background) 最后以forPath()方法结尾
       • delete(): 发起一个删除操作. 可以组合其他方法(version 或background) 最后以forPath()方法结尾
       • checkExists(): 发起一个检查ZNode 是否存在的操作. 可以组合其他方法(watch 或background) 最后以forPath()方法结尾
       • getData(): 发起一个获取ZNode数据的操作. 可以组合其他方法(watch, background 或get stat) 最后以forPath()方法结尾
       • setData(): 发起一个设置ZNode数据的操作. 可以组合其他方法(version 或background) 最后以forPath()方法结尾
       • getChildren(): 发起一个获取ZNode子节点的操作. 可以组合其他方法(watch, background 或get stat) 最后以forPath()方法结尾
       • inTransaction(): 发起一个ZooKeeper事务. 可以组合create, setData, check, 和/或delete 为一个操作, 然后commit() 提交

监听器
Curator提供了三种Watcher(Cache)来监听结点的变化:
       • Path Cache:监视一个路径下 孩子结点的创建、删除、以及结点数据的更新。产生的事件会传递给注册的PathChildrenCacheListener。
       • Node Cache:监视一个结点的创建、更新、删除,并将结点的数据缓存在本地。
       • Tree Cache:Path Cache和Node Cache的“合体”,监视路径下的创建、更新、删除事件,并缓存路径下所有孩子结点的数据。

示例:
//创建client
CuratorFramework client = CuratorFrameworkFactory.newClient(ZK_SERVER, new ExponentialBackoffRetry(1000, 3));
client.start();

//节点创建
if (client.checkExists().forPath("/curator") == null)
    client.create().withMode(CreateMode.PERSISTENT).forPath("/curator");

//获取子节点
System.out.println(client.getChildren().forPath("/curator"));

//设置并获取数据
client.setData().forPath("/curator", "zero".getBytes());
System.out.println(client.getData().forPath("/curator"));

//删除节点
client.delete().forPath("/curator");

PathChildrenCache watcher = new PathChildrenCache(client, "/curator", true);
watcher.getListenable().addListener(new PathChildrenCacheListener() {
    @Override
    public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
        ChildData data = event.getData();
        if (data == null) {
            System.out.println("No data in event[" + event + "]");
        } else {
            System.out.println("Receive event: "
                    + "type=[" + event.getType() + "]"
                    + ", path=[" + data.getPath() + "]"
                    + ", data=[" + new String(data.getData()) + "]"
                    + ", stat=[" + data.getStat() + "]");
        }
    }
});
watcher.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
System.out.println("Register zk watcher successfully!");

Curator实现ZooKeeper的所有recipe
选举

       集群领导选举(leader election)
锁服务
       共享锁: 全局同步分布式锁, 同一时间两台机器只有一台能获得同一把锁。
       共享读写锁: 用于分布式的读写互斥处理, 同时生成两个锁:一个读锁, 一个写锁, 读锁能被多个应用持有, 而写锁只能一个独占, 当写锁未被持有时, 多个读锁持有者可以同时进行读操作。
       共享信号量: 在分布式系统中的各个JVM使用同一个zk lock path, 该path将跟一个给定数量的租约(lease)相关联, 然后各个应用根据请求顺序获得对应的lease, 相对来说, 这是最公平的锁服务使用方式。
       多共享锁:内部构件多个共享锁(会跟一个znode path关联), 在acquire()过程中, 执行所有共享锁的acquire()方法, 如果中间出现一个失败, 则将释放所有已require的共享锁; 执行release()方法时, 则执行内部多个共享锁的release方法(如果出现失败将忽略)。

队列(Queue)

       分布式队列:采用持久顺序zk node来实现FIFO队列, 如果有多个消费者, 可以使用LeaderSelector来保证队列的消费者顺序。
       分布式优先队列: 优先队列的分布式版本。
       BlockingQueueConsumer: JDK阻塞队列的分布式版本 。
关卡(Barrier)
       分布式关卡:一堆客户端去处理一堆任务, 只有所有的客户端都执行完, 所有客户端才能继续往下处理。
       双分布式关卡:同时开始, 同时结束。
计数器(Counter)
       共享计数器:所有客户端监听同一个znode path, 并共享一个最新的integer计数值。
       分布式AtomicLong(AtomicInteger): AtomicXxx的分布式版本, 先采用乐观锁更新, 若失败再采用互斥锁更新, 可以配置重试策略来处理重试。

示例:
分布式锁
       分布式编程时,比如最容易碰到的情况就是应用程序在线上多机部署,于是当多个应用同时访问某一资源时,就需要某种机制去协调它们。下面的程序会启动两个线程t1和t2去争夺锁,拿到锁的线程会占用5秒。运行多次可以观察到,有时是t1先拿到锁而t2等待,有时又会反过来。Curator会用我们提供的lock路径的结点作为全局锁,这个结点的数据类似这种格式:[_c_64e0811f-9475-44ca-aa36- c1db65ae5350-lock-0000000005],每次获得锁时会生成这种串,释放锁时清空数据。
public class CuratorDistrLockTest {

    /** Zookeeper info */
    private static final String ZK_ADDRESS = "192.168.1.100:2181";
    private static final String ZK_LOCK_PATH = "/zktest";

    public static void main(String[] args) throws InterruptedException {
        // 1.Connect to zk
        CuratorFramework client = CuratorFrameworkFactory.newClient(
                ZK_ADDRESS,
                new RetryNTimes(10, 5000)
        );
        client.start();
        System.out.println("zk client start successfully!");

        Thread t1 = new Thread(() -> {
            doWithLock(client);
        }, "t1");
        Thread t2 = new Thread(() -> {
            doWithLock(client);
        }, "t2");

        t1.start();
        t2.start();
    }

    private static void doWithLock(CuratorFramework client) {
        InterProcessMutex lock = new InterProcessMutex(client, ZK_LOCK_PATH);
        try {
            if (lock.acquire(10 * 1000, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + " hold lock");
                Thread.sleep(5000L);
                System.out.println(Thread.currentThread().getName() + " release lock");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                lock.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}
Leader选举
       当集群里的某个服务down机时,可能要从slave结点里选出一个作为新的master,这时就需要一套能在分布式环境中自动协调的 Leader选举方法。Curator实现Leader选举有两种方式,第一种是LeaderLatch,这种是有阻塞的,所有client一起去争leader,没有选上的client会一直阻塞,等待上一leader退出或则挂掉。一旦leader位置为空了,继续争夺leader。第二种是LeaderSelector监听器实现Leader选举功能。同一时刻,只有一个Listener会进入 takeLeadership()方法,说明它是当前的Leader。注意:当Listener从takeLeadership()退出时就说明它放弃了“Leader身份”, 这时Curator会利用Zookeeper再从剩余的Listener中选出一个新的Leader。autoRequeue()方法使放弃 Leadership的Listener有机会重新获得Leadership,如果不设置的话放弃了的Listener是不会再变成Leader的。
public static void LeaderSelectorTest() throws Exception {
    RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    final CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString(host).sessionTimeoutMs(5000)
            .connectionTimeoutMs(10000).retryPolicy(retryPolicy)
            .namespace(namespace).build();
    client.start();

    final LeaderSelector leaderSelector = new LeaderSelector(client, "/leader",
            new LeaderSelectorListenerAdapter() {
                @Override
                public void takeLeadership(CuratorFramework client) throws Exception {
                    System.out.println("I am leader, working...");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println("I am leader, end.");
                }

            });
    leaderSelector.autoRequeue();
    leaderSelector.start();
    TimeUnit.SECONDS.sleep(1000);
}

public static void leaderLatchTest() throws Exception {
    RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString(host).sessionTimeoutMs(2000)
            .connectionTimeoutMs(10000).retryPolicy(retryPolicy)
            .namespace(namespace).build();
    client.start();

    System.out.println("I want to be leader.");
    // 选举Leader 启动
    LeaderLatch latch = new LeaderLatch(client, "/leader");
    latch.start();
    latch.await();
    System.out.println("I am leader");
    TimeUnit.SECONDS.sleep(10);
    latch.close();
    client.close();
}

你可能感兴趣的:(Curator)