zookeeper Curator框架簡單使用

github: https://github.com/zhaikaishun/zookeeper_tutorial


Curator框架的目的

官網首頁介紹是 Guava is to Java what Curator is to Xookeeper ,爲了更好的實現java操作zookeeper服務器,後來出現了Curator框架,非常的強大,目前已經是Apache的頂級項目,裏面提供了更多豐富的操作,例如session超時重連、主從選舉、分佈式計算器、分佈式鎖等等適用於各種複雜zookeeper場景的API封裝。
Maven依賴
jar包下載
都去官網下載,http://curator.apache.org/

Curatot框架使用(一)

Curatir框架使用鏈式編程風格,易讀性更強,使用工程方法創建連接對象。
1 使用CuratorFrameworkFactory的兩個靜態工廠方法(參數不同)來實現:

  • 參數1: connectString,連接串
  • 參數2: retyPolicy,重試連接策略。有四中實現分別爲:
    ExponentialBackoffRetry、RetryTimes、RetryOneTimes、RetryUntilElapsed(具體參數的意思以後會講解,也可先上網查看)
  • 參數3:sessionTimeoutMs 會話超時時間 默認爲60000ms
  • 參數4:connectionTimeoutMs 連接超時時間,默認爲15000ms
    注意:對於retryPolicy策略通過一個接口來讓用戶自定義實現。
    代碼在package bjsxt.curator.base;

代碼示例
前面的設置

    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181,192.168.1.33:2181";
    /** session超時時間 */
    static final int SESSION_OUTTIME = 5000;//ms 

    public static void main(String[] args) throws Exception {

        //1 重試策略:初試時間爲1s 重試10次
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
        //2 通過工廠創建連接
        CuratorFramework cf = CuratorFrameworkFactory.builder()
                    .connectString(CONNECT_ADDR)
                    .sessionTimeoutMs(SESSION_OUTTIME)
                    .retryPolicy(retryPolicy)
//                  .namespace("super")
                    .build();
        //3 開啓連接
        cf.start();

        System.out.println(States.CONNECTED);
        System.out.println(cf.getState());

Curator的基本方法

1.創建連接

2.Curator創建節點

Create方法,可選鏈式項:creatingParentslfNeeded、withMode、forPath、withACL等。
例如

//4 建立節點 指定節點類型(不加withMode默認爲持久類型節點)、路徑、數據內容
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c1","c1內容".getBytes());

或者 

//      cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c1","c1內容".getBytes());
//      cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c2","c2內容".getBytes());

3.刪除節點

delete方法,可選鏈式項: deletingChildrenIfNeeded、guranteed、withVersion、forPath等。
例如

cf.delete().guaranteed().deletingChildrenIfNeeded().forPath("/super");

4.讀取和修改數據

getData、setData方法

//讀取節點
String ret1 = new String(cf.getData().forPath("/super/c2"));
System.out.println(ret1);
//修改節點
cf.setData().forPath("/super/c2", "修改c2內容".getBytes());
String ret2 = new String(cf.getData().forPath("/super/c2"));
System.out.println(ret2);   

5.異步回調方法。

比如創建節點時綁定一個回調函數,該回調函數可以輸出服務器的狀態碼以及服務器事件類型。還可以加入一個線程池進行優化操作。

ExecutorService pool = Executors.newCachedThreadPool();
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
.inBackground(new BackgroundCallback() {
    @Override
    public void processResult(CuratorFramework cf, CuratorEvent ce) throws Exception {
        System.out.println("code:" + ce.getResultCode());
        System.out.println("type:" + ce.getType());
        System.out.println("線程爲:" + Thread.currentThread().getName());
    }
}, pool)
.forPath("/super/c3","c3內容".getBytes());
Thread.sleep(Integer.MAX_VALUE);

6.讀取子節點方法

getChildren

List<String> list = cf.getChildren().forPath("/super");
for(String p : list){
    System.out.println(p);
}

7.判斷子節點是否存在

checkExists方法

Stat stat = cf.checkExists().forPath("/super/c3");
System.out.println(stat);

講上面異步回調的那個線程池的作用

比如某個操作一次性要創建500個節點,不可能一次用500個線程去處理。所以這裏使用的是一個線程池來進行控制

CuratorWatcher

原理,使用緩存的判斷的方式,不需要重複註冊!!!最牛的地方,估計可以想到那個宕機訂閱問題。具體的原理,建議深入瞭解一下,感覺挺厲害的。

1. 方法1

注意最後一個參數,這個是是否壓縮 , 注意那個cache.star的時候的那個模式 POST_INITALLZED_EVENT
直接上代碼看即可

public class CuratorWatcher1 {

    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181,192.168.1.33:2181";
    /** session超時時間 */
    static final int SESSION_OUTTIME = 5000;//ms 

    public static void main(String[] args) throws Exception {

        //1 重試策略:初試時間爲1s 重試10次
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
        //2 通過工廠創建連接
        CuratorFramework cf = CuratorFrameworkFactory.builder()
                    .connectString(CONNECT_ADDR)
                    .sessionTimeoutMs(SESSION_OUTTIME)
                    .retryPolicy(retryPolicy)
                    .build();

        //3 建立連接
        cf.start();

        //4 建立一個cache緩存
        final NodeCache cache = new NodeCache(cf, "/super", false);
        cache.start(true);
        cache.getListenable().addListener(new NodeCacheListener() {
            /**
             * <B>方法名稱:</B>nodeChanged<BR>
             * <B>概要說明:</B>觸發事件爲創建節點和更新節點,在刪除節點的時候並不觸發此操作。<BR>
             * @see org.apache.curator.framework.recipes.cache.NodeCacheListener#nodeChanged()
             */
            @Override
            public void nodeChanged() throws Exception {
                System.out.println("路徑爲:" + cache.getCurrentData().getPath());
                System.out.println("數據爲:" + new String(cache.getCurrentData().getData()));
                System.out.println("狀態爲:" + cache.getCurrentData().getStat());
                System.out.println("---------------------------------------");
            }
        });

        Thread.sleep(1000);
        cf.create().forPath("/super", "123".getBytes());

        Thread.sleep(1000);
        cf.setData().forPath("/super", "456".getBytes());

        Thread.sleep(1000);
        cf.delete().forPath("/super");

        Thread.sleep(Integer.MAX_VALUE);

    }
}

輸出

路徑爲:/super
數據爲:123
狀態爲:38654705677,38654705677,1509971265443,1509971265443,0,0,0,0,3,0,38654705677

---------------------------------------
路徑爲:/super
數據爲:456
狀態爲:38654705677,38654705678,1509971265443,1509971266479,1,0,0,0,3,0,38654705677

---------------------------------------

2. 方法2

注意第三個參數,表示是否接受節點數據內容,如果爲false則不接受

public class CuratorWatcher2 {

    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
    /** session超時時間 */
    static final int SESSION_OUTTIME = 10000;//ms

    public static void main(String[] args) throws Exception {

        //1 重試策略:初試時間爲1s 重試10次
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
        //2 通過工廠創建連接
        CuratorFramework cf = CuratorFrameworkFactory.builder()
                    .connectString(CONNECT_ADDR)
                    .sessionTimeoutMs(SESSION_OUTTIME)
                    .retryPolicy(retryPolicy)
                    .build();

        //3 建立連接
        cf.start();

        //4 建立一個PathChildrenCache緩存,第三個參數爲是否接受節點數據內容 如果爲false則不接受
        PathChildrenCache cache = new PathChildrenCache(cf, "/super", true);
        //5 在初始化的時候就進行緩存監聽
        cache.start(StartMode.POST_INITIALIZED_EVENT);
        cache.getListenable().addListener(new PathChildrenCacheListener() {
            /**
             * <B>方法名稱:</B>監聽子節點變更<BR>
             * <B>概要說明:</B>新建、修改、刪除<BR>
             * @see org.apache.curator.framework.recipes.cache.PathChildrenCacheListener#childEvent(org.apache.curator.framework.CuratorFramework, org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent)
             */
            @Override
            public void childEvent(CuratorFramework cf, PathChildrenCacheEvent event) throws Exception {
                switch (event.getType()) {
                case CHILD_ADDED:
                    System.out.println("CHILD_ADDED :" + event.getData().getPath());
                    //也可以獲取內容
                    System.out.println("CHILD_ADDED 內容 :" + new String(event.getData().getData(),"utf-8"));
                    break;
                case CHILD_UPDATED:
                    System.out.println("CHILD_UPDATED :" + event.getData().getPath());
                    System.out.println("CHILD_UPDATED 內容 :" + new String(event.getData().getData(),"utf-8"));
                    break;
                case CHILD_REMOVED:
                    System.out.println("CHILD_REMOVED :" + event.getData().getPath());
                    break;
                default:
                    break;
                }
            }
        });

        //創建本身節點不發生變化
        cf.create().forPath("/super", "init".getBytes());

        //添加子節點
        Thread.sleep(1000);
        cf.create().forPath("/super/c1", "c1內容".getBytes());
        Thread.sleep(1000);
        cf.create().forPath("/super/c2", "c2內容".getBytes());

        //修改子節點
        Thread.sleep(1000);
        cf.setData().forPath("/super/c1", "c1更新內容".getBytes());

        //刪除子節點
        Thread.sleep(1000);
        cf.delete().forPath("/super/c2");       

        //刪除本身節點
        Thread.sleep(1000);
        cf.delete().deletingChildrenIfNeeded().forPath("/super");

        System.out.println("------end------");
        Thread.sleep(Integer.MAX_VALUE);

    }
}

運行結果

CHILD_ADDED :/super/c1
CHILD_ADDED 內容 :c1內容
CHILD_ADDED :/super/c2
CHILD_ADDED 內容 :c2內容
CHILD_UPDATED :/super/c1
CHILD_UPDATED 內容 :c1更新內容
CHILD_REMOVED :/super/c2
CHILD_REMOVED :/super/c1
------end------

Curator場景應用(一)

分佈式鎖功能

在分佈式場景中,我們爲了保證數據的一致性,經常在程序運行的某一個點需要進行同步操作(java可提供synchronized或者Reentrantlock實現)比如我們看一個小示例,這個示例會出現分佈式不同步的問題:
因爲我們之前所說的是再高併發下訪問一個程序,現在我們則是在高併發下訪問多個服務器節點(分佈式)
我們使用Curator基於Zookeeper的特性提供的分佈式鎖來處理分佈式場景的數據一致性,zookeeper原生的寫分佈式比較麻煩,我們這裏強烈推薦使用Curator的分佈式鎖!
Curator主要使用 InterProcessMutex 來進行分佈式鎖的控制

public class Lock2 {

    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
    /** session超時時間 */
    static final int SESSION_OUTTIME = 20000;//ms

    static int count = 10;
    public static void genarNo(){
        try {
            count--;
            System.out.println(count);
        } finally {

        }
    }

    public static void main(String[] args) throws Exception {

        //1 重試策略:初試時間爲1s 重試10次
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
        //2 通過工廠創建連接
        CuratorFramework cf = CuratorFrameworkFactory.builder()
                    .connectString(CONNECT_ADDR)
                    .sessionTimeoutMs(SESSION_OUTTIME)
                    .retryPolicy(retryPolicy)
//                  .namespace("super")
                    .build();
        //3 開啓連接
        cf.start();

        //4 分佈式鎖
        final CountDownLatch countdown = new CountDownLatch(1);

        for(int i = 0; i < 10; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    InterProcessMutex lock = new InterProcessMutex(cf, "/super");
                    try {
                        countdown.await();
                        //加鎖
                        lock.acquire();
                        //-------------業務處理開始
                        genarNo();
                        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");

                        Thread.sleep(500);
                        System.out.println(Thread.currentThread().getName()+"執行此操作");
                        //-------------業務處理結束
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            //釋放
                            lock.release();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            },"t" + i).start();
        }
        Thread.sleep(100);
        countdown.countDown();
    }
}

輸出

9
t9執行此操作
8
t8執行此操作
7
t3執行此操作
6
t0執行此操作
5
t6執行此操作
4
t7執行此操作
3
t2執行此操作
2
t5執行此操作
1
t4執行此操作
0
t1執行此操作

我們可以看到,這裏new了10個線程,但是每個線程裏面都有各自的鎖,按照道理來說,他們各部干擾,但是從結果可以看出來,這個程序還是同步的,也實現了鎖的原理。(相當於不同的程序放在不同的機器上,也有類似的效果)。

分佈式計數器功能

一說到分佈式計數器,你可能腦海裏想到了AtomicInteger這種經典的方式,如果針對一個jvm的場景當然沒有問題,但是我們現在是在分佈式場景下,就需要利用Curator框架的DistributedAtomicInteger了
代碼

public class CuratorAtomicInteger {

    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
    /** session超時時間 */
    static final int SESSION_OUTTIME = 5000;//ms 

    public static void main(String[] args) throws Exception {

        //1 重試策略:初試時間爲1s 重試10次
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
        //2 通過工廠創建連接
        CuratorFramework cf = CuratorFrameworkFactory.builder()
                    .connectString(CONNECT_ADDR)
                    .sessionTimeoutMs(SESSION_OUTTIME)
                    .retryPolicy(retryPolicy)
                    .build();
        //3 開啓連接
        cf.start();
        //cf.delete().forPath("/super");


        //4 使用DistributedAtomicInteger
        DistributedAtomicInteger atomicIntger = 
                new DistributedAtomicInteger(cf, "/super", new RetryNTimes(3, 1000));
        //atomicIntger.forceSet(0);  //第一次需要有吧?

        AtomicValue<Integer> value = atomicIntger.add(1);
//      atomicIntger.increment();
//      AtomicValue<Integer> value = atomicIntger.get();
        System.out.println(value.succeeded());
        System.out.println(value.postValue());  //最新值
        System.out.println(value.preValue());   //原始值

    }
}

第一次運行

true
1
0

第二次運行

true
2
1

第三次運行

true
3
2

其實這也就模擬了分佈式的計數功能

barrier功能

有這樣的場景,多個程序在不同的機器中,需要等待同時都準備好了,再一起運行
有兩種方式,一種是等所有的都準備好在一起跑,一種是有一個開關,這個開關打開就跑,直接看代碼
方式一: 估計不常用

public class CuratorBarrier1 {

    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
    /** session超時時間 */
    static final int SESSION_OUTTIME = 5000;//ms 

    public static void main(String[] args) throws Exception {



        for(int i = 0; i < 5; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
                        CuratorFramework cf = CuratorFrameworkFactory.builder()
                                    .connectString(CONNECT_ADDR)
                                    .retryPolicy(retryPolicy)
                                    .build();
                        cf.start();

                        DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(cf, "/super", 5);
                        Thread.sleep(1000 * (new Random()).nextInt(3)); 
                        System.out.println(Thread.currentThread().getName() + "已經準備");
                        barrier.enter();
                        System.out.println("同時開始運行...");
                        Thread.sleep(1000 * (new Random()).nextInt(3));
                        System.out.println(Thread.currentThread().getName() + "運行完畢");
                        barrier.leave();
                        System.out.println("同時退出運行...");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            },"t" + i).start();
        }

    }
}

運行結果

t4已經準備
t2已經準備
t0已經準備
t1已經準備
t3已經準備
同時開始運行...
同時開始運行...
t2運行完畢
同時開始運行...
t4運行完畢
同時開始運行...
t0運行完畢
同時開始運行...
t3運行完畢
t1運行完畢
同時退出運行...
同時退出運行...
同時退出運行...
同時退出運行...
同時退出運行...

方式二: 可能用的多一些,切近實際一些
代碼如下

public class CuratorBarrier2 {

    /** zookeeper地址 */
    static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181";
    /** session超時時間 */
    static final int SESSION_OUTTIME = 50000;//ms

    static DistributedBarrier barrier = null;

    public static void main(String[] args) throws Exception {



        for(int i = 0; i < 5; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
                        CuratorFramework cf = CuratorFrameworkFactory.builder()
                                    .connectString(CONNECT_ADDR)
                                    .sessionTimeoutMs(SESSION_OUTTIME)
                                    .retryPolicy(retryPolicy)
                                    .build();
                        cf.start();
                        barrier = new DistributedBarrier(cf, "/super");
                        System.out.println(Thread.currentThread().getName() + "設置barrier!");
                        barrier.setBarrier();   //設置
                        barrier.waitOnBarrier();    //等待
                        System.out.println("---------開始執行程序----------");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            },"t" + i).start();
        }

        Thread.sleep(5000);
        barrier.removeBarrier();    //釋放
    }
}

運行結果

t0設置barrier!
t4設置barrier!
t2設置barrier!
t3設置barrier!
t1設置barrier!
---------開始執行程序----------

集羣的功能

管理配置等
注意,這個之後訂閱後,宕機後再次打開,也會接受節點變更的信號。 我估計是由於緩存? 不太明白,具體再看了。

TODO

本文大多來自於筆記,好記性不如爛筆頭,爛筆頭這年頭比不上雲筆記了

發佈了137 篇原創文章 · 獲贊 211 · 訪問量 59萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章