zookeeper之Curator框架(CRUD/事务/选举/锁)的使用

Curator框架是最好用,最流行的zookeeper的客户端。

它有以下三个优点

1.提供了一套非常友好的操作API;

2. 提供一些高级特性(包括但不仅限于前篇文章中提到的)的封装

3.易测试

 

maven依赖如下

  1. <dependency>    
  2.     <groupId>org.apache.curator</groupId>    
  3.     <artifactId>curator-recipes</artifactId>    
  4.     <version>2.5.0</version>    
  5. </dependency>    
<dependency>  
    <groupId>org.apache.curator</groupId>  
    <artifactId>curator-recipes</artifactId>  
    <version>2.5.0</version>  
</dependency>  


按照官方给出的文档和包结构,可以轻松的看出Curator功能分两大类,一是对zookeeper的一些基本命令的封装,比如增删改查。是他的framework模块,一个是他的高级特性,即recipes模块。

 

一、framework模块

Curator提供了一套Fluent风格的操作API。这在很多脚本类语言里比较流行。

 

比如他创建client的代码是这样

  1. CuratorFramework client = builder.connectString("192.168.11.56:2180")    
  2.         .sessionTimeoutMs(30000)    
  3.         .connectionTimeoutMs(30000)    
  4.         .canBeReadOnly(false)    
  5.         .retryPolicy(new ExponentialBackoffRetry(1000, Integer.MAX_VALUE))    
  6.         .namespace(namespace)    
  7.         .defaultData(null)    
  8.         .build();    
  9. client.start();    
CuratorFramework client = builder.connectString("192.168.11.56:2180")  
        .sessionTimeoutMs(30000)  
        .connectionTimeoutMs(30000)  
        .canBeReadOnly(false)  
        .retryPolicy(new ExponentialBackoffRetry(1000, Integer.MAX_VALUE))  
        .namespace(namespace)  
        .defaultData(null)  
        .build();  
client.start();  


 一路点到底,这就是所谓的Fluent风格。 

 

我们再看增删改查的

  1. public class CrudExamples {    
  2.     private static CuratorFramework client = ClientFactory.newClient();    
  3.     private static final String PATH = "/crud";    
  4.     
  5.     public static void main(String[] args) {    
  6.         try {    
  7.             client.start();    
  8.     
  9.             client.create().forPath(PATH, "I love messi".getBytes());    
  10.     
  11.             byte[] bs = client.getData().forPath(PATH);    
  12.             System.out.println("新建的节点,data为:" + new String(bs));    
  13.     
  14.             client.setData().forPath(PATH, "I love football".getBytes());    
  15.     
  16.             // 由于是在background模式下获取的data,此时的bs可能为null    
  17.             byte[] bs2 = client.getData().watched().inBackground().forPath(PATH);    
  18.             System.out.println("修改后的data为" + new String(bs2 != null ? bs2 : new byte[0]));    
  19.     
  20.             client.delete().forPath(PATH);    
  21.             Stat stat = client.checkExists().forPath(PATH);    
  22.     
  23.             // Stat就是对zonde所有属性的一个映射, stat=null表示节点不存在!    
  24.             System.out.println(stat);    
  25.         } catch (Exception e) {    
  26.             e.printStackTrace();    
  27.         } finally {    
  28.             CloseableUtils.closeQuietly(client);    
  29.         }    
  30.     }    
  31. }    
public class CrudExamples {  
    private static CuratorFramework client = ClientFactory.newClient();  
    private static final String PATH = "/crud";  
  
    public static void main(String[] args) {  
        try {  
            client.start();  
  
            client.create().forPath(PATH, "I love messi".getBytes());  
  
            byte[] bs = client.getData().forPath(PATH);  
            System.out.println("新建的节点,data为:" + new String(bs));  
  
            client.setData().forPath(PATH, "I love football".getBytes());  
  
            // 由于是在background模式下获取的data,此时的bs可能为null  
            byte[] bs2 = client.getData().watched().inBackground().forPath(PATH);  
            System.out.println("修改后的data为" + new String(bs2 != null ? bs2 : new byte[0]));  
  
            client.delete().forPath(PATH);  
            Stat stat = client.checkExists().forPath(PATH);  
  
            // Stat就是对zonde所有属性的一个映射, stat=null表示节点不存在!  
            System.out.println(stat);  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            CloseableUtils.closeQuietly(client);  
        }  
    }  
}  


 常用接口有

create()增

delete(): 删

checkExists(): 判断是否存在

setData():  改

getData(): 查

所有这些方法都以forpath()结尾,辅以watch(监听),withMode(指定模式),和inBackground(后台运行)等方法来使用。


此外,Curator还支持事务,一组crud操作同生同灭。代码如下

  1. /** 
  2.  * 事务操作 
  3.  *  
  4.  * @author shencl 
  5.  */  
  6. public class TransactionExamples {  
  7.     private static CuratorFramework client = ClientFactory.newClient();  
  8.   
  9.     public static void main(String[] args) {  
  10.         try {  
  11.             client.start();  
  12.             // 开启事务  
  13.             CuratorTransaction transaction = client.inTransaction();  
  14.   
  15.             Collection<CuratorTransactionResult> results = transaction.create()  
  16.                     .forPath("/a/path""some data".getBytes()).and().setData()  
  17.                     .forPath("/another/path""other data".getBytes()).and().delete().forPath("/yet/another/path")  
  18.                     .and().commit();  
  19.   
  20.             for (CuratorTransactionResult result : results) {  
  21.                 System.out.println(result.getForPath() + " - " + result.getType());  
  22.             }  
  23.         } catch (Exception e) {  
  24.             e.printStackTrace();  
  25.         } finally {  
  26.             // 释放客户端连接  
  27.             CloseableUtils.closeQuietly(client);  
  28.         }  
  29.   
  30.     }  
  31. }  

 这段的代码的运行结果,由于最后一步delete的节点不存在,所以整个事务commit失败。失败的原因会放在Collection<CuratorTransactionResult>中,非常友好。

 

好了framework部分的内容就这么多,是不是特别简单呢。下面就来看看recipes包的内容吧。。

 

Recipes部分提供的功能官网列的很详细,点击这里。注意文章第一段:Curator宣称,Recipes模块实现了除二阶段提交之外的所有zookeeper特性。

 

二、Recipes模块

 

主要有

Elections(选举),Locks(锁),Barriers(关卡),Atomic(原子量),Caches,Queues等

 

1、 Elections

选举主要依赖于LeaderSelector和LeaderLatch2个类。前者是所有存活的客户端不间断的轮流做Leader,大同社会。后者是一旦选举出Leader,除非有客户端挂掉重新触发选举,否则不会交出领导权。某党?

 

这两者在实现上是可以切换的,直接上代码,怎么切换注释里有。由于篇幅所限,这里仅贴出基于LeaderSelector的选举,更多代码见附件



  1. /**  
  2.  * 本类基于leaderSelector实现,所有存活的client会公平的轮流做leader  
  3.  * 如果不想频繁的变化Leader,需要在takeLeadership方法里阻塞leader的变更! 或者使用 {@link}  
  4.  * LeaderLatchClient  
  5.  */    
  6. public class LeaderSelectorClient extends LeaderSelectorListenerAdapter implements Closeable {    
  7.     private final String name;    
  8.     private final LeaderSelector leaderSelector;    
  9.     private final String PATH = "/leaderselector";    
  10.     
  11.     public LeaderSelectorClient(CuratorFramework client, String name) {    
  12.         this.name = name;    
  13.         leaderSelector = new LeaderSelector(client, PATH, this);    
  14.         leaderSelector.autoRequeue();    
  15.     }    
  16.     
  17.     public void start() throws IOException {    
  18.         leaderSelector.start();    
  19.     }    
  20.     
  21.     @Override    
  22.     public void close() throws IOException {    
  23.         leaderSelector.close();    
  24.     }    
  25.     
  26.     /**  
  27.      * client成为leader后,会调用此方法  
  28.      */    
  29.     @Override    
  30.     public void takeLeadership(CuratorFramework client) throws Exception {    
  31.         int waitSeconds = (int) (5 * Math.random()) + 1;    
  32.         System.out.println(name + "是当前的leader");    
  33.         try {    
  34.             Thread.sleep(TimeUnit.SECONDS.toMillis(waitSeconds));    
  35.         } catch (InterruptedException e) {    
  36.             Thread.currentThread().interrupt();    
  37.         } finally {    
  38.             System.out.println(name + " 让出领导权\n");    
  39.         }    
  40.     }    
/** 
 * 本类基于leaderSelector实现,所有存活的client会公平的轮流做leader 
 * 如果不想频繁的变化Leader,需要在takeLeadership方法里阻塞leader的变更! 或者使用 {@link} 
 * LeaderLatchClient 
 */  
public class LeaderSelectorClient extends LeaderSelectorListenerAdapter implements Closeable {  
    private final String name;  
    private final LeaderSelector leaderSelector;  
    private final String PATH = "/leaderselector";  
  
    public LeaderSelectorClient(CuratorFramework client, String name) {  
        this.name = name;  
        leaderSelector = new LeaderSelector(client, PATH, this);  
        leaderSelector.autoRequeue();  
    }  
  
    public void start() throws IOException {  
        leaderSelector.start();  
    }  
  
    @Override  
    public void close() throws IOException {  
        leaderSelector.close();  
    }  
  
    /** 
     * client成为leader后,会调用此方法 
     */  
    @Override  
    public void takeLeadership(CuratorFramework client) throws Exception {  
        int waitSeconds = (int) (5 * Math.random()) + 1;  
        System.out.println(name + "是当前的leader");  
        try {  
            Thread.sleep(TimeUnit.SECONDS.toMillis(waitSeconds));  
        } catch (InterruptedException e) {  
            Thread.currentThread().interrupt();  
        } finally {  
            System.out.println(name + " 让出领导权\n");  
        }  
    }  

  1. /**  
  2.  * leader选举  
  3.  *   
  4.  * @author shencl  
  5.  */    
  6. public class LeaderSelectorExample {    
  7.     
  8.     public static void main(String[] args) {    
  9.     
  10.         List<CuratorFramework> clients = Lists.newArrayList();    
  11.         List<LeaderSelectorClient> examples = Lists.newArrayList();    
  12.         try {    
  13.             for (int i = 0; i < 10; i++) {    
  14.                 CuratorFramework client = ClientFactory.newClient();    
  15.                 LeaderSelectorClient example = new LeaderSelectorClient(client, "Client #" + i);    
  16.                 clients.add(client);    
  17.                 examples.add(example);    
  18.     
  19.                 client.start();    
  20.                 example.start();    
  21.             }    
  22.     
  23.             System.out.println("----------先观察一会选举的结果-----------");    
  24.             Thread.sleep(10000);    
  25.     
  26.             System.out.println("----------关闭前5个客户端,再观察选举的结果-----------");    
  27.             for (int i = 0; i < 5; i++) {    
  28.                 clients.get(i).close();    
  29.             }    
  30.     
  31.             // 这里有个小技巧,让main程序一直监听控制台输入,异步的代码就可以一直在执行。不同于while(ture)的是,按回车或esc可退出    
  32.             new BufferedReader(new InputStreamReader(System.in)).readLine();    
  33.     
  34.         } catch (Exception e) {    
  35.             e.printStackTrace();    
  36.         } finally {    
  37.             for (LeaderSelectorClient exampleClient : examples) {    
  38.                 CloseableUtils.closeQuietly(exampleClient);    
  39.             }    
  40.             for (CuratorFramework client : clients) {    
  41.                 CloseableUtils.closeQuietly(client);    
  42.             }    
  43.         }    
  44.     }    
  45. }    
/** 
 * leader选举 
 *  
 * @author shencl 
 */  
public class LeaderSelectorExample {  
  
    public static void main(String[] args) {  
  
        List<CuratorFramework> clients = Lists.newArrayList();  
        List<LeaderSelectorClient> examples = Lists.newArrayList();  
        try {  
            for (int i = 0; i < 10; i++) {  
                CuratorFramework client = ClientFactory.newClient();  
                LeaderSelectorClient example = new LeaderSelectorClient(client, "Client #" + i);  
                clients.add(client);  
                examples.add(example);  
  
                client.start();  
                example.start();  
            }  
  
            System.out.println("----------先观察一会选举的结果-----------");  
            Thread.sleep(10000);  
  
            System.out.println("----------关闭前5个客户端,再观察选举的结果-----------");  
            for (int i = 0; i < 5; i++) {  
                clients.get(i).close();  
            }  
  
            // 这里有个小技巧,让main程序一直监听控制台输入,异步的代码就可以一直在执行。不同于while(ture)的是,按回车或esc可退出  
            new BufferedReader(new InputStreamReader(System.in)).readLine();  
  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            for (LeaderSelectorClient exampleClient : examples) {  
                CloseableUtils.closeQuietly(exampleClient);  
            }  
            for (CuratorFramework client : clients) {  
                CloseableUtils.closeQuietly(client);  
            }  
        }  
    }  
}  


2、locks

curator lock相关的实现在recipes.locks包里。顶级接口都是InterProcessLock。我们直接看最有代表性的InterProcessReadWriteLock 进程内部读写锁(可重入读写锁)。什么叫可重入,什么叫读写锁。不清楚的先查好资料吧。总之读写锁一定是成对出现的。    简易传送门

 

我们先定义两个任务,可并行的执行的,和互斥执行的。

  1. /**  
  2.  * 并行任务  
  3.  *   
  4.  * @author shencl  
  5.  */    
  6. public class ParallelJob implements Runnable {    
  7.     
  8.     private final String name;    
  9.     
  10.     private final InterProcessLock lock;    
  11.     
  12.     // 锁等待时间    
  13.     private final int wait_time = 5;    
  14.     
  15.     ParallelJob(String name, InterProcessLock lock) {    
  16.         this.name = name;    
  17.         this.lock = lock;    
  18.     }    
  19.     
  20.     @Override    
  21.     public void run() {    
  22.         try {    
  23.             doWork();    
  24.         } catch (Exception e) {    
  25.             // ingore;    
  26.         }    
  27.     }    
  28.     
  29.     public void doWork() throws Exception {    
  30.         try {    
  31.             if (!lock.acquire(wait_time, TimeUnit.SECONDS)) {    
  32.                 System.err.println(name + "等待" + wait_time + "秒,仍未能获取到lock,准备放弃。");    
  33.             }    
  34.             // 模拟job执行时间0-4000毫秒    
  35.             int exeTime = new Random().nextInt(4000);    
  36.             System.out.println(name + "开始执行,预计执行时间= " + exeTime + "毫秒----------");    
  37.             Thread.sleep(exeTime);    
  38.         } catch (Exception e) {    
  39.             e.printStackTrace();    
  40.         } finally {    
  41.             lock.release();    
  42.         }    
  43.     }    
  44. }    
/** 
 * 并行任务 
 *  
 * @author shencl 
 */  
public class ParallelJob implements Runnable {  
  
    private final String name;  
  
    private final InterProcessLock lock;  
  
    // 锁等待时间  
    private final int wait_time = 5;  
  
    ParallelJob(String name, InterProcessLock lock) {  
        this.name = name;  
        this.lock = lock;  
    }  
  
    @Override  
    public void run() {  
        try {  
            doWork();  
        } catch (Exception e) {  
            // ingore;  
        }  
    }  
  
    public void doWork() throws Exception {  
        try {  
            if (!lock.acquire(wait_time, TimeUnit.SECONDS)) {  
                System.err.println(name + "等待" + wait_time + "秒,仍未能获取到lock,准备放弃。");  
            }  
            // 模拟job执行时间0-4000毫秒  
            int exeTime = new Random().nextInt(4000);  
            System.out.println(name + "开始执行,预计执行时间= " + exeTime + "毫秒----------");  
            Thread.sleep(exeTime);  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            lock.release();  
        }  
    }  
}  

  1. /**  
  2.  * 互斥任务  
  3.  *   
  4.  * @author shencl  
  5.  */    
  6. public class MutexJob implements Runnable {    
  7.     
  8.     private final String name;    
  9.     
  10.     private final InterProcessLock lock;    
  11.     
  12.     // 锁等待时间    
  13.     private final int wait_time = 10;    
  14.     
  15.     MutexJob(String name, InterProcessLock lock) {    
  16.         this.name = name;    
  17.         this.lock = lock;    
  18.     }    
  19.     
  20.     @Override    
  21.     public void run() {    
  22.         try {    
  23.             doWork();    
  24.         } catch (Exception e) {    
  25.             // ingore;    
  26.         }    
  27.     }    
  28.     
  29.     public void doWork() throws Exception {    
  30.         try {    
  31.             if (!lock.acquire(wait_time, TimeUnit.SECONDS)) {    
  32.                 System.err.println(name + "等待" + wait_time + "秒,仍未能获取到lock,准备放弃。");    
  33.             }    
  34.             // 模拟job执行时间0-2000毫秒    
  35.             int exeTime = new Random().nextInt(2000);    
  36.             System.out.println(name + "开始执行,预计执行时间= " + exeTime + "毫秒----------");    
  37.             Thread.sleep(exeTime);    
  38.         } catch (Exception e) {    
  39.             e.printStackTrace();    
  40.         } finally {    
  41.             lock.release();    
  42.         }    
  43.     }    
  44. }    
/** 
 * 互斥任务 
 *  
 * @author shencl 
 */  
public class MutexJob implements Runnable {  
  
    private final String name;  
  
    private final InterProcessLock lock;  
  
    // 锁等待时间  
    private final int wait_time = 10;  
  
    MutexJob(String name, InterProcessLock lock) {  
        this.name = name;  
        this.lock = lock;  
    }  
  
    @Override  
    public void run() {  
        try {  
            doWork();  
        } catch (Exception e) {  
            // ingore;  
        }  
    }  
  
    public void doWork() throws Exception {  
        try {  
            if (!lock.acquire(wait_time, TimeUnit.SECONDS)) {  
                System.err.println(name + "等待" + wait_time + "秒,仍未能获取到lock,准备放弃。");  
            }  
            // 模拟job执行时间0-2000毫秒  
            int exeTime = new Random().nextInt(2000);  
            System.out.println(name + "开始执行,预计执行时间= " + exeTime + "毫秒----------");  
            Thread.sleep(exeTime);  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            lock.release();  
        }  
    }  
}  


锁测试代码


  1. /**  
  2.  * 分布式锁实例  
  3.  *   
  4.  * @author shencl  
  5.  */    
  6. public class DistributedLockExample {    
  7.     private static CuratorFramework client = ClientFactory.newClient();    
  8.     private static final String PATH = "/locks";    
  9.     
  10.     // 进程内部(可重入)读写锁    
  11.     private static final InterProcessReadWriteLock lock;    
  12.     // 读锁    
  13.     private static final InterProcessLock readLock;    
  14.     // 写锁    
  15.     private static final InterProcessLock writeLock;    
  16.     
  17.     static {    
  18.         client.start();    
  19.         lock = new InterProcessReadWriteLock(client, PATH);    
  20.         readLock = lock.readLock();    
  21.         writeLock = lock.writeLock();    
  22.     }    
  23.     
  24.     public static void main(String[] args) {    
  25.         try {    
  26.             List<Thread> jobs = Lists.newArrayList();    
  27.             for (int i = 0; i < 10; i++) {    
  28.                 Thread t = new Thread(new ParallelJob("Parallel任务" + i, readLock));    
  29.                 jobs.add(t);    
  30.             }    
  31.     
  32.             for (int i = 0; i < 10; i++) {    
  33.                 Thread t = new Thread(new MutexJob("Mutex任务" + i, writeLock));    
  34.                 jobs.add(t);    
  35.             }    
  36.     
  37.             for (Thread t : jobs) {    
  38.                 t.start();    
  39.             }    
  40.         } catch (Exception e) {    
  41.             e.printStackTrace();    
  42.         } finally {    
  43.             CloseableUtils.closeQuietly(client);    
  44.         }    
  45.     }    
  46. }    
/** 
 * 分布式锁实例 
 *  
 * @author shencl 
 */  
public class DistributedLockExample {  
    private static CuratorFramework client = ClientFactory.newClient();  
    private static final String PATH = "/locks";  
  
    // 进程内部(可重入)读写锁  
    private static final InterProcessReadWriteLock lock;  
    // 读锁  
    private static final InterProcessLock readLock;  
    // 写锁  
    private static final InterProcessLock writeLock;  
  
    static {  
        client.start();  
        lock = new InterProcessReadWriteLock(client, PATH);  
        readLock = lock.readLock();  
        writeLock = lock.writeLock();  
    }  
  
    public static void main(String[] args) {  
        try {  
            List<Thread> jobs = Lists.newArrayList();  
            for (int i = 0; i < 10; i++) {  
                Thread t = new Thread(new ParallelJob("Parallel任务" + i, readLock));  
                jobs.add(t);  
            }  
  
            for (int i = 0; i < 10; i++) {  
                Thread t = new Thread(new MutexJob("Mutex任务" + i, writeLock));  
                jobs.add(t);  
            }  
  
            for (Thread t : jobs) {  
                t.start();  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            CloseableUtils.closeQuietly(client);  
        }  
    }  
}  

看到没,用法和java concurrent包里的ReentrantReadWriteLock 是一模一样的。

事实上,整个recipes包的目录结构、实现原理同java concurrent包的设置是很一致的。比如有queue,Semaphore,Barrier等类,。他整个就是模仿jdk的实现,只不过是基于分布式的!

 

后边的几项,Barriers(关卡),Atomic(原子量),Caches,Queues和java concurrent包里的类的用法是一样的,就不继续贴了,有些附件里有。

要说明的是:有的功能性能不是特别理想,网上也没见有大的项目的使用案例。比如基于CAS机制的atomic,在某些情况重试的效率还不如硬同步,要是zookeeper节点再一多,各个节点之间通过event触发的数据同步极其频繁。那性能可以想象。

 

三、测试方法

 curator提供了很好的测试工具,你甚至是可以在完全没有搭建zookeeper server端的情况下,完成测试。

有2个重要的类

TestingServer 模拟单点, TestingCluster模拟集群。

需要使用的话,得依赖


  1. <dependency>    
  2.     <groupId>org.apache.curator</groupId>    
  3.     <artifactId>curator-test</artifactId>    
  4.     <version>2.5.0</version>    
  5. </dependency>    
<dependency>  
    <groupId>org.apache.curator</groupId>  
    <artifactId>curator-test</artifactId>  
    <version>2.5.0</version>  
</dependency>  

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值