zookeeper與客戶端curator

zookeeper

zookeeper被廣泛用於分佈式服務中,如集羣,kafka等。但基本的api卻很簡單。通過客戶端調用簡單的zookeeper的api實現數據更新、節點檢測、權限控制、異步操作、事件監聽等,最終實現分佈式需要。

這裏簡單介紹一下客戶端api。

首先,啓動zookeeper

zkServer.sh start

開放2181端口,供外部調用

firewall-cmd --zone=public --add-port=2181/tcp --permanent

重啓防火牆

systemctl restart firewalld.service

重載配置文件讓它生效

firewall-cmd --reload

這樣就可以根據IP和端口訪問zookeeper服務器了。

啓動客戶端,就可以連接到服務器了

zkCli.sh -server 127.0.0.1:2181

zookeeper的數據結構其實就是一棵樹,每個數據節點都是一個 ZNode。znode一共4種類型:持久的,臨時的,持久有序的,臨時有序的。
提供的操作也就是操作這棵樹。輸入 h 後回車,看下zkCli.sh提供的命令

使用客戶端可以創建會話、創建節點、刪除節點、獲取子節點、獲取節點內容等。

命令行客戶端

 

命令行客戶端一般用於調試、運維,實際服務不要用它。

執行 ls / 發現,/路徑下已經有一個節點 /zookeeper 了

 

創建節點

create -e /master "hello"

這裏 -e 是標明創建臨時節點。還有-s,標明創建順序節點(會在在節點名稱後自動加上一串數字,每次創建時這串數字自增)
/master 爲節點路徑,"hello" 爲節點的數據

注意,zookeeper根節點是 / 。因此所有節點路徑都要帶上 /

查看子節點列表

ls2 /master

獲取節點

get /master

獲取節點狀態

stat /master

這裏說下節點的一些屬性:

屬性

含義

cZxid

節點被創建時的事務id

ctime

當前節點的創建時間

mZxid

節點最後一次修被改時的事務id

mtime

節點數據的修改時間

pZxid

當前節點的子節點列表最後一次被修改時的事務id

cversion

子節點的版本號

dataVersion

節點數據的版本號

aclVersion

節點訪問權限的版本號

ephemeralOwner

創建該臨時節點的會話sessionId,若是持久節點則爲0

dataLength

節點的數據長度

numChildren

子節點個數

註冊監聽

stat /master true

注意:stat 命令加true參數是爲當前客戶端註冊監聽。並且註冊監視點,是一次性下的!

修改數據

set /master "5678"

給節點設置限制

set quota -n|-b val path

這裏-n 代表限制節點的數據長度,-b 代表限制子節點個數,val是對應的值。注意,zookeeper節點超過限制後,會告警不會報錯。

原生API

zookeeper用java寫的。也提供了簡單的api供與服務器交互。

public class MyMain {


    static final CountDownLatch connectedSemaphore = new CountDownLatch(1);

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        // 採用Watcher實現,不過Watcher監聽是一次性的
        ZooKeeper zk = new ZooKeeper("172.16.59.154:2181", 5000, new WatcherListener(connectedSemaphore));

        System.out.println("ZooKeeper Connecting");

        // /進行阻塞,直到連接成功
        connectedSemaphore.await();

        System.out.println("ZooKeeper Connected is " + zk.getState().isConnected());

        // 獲取子節點列表
        List<String> children = zk.getChildren("/", true);
        for (String ch : children) {

            Stat stat = new Stat();
            byte[] rt = zk.getData("/" + ch, false, stat);
            System.out.println("Path : /" + ch + " get : " + rt.toString() + " stat " + stat.toString());
        }


        // 創建節點,節點的狀態會存入stat中
        Stat stat = new Stat();
        String p = zk.create("/test", " 123".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL, stat);
        System.out.println("Zk created :" + p);

        // 檢測節點的存在,不存在返回null
        Stat existStat = zk.exists("/test", false);
        System.out.println("exist node ? :" + (existStat != null ? "Yes" : "No"));

        // 修改節點數據
        Stat rtStat = zk.setData("/test", "456".getBytes(), stat.getVersion());
        System.out.println("Zk setData");

        // 查詢節點數據
        byte[] r = zk.getData("/test", false, stat);

        System.out.println("Zk getData " + new String(r));

        // 刪除節點
        zk.delete("/test", stat.getVersion());

        // 關閉會話
        zk.close();
        System.out.println("The end");
    }
}

客戶端對象的構建需要一個監聽對象

public class WatcherListener implements Watcher {

    private CountDownLatch connectedSemaphore;

    public void process(WatchedEvent event) {
        // 注意,這裏原生event只包含節點狀態、event類型、節點路徑信息。不包含節點數據。
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if (Event.EventType.None == event.getType()) {
                //如果建立連接成功,則發送信號量,讓後續阻塞程序向下執行
                connectedSemaphore.countDown();
                System.out.println("ZooKeeper 建立連接");
            }
        } else {
            System.out.println("Path:" + event.getPath() + ", event :" + event.getType().name());
        }
    }

    public WatcherListener(CountDownLatch countDownLatch) {
        this.connectedSemaphore = countDownLatch;
    }
}

通過原生api構建客戶端對象,操作節點。注意:原生api的事件監聽跟命令行客戶端一樣,都是一次性的。

zkClient

ZkClient是由Datameer的工程師開發的開源客戶端,對Zookeeper的原生API進行了包裝,實現了超時重連、Watcher反覆註冊等功能。

public class MyMain {

    public static void main(String[] args) {
        ZkClient zkClient = new ZkClient("172.16.59.154:2181", 1000 * 6, 10000, new SerializableSerializer());
        System.out.println("connected success");
        User user = new User();
        user.setName("kaka");

        // 創建節點
        String path = zkClient.create("/hust", user, CreateMode.PERSISTENT);


        zkClient.addAuthInfo("", null);
        // TODO

        System.out.println("created hust " + path);

        user.setName("zw");

        // 臨時節點下不能創建子節點
        zkClient.create("/hust/zw", user, CreateMode.EPHEMERAL);

        boolean existZw = zkClient.exists("/hust/zw");
        System.out.println("created zw " + path + " : " + existZw);

        User findResult = zkClient.readData("/hust");

        System.out.println(findResult.toString());


        Stat stat = new Stat();
        User resultWithStat = zkClient.readData("/hust", stat);
        System.out.println(resultWithStat.toString());
        System.out.println(stat.toString());


        List<String> childrens = zkClient.getChildren("/hust");

        for (String children : childrens) {
            System.out.println(children);
            User ch = zkClient.readData("/hust/" + children);

            System.out.println("each children : " + ch.getName());
        }

        // 迭代刪除子節點
        boolean deleteResult = zkClient.deleteRecursive("/hust");
        System.out.println("Delete /hust result : " + deleteResult);

        zkClient.subscribeChildChanges("/hust", new MinChildListener());
        System.out.println("The end");
    }

    private static class MinChildListener implements IZkChildListener {
        public void handleChildChange(String s, List<String> list) throws Exception {

        }
    }
}

zkClient可以省去反覆註冊監聽,而且它需要一個序列化器,也就是說我們直接把java對象傳遞給zookeeper存儲。

還有IZkDataListener,IZkStateListener 可供選擇監聽。

curator客戶端

Curator是Netflix公司開源的一套zookeeper客戶端框架,解決了很多Zookeeper客戶端非常底層的細節開發工作,包括連接重連、反覆註冊Watcher和NodeExistsException異常等等。一句話,是一個非常優秀的java包。

Curator包含了幾個包:
curator-framework:對zookeeper的底層api的一些封裝
curator-client:提供一些客戶端的操作,例如重試策略等
curator-recipes:封裝了一些高級特性,如:Cache事件監聽、選舉、分佈式鎖、分佈式計數器、分佈式Barrier等

簡單看例子

引入依賴,注意兼容性問題。否則報java.lang.NoClassDefFoundError: org/apache/zookeeper/MultiTransactionRecord

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'

    // 注意這裏 3.6.0 暫時不支持 curator的4.3.0
    compile "org.apache.zookeeper:zookeeper:3.5.0"
    compile "org.apache.curator:curator-framework:4.3.0"
    compile "org.apache.curator:curator-client:4.3.0"
    compile "org.apache.curator:curator-recipes:4.3.0"

}

例子

public class MinMain {

    private static CountDownLatch count = new CountDownLatch(1);

    public static void main(String[] args) throws Exception {
        // 重試策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("172.16.59.154:2181").sessionTimeoutMs(5000).connectionTimeoutMs(5000).retryPolicy(retryPolicy)
                .namespace("base")
                .build();

        client.start();// 調用start後纔會去連接


        // 創建數據
        client.create().creatingParentContainersIfNeeded()// 遞歸創建所需父節點
                .withMode(CreateMode.PERSISTENT) // 創建類型爲持久節點
                .forPath("/nodeA", "init".getBytes()); // 目錄及內容

        Stat stat = new Stat();
        // 讀取數據
        byte[] bytes = client.getData().storingStatIn(stat).forPath("/nodeA");
        System.out.println("Get data " + new String(bytes) + " stat " + stat.toString());

        ExecutorService pool = Executors.newFixedThreadPool(2);
        /**
         * 監聽數據節點的變化情況
         */
        final NodeCache nodeCache = new NodeCache(client, "/nodeA", false);
        nodeCache.start(true);
        nodeCache.getListenable().addListener(
                new NodeCacheListener() {
                    @Override
                    public void nodeChanged() throws Exception {
                        System.out.println("Node data is changed, new data: " + new String(nodeCache.getCurrentData().getData()));
                    }
                },
                pool
        );

        // 修改數據
        client.setData()
                .withVersion(stat.getVersion()) // 指定版本修改
                .forPath("/nodeA", "data".getBytes());


        List<String> children = client.getChildren().forPath("/"); // 獲取子節點的路徑
        for (String child : children) {
            System.out.println("path : " + "/base/" + child);
        }


        /**
         * 監聽子節點的變化情況
         */
        final PathChildrenCache childrenCache = new PathChildrenCache(client, "/nodeA", true);
        childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
        childrenCache.getListenable().addListener(
                new PathChildrenCacheListener() {
                    @Override
                    public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                        switch (event.getType()) {
                            case CHILD_ADDED:
                                System.out.println("CHILD_ADDED for: " + event.getData().getPath());
                                break;
                            case CHILD_REMOVED:
                                System.out.println("CHILD_REMOVED for: " + event.getData().getPath());
                                break;
                            case CHILD_UPDATED:
                                System.out.println("CHILD_UPDATED for: " + event.getData().getPath());
                                break;
                            default:
                                System.out.println("連接異常" + event.getType());
                                break;
                        }
                        MinMain.count.countDown();
                    }
                },
                pool
        );

        // 事務
        Collection<CuratorTransactionResult> transactionResultCollection = client.inTransaction()
                .create().withMode(CreateMode.EPHEMERAL).forPath("/nodeA/one", "sssss".getBytes())
                .and()
                .create().withMode(CreateMode.EPHEMERAL).forPath("/nodeA/two", "sss".getBytes())
                .and()
                .commit();
        for (CuratorTransactionResult result : transactionResultCollection) {
            System.out.println("TransactionResult " + result);
        }

        // 節點檢測,返回結果不空則存在
        Stat exist = client.checkExists() // 檢查是否存在
                .forPath("/nodeA");
        System.out.println("Exist znode /nodeA " + (exist != null ? "Yes" : "No"));


        // 異步調用:在forPath 前調用 inBackground 並傳入參數即可
        Executor executor = Executors.newFixedThreadPool(2);
        client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .inBackground((curatorFramework, curatorEvent) -> {
                    System.out.println(String.format("異步調用 eventType:%s,ResultCode:%s", curatorEvent.getType(), curatorEvent.getResultCode()));
                    MinMain.count.countDown();
                }, executor)
                .forPath("/path");

        MinMain.count.await();
        // 刪除
        client.delete()
                .guaranteed()  // 強制保證刪除
                .deletingChildrenIfNeeded() // 遞歸刪除子節點
                .withVersion(exist.getVersion()) // 指定刪除的版本號
                .forPath("/nodeA");

        client.close();
        System.out.println("The end");
    }
}

功能非常強大,而且是 Fluent編程風格,非常優雅。對應的event和操作也很全面

 

CuratorEventType

對應method

CREATE

CuratorFramework#create()

DELETE

CuratorFramework#delete()

EXISTS

CuratorFramework#checkExists()

GET_DATA

CuratorFramework#getData()

SET_DATA

CuratorFramework#setData()

CHILDREN

CuratorFramework#getChildren()

SYNC

CuratorFramework#sync(String, Object)

GET_ACL

CuratorFramework#getACL()

SET_ACL

CuratorFramework#setACL()

TRANSACTION

CuratorFramework#transaction()

GET_CONFIG

CuratorFramework#getConfig()

RECONFIG

CuratorFramework#reconfig()

WATCHED

Watchable#usingWatcher(Watcher)

Watchable#watched()

REMOVE_WATCHES

CuratorFramework#watches()

CLOSING

客戶端關閉

 

curator recipes

這裏單獨拎出來說。zookeeper只提供基本的api,然後給出一些解決方案。但是沒有實現它。簡單來說提供了材料(原生api)、菜單(doc裏的方案場景),就是沒有提供菜譜(方案)。
引用官方文檔:

Curator implements all of the recipes listed on the ZooKeeper recipes doc (except two phase commit). NOTE: Most Curator recipes will autocreate parent nodes of paths given to the recipe as CreateMode.CONTAINER. 

意思:Curator實現了除ZooKeeper二次提交外的所有的技巧(recipes)。Curator Recipes可以實現自動化創建節點路徑的父節點。

也就是說zookeeper列舉的分佈式中的一些方案,它都實現了。

採用cache來封裝了對事件的監聽,包括監聽節點,監聽子節點等。這裏簡單列舉,就不舉例說明了。

cache

說明

NodeCache

主要用來監聽節點本身的變化,當節點的狀態發生變更後,回調NodeCachaListener

PathChildrenCache

主要用來監聽子節點

TreeCache

即能監聽節點也能監聽節點的子節點變更

注意:這裏是對節點和子節點的監聽,二級節點變化不會被監聽。

Elections(選舉)
Locks(鎖)
Barriers(阻塞)
Counters(計數器)
Caches(緩存)
Nodes(節點)
Queues(隊列)

這些功能待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章