Spring整合ZooKeeper基礎使用介紹

【中間件】Spring整合ZooKeeper基礎使用介紹

ZooKeeper是一個分佈式的,開放源碼的分佈式應用程序協調服務,廣泛應用於分佈式系統中,比如有用它做配置中心,註冊中心,也有使用它來實現分佈式鎖的,作爲高併發技術棧中不可或缺的一個基礎組件,接下來我們將看一下,zk應該怎麼玩,可以怎麼玩

本文作爲第一篇,將主要介紹基於zk-client的基本使用姿勢,依次來了解下zk的基本概念

<!-- more -->

I. 準備

1. zk環境安裝

用於學習試點目的的體驗zk功能,安裝比較簡單,可以參考博文: 210310-ZooKeeper安裝及初體驗

wget https://mirrors.bfsu.edu.cn/apache/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz
tar -zxvf apache-zookeeper-3.6.2-bin.tar.gz
cd apache-zookeeper-3.6.2-bin

# 前臺啓動
bin/zkServer.sh start-foreground

2. 項目環境

本文演示的是直接使用apache的zookeeper包來操作zk,與是否是SpringBoot環境無關

核心依賴

<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.7.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

版本說明:

  • zk: 3.6.2
  • SpringBoot: 2.2.1.RELEASE

II. ZK使用姿勢

1. zk基本知識點

首先介紹下zk的幾個主要的知識點,如zk的數據模型,四種常說的節點

1.1 數據模型

zk的數據模型和我們常見的目錄樹很像,從/開始,每一個層級就是一個節點

每個節點,包含數據 + 子節點

注意:EPHEMERAL節點,不能有子節點(可以理解爲這個目錄下不能再掛目錄)

zk中常說的監聽器,就是基於節點的,一般來講監聽節點的創建、刪除、數據變更

1.2 節點

  • 持久節點 persistent node
  • 持久順序節點 persistent sequental
  • 臨時節點 ephemeral node
  • 臨時順序節點 ephemeral sequental

注意:

  • 節點類型一經指定,不允許修改
  • 臨時節點,當會話結束,會自動刪除,且不能有子節點

2. 節點創建

接下來我們看一下zk的使用姿勢,首先是創建節點,當然創建前提是得先拿到zkClient

初始化連接

private ZooKeeper zooKeeper;

@PostConstruct
public void initZk() throws IOException {
    // 500s 的會話超時時間
    zooKeeper = new ZooKeeper("127.0.0.1:2181", 500_000, this);
}

節點創建方法,下面分別給出兩種不同的case

@Service
public class NodeExample implements Watcher {
    /**
     * 創建節點
     *
     * @param path
     */
    private void nodeCreate(String path) {
        // 第三個參數ACL 表示訪問控制權限
        // 第四個參數,控制創建的是持久節點,持久順序節點,還是臨時節點;臨時順序節點
        // 返回 the actual path of the created node
        // 單節點存在時,拋異常 KeeperException.NodeExists
        try {
            String node = zooKeeper.create(path + "/yes", "保存的數據".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.println("create node: " + node);
        } catch (KeeperException.NodeExistsException e) {
            // 節點存在
            System.out.println("節點已存在: " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 帶生命週期的節點
        try {
            Stat stat = new Stat();
            // 當這個節點上沒有child,且1s內沒有變動,則刪除節點
            // 實測拋了異常,未知原因
            String node = zooKeeper.create(path + "/ttl", ("now: " + LocalDateTime.now()).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, stat, 1000);
            System.out.println("ttl nod:" + node + " | " + stat);
            // 創建已給監聽器來驗證
            zooKeeper.exists(path + "/ttl", (e) -> {
                System.out.println("ttl 節點變更: " + e);
            });
        } catch (KeeperException.NodeExistsException e) {
            System.out.println("節點已存在: " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

節點創建,核心在於 zooKeeper.create(path + "/yes", "保存的數據".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

  • 當節點已存在時,再創建會拋異常 KeeperException.NodeExistsException
  • 最後一個參數,來決定我們創建的節點類型
  • todo: 上面實例中在指定ttl時,沒有成功,暫未找到原因,待解決

3. 節點存在判斷

判斷節點是否存在,比較常見了(比如我們在創建之前,可能會先判斷一下是否存在)

/**
 * 判斷節點是否存在
 */
private void checkPathExist(String path) {
    try {
        // 節點存在,則返回stat對象; 不存在時,返回null
        // watch: true 表示給這個節點添加監聽器,當節點出現創建/刪除 或者 新增數據時,觸發watcher回調
        Stat stat = zooKeeper.exists(path + "/no", false);
        System.out.println("NoStat: " + stat);
    } catch (Exception e) {
        e.printStackTrace();
    }

    try {
        // 判斷節點是否存在,並監聽 節點的創建 + 刪除 + 數據變更
        // 注意這個事件監聽,只會觸發一次,即單這個節點數據變更多次,只有第一次能拿到,之後的變動,需要重新再註冊監聽
        Stat stat = zooKeeper.exists(path + "/yes", this);
        System.out.println("YesStat: " + stat);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

注意

核心用法: zooKeeper.exists(path + "/yes", this);

  • 當節點存在時,返回Stat對象,包含一些基本信息;如果不存在,則返回null
  • 第二個參數,傳入的是事件回調對象,我們的測試類NodeExmaple 實現了接口 Watcher, 所以直接傳的是this
  • 註冊事件監聽時,需要注意這個回調只會執行一次,即觸發之後就沒了;後面再次修改、刪除、創建節點都不會再被接收到

4. 子節點獲取

獲取某個節點的所有子節點,這裏返回的是當前節點的一級子節點

/**
 * 獲取節點的所有子節點, 只能獲取一級節點
 *
 * @param path
 */
private void nodeChildren(String path) {
    try {
        // 如果獲取成功,會監聽 當前節點的刪除,子節點的創建和刪除,觸發回調事件, 這個回調也只會觸發一次
        List<String> children = zooKeeper.getChildren(path, this, new Stat());
        System.out.println("path:" + path + " 's children:" + children);
    } catch (KeeperException e) {
        System.out.println(e.getMessage());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

5. 數據獲取與修改

節點上是可以存儲數據的,在創建的時候,可以加上數據;後期可以讀取,也可以修改

/**
 * 設置數據,獲取數據
 *
 * @param path
 */
public void dataChange(String path) {
    try {
        Stat stat = new Stat();
        byte[] data = zooKeeper.getData(path, false, stat);
        System.out.println("path: " + path + " data: " + new String(data) + " : " + stat);

        // 根據版本精確匹配; version = -1 就不需要進行版本匹配了
        Stat newStat = zooKeeper.setData(path, ("new data" + LocalDateTime.now()).getBytes(), stat.getVersion());
        System.out.println("newStat: " + stat.getVersion() + "/" + newStat.getVersion() + " data: " + new String(zooKeeper.getData(path, false, stat)));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在設置數據時,可以指定版本,當version > 0時,表示根據版本精確匹配;如果爲-1時,則只要節點路徑對上就成

6. 事件監聽

監聽主要是針對節點而言,前面在判斷節點是否存在、修改數據時都可以設置監聽器,但是他們是一次性的,如果我們希望長久有效,則可以使用下面的addWatch

public void watchEvent(String path) {
    try {
        // 注意這個節點存在
        // 添加監聽, 與 exist判斷節點是否存在時添加的監聽器 不同的在於,觸發之後,依然有效還會被觸發, 只有手動調用remove纔會取消
        // 感知: 節點創建,刪除,數據變更 ; 創建子節點,刪除子節點
        // 無法感知: 子節點的子節點創建/刪除, 子節點的數據變更
        zooKeeper.addWatch(path + "/yes", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("事件觸發 on " + path + " event:" + event);
            }
        }, AddWatchMode.PERSISTENT);
    } catch (Exception e) {
        e.printStackTrace();
    }

    try {
        // 注意這個節點不存在
        // 添加監聽, 與 exist 不同的在於,觸發之後,依然有效還會被觸發, 只有手動調用remove纔會取消
        // 與前面的區別在於,它的子節點的變動也會被監聽到
        zooKeeper.addWatch(path + "/no", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("事件觸發 on " + path + " event:" + event);
            }
        }, AddWatchMode.PERSISTENT_RECURSIVE);
    } catch (Exception e) {
        e.printStackTrace();
    }

    // 移除所有的監聽
    //zooKeeper.removeAllWatches(path, WatcherType.Any, true);
}

上面給出了兩種case,

  • AddWatchMode.PERSISTENT: 表示只關心當前節點的刪除、數據變更,創建,一級子節點的創建、刪除;無法感知子節點的子節點創建、刪除,無法感知子節點的數據變更
  • AddWatchMode.PERSISTENT_RECURSIVE: 相當於遞歸監聽,改節點及其子節點的所有變更都監聽

7. 節點刪除

最後再介紹一個基本功能,節點刪除,只有子節點都不存在時,才能刪除當前節點(和linux的rmdir類似)

/**
 * 刪除節點
 */
public void deleteNode(String path) {
    try {
        // 根據版本限定刪除, -1 表示不需要管版本,path匹配就可以執行;否則需要版本匹配,不然就會拋異常
        zooKeeper.delete(path, -1);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

8. 小結

本文主要介紹的是java側對zookeeper的基本操作姿勢,可以算是zk的入門,瞭解下節點的增刪改,事件監聽;

當然一般更加推薦的是使用Curator來操作zk,相比較於apache的jar包,使用姿勢更加順滑,後面也會做對比介紹

II. 其他

0. 項目

1. 一灰灰Blog

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛

一灰灰blog

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