ZooKeeper 入門教程

Java陈序员發表於2024-10-23

0. 前言

文章已經收錄到 GitHub 個人部落格專案,歡迎 Star:

https://github.com/chenyl8848/chenyl8848.github.io

或者訪問網站,進行線上瀏覽:

https://chenyl8848.github.io/

1. ZooKeeper 簡介

ZooKeeper(動物園管理者)簡稱 ZK一個分散式的,開放原始碼的分散式應用程式協調服務,是 Google 的 Chubby 一個開源的實現,是 Hadoop 和 Hbase 的重要元件。ZooKeeper 使用 Java 所編寫,但是支援 Java 和 C 兩種程式語言。

應用場景

  • 分散式微服務註冊中心:Dubbo 框架、Spring Cloud 框架
  • 叢集管理:Hadoop Hbase 元件
  • 分散式鎖

關注微信公眾號:【Java陳序員】,獲取開源專案分享、AI副業分享、超200本經典計算機電子書籍等。


2. ZooKeeper 記憶體資料模型

2.1 模型結構

2.2 模型的特點

  • 每個子目錄如 /node1 都被稱作一個 znode(節點),這個 znode 是被它所在的路徑唯一標識
  • znode 可以有子節點目錄,並且每個 znode 可以儲存資料
  • znode 是有版本的,每個 znode 中儲存的資料可以有多個版本,也就是一個訪問路徑中可以儲存多份資料
  • znode 可以被監控,包括這個目錄節點中儲存的資料的修改,子節點目錄的變化等,一旦變化可以通知設定監控的客戶端

3. 節點的分類

3.1 持久節點(PERSISTENT)

指在節點建立後,就一直存在,直到有刪除操作來主動刪除這個節點 —— 不會因為建立該節點的客戶端會話失效而消失

3.2 持久順序節點(PERSISTENT_SEQUENTIAL)

這類節點的基本特性和上面的節點型別是一致的。額外的特性是,在 ZooKeeper 中,每個父節點會為他的第一級子節點維護一份時序,會記錄每個子節點建立的先後順序。基於這個特性,在建立子節點的時候,可以設定這個屬性,那麼在建立節點過程中,ZooKeeper 會自動為給定節點名加上一個數字字尾,作為新的節點名。這個數字字尾的範圍是整型的最大值。

3.3 臨時節點(EPHEMERAL)

和持久節點不同的是,臨時節點的生命週期和客戶端會話繫結。也就是說,如果客戶端會話失效,那麼這個節點就會自動被清除掉。注意,這裡提到的是會話失效,而非連線斷開。另外,在臨時節點下面不能建立子節點。

3.4 臨時順序節點(EPHEMERAL_SEQUENTIAL)

具有臨時節點特點,額外的特性是,每個父節點會為他的第一級子節點維護一份時序,這點和持久順序節點類似。


4. 安裝

4.1 Linux 系統安裝

# 1.安裝 JDK 並配置環境變數&下載 ZooKeeper 安裝包
-	https://mirrors.bfsu.edu.cn/apache/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz

# 2.下載安裝包上傳到 Linux 伺服器中,並解壓縮
-	tar -zxvf zookeeper-3.4.12.tar.gz

# 3.重新命名安裝目錄
-	mv zookeeper-3.4.12 zk

# 4.配置 zoo.cfg 配置檔案
-	1.修改 ZooKeeper 的 conf 目錄下的 zoo_simple.cfg,修改完後,重新命名為zoo.cfg
  tickTime=2000
  initLimit=10
  syncLimit=5
  dataDir=/usr/zookeeper/zkdata
  clientPort=2181

# 5.啟動 ZooKeeper
-	在 ZooKeeper 的 bin 目錄下,執行 zkServer.sh
	./bin/zkServer.sh start /usr/zookeeper/conf/zoo.cfg
	
# 6.使用 jps 檢視啟動是否成功

# 7.啟動客戶端連線到 ZooKeeper
- ./bin/zkCli.sh -server 192.168.0.220:2181
	注意:可以透過  ./bin/zkCli.sh help 檢視客戶端所有可以執行的指令

4.2 Docker 安裝 ZooKeeper

# 1.獲取 ZooKeeper 的映象
- docker pull zookeeper:3.4.14

# 2.啟動 ZooKeeper 服務
- docker run --name zk -p 2181:2181 -d zookeeper:3.4.14

5. 客戶端基本指令

# 1.ls path    							檢視特定節點下面的子節點

# 2.create path data   			建立一個節點。並給節點繫結資料(預設是永續性節點)
  - create path data								  建立持久節點(預設是持久節點)
  - create -s path data 						  建立永續性順序節點
  -	create -e path data 						  建立臨時性節點(注意:臨時節點不能含有任何子節點)
  -	create -e -s path data            建立臨時順序節點(注意:臨時節點不能含有任何子節點)
 
# 3.stat path                檢視節點狀態
# 4.set path data            修改節點資料
# 5.ls2 path                 檢視節點下孩子和當前節點的狀態
# 6.history                  檢視操作歷史
# 7.get path                 獲得節點上繫結的資料資訊
# 8.delete path              刪除節點(注意:刪除節點不能含有子節點)
# 9.rmr  path                遞迴刪除節點(注意:會將當前節點下所有節點刪除)
# 10.quit                    退出當前會話(會話失效)

6. 節點監聽機制 watch

客戶端可以監測 znode 節點的變化。znode 節點的變化會觸發相應的事件,然後清除對該節點的監測。

當監測一個 znode 節點時候,Zookeeper 會傳送通知給監測節點。一個 Watch 事件是一個一次性的觸發器,當被設定了 Watch 的資料和目錄發生了改變的時候,則伺服器將這個改變傳送給設定了 Watch 的客戶端以便通知它們

# 1.ls /path true             監聽節點目錄的變化
# 2.get /path true						監聽節點資料的變化 

7.Java 操作 ZooKeeper

7.1 建立專案引入依賴

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

7.2 獲取 ZooKeeper 客戶端物件

private ZkClient zkClient;
/**
 * 獲取zk客戶端連線
 */
@Before
public void Before() {

    // 引數1:伺服器的ip和埠
    // 引數2:會話的超時時間
    // 引數3:回話的連線時間
    // 引數4:序列化方式
    zkClient = new ZkClient("192.168.28.132:2181", 30000, 60000, new SerializableSerializer());
}
/**
 * 關閉資源
 */
@After
public void after(){
    zkClient.close();
}

7.3 常用 API

  • 建立節點
/**
 * 建立節點
 */
@Test
public void testCreateNode() {
    //第一中建立方式  返回建立節點的名稱
    String nodeName = zkClient.create("/node5", "lisi", CreateMode.PERSISTENT);
    zkClient.create("/node6", "zhangsan", CreateMode.PERSISTENT_SEQUENTIAL);
    zkClient.create("/node7", "王五", CreateMode.EPHEMERAL);
    zkClient.create("/node8", "xiaozhang", CreateMode.EPHEMERAL_SEQUENTIAL);
    //第二種建立方式 不會返回建立節點的名稱
    zkClient.createPersistent("/node1", "持久資料");
    zkClient.createPersistentSequential("/node1/aa", "持久資料順序節點");
    zkClient.createEphemeral("/node2", "臨時節點");
    zkClient.createEphemeralSequential("/node1/bb", "臨時順序節點");
}
  • 刪除節點
/**
 * 刪除節點
 */
@Test
public void testDeleteNode() {
    // 刪除沒有子節點的節點  返回值:是否刪除成功
    boolean delete = zkClient.delete("/node1");
    // 遞迴刪除節點資訊 返回值:是否刪除成功
    boolean recursive = zkClient.deleteRecursive("/node1");
}
  • 檢視節點的子節點
/**
 * 查詢節點
 */
@Test
public void testFindNodes() {
    // 獲取指定路徑的節點資訊  
    // 返回值:為當前節點的子節點資訊
    List<String> children = zkClient.getChildren("/");
    for (String child : children) {
        System.out.println(child);
    }
}
  • 檢視當前節點的資料

注意:如果出現:org.I0Itec.zkclient.exception.ZkMarshallingError: java.io.StreamCorruptedException: invalid stream header: 61616161. 異常的原因是:在 Shell 中的資料序列化方式和 Java 程式碼中使用的序列化方式不一致,因此要解決這個問題只需要保證序列化一致即可。

/**
 * 獲取節點的資料
 *
 */
@Test
public void testFindNodeData() {
    Object readData = zkClient.readData("/node3");
    System.out.println(readData);
}
  • 檢視當前節點的資料並獲取狀態資訊
/**
 * 獲取資料以及當前節點的狀態資訊
 */
@Test
public void testFindNodeDataAndStat() {
    Stat stat = new Stat();
    Object readData = zkClient.readData("/node60000000024", stat);
    System.out.println(readData);
    System.out.println(stat);
}
  • 修改節點資料
/**
 * 修改節點資料
 */
@Test
public void testUpdateNodeData() {
    zkClient.writeData("/node60000000024", new User("121", "name", "xxx"));
}
  • 監聽節點資料的變化
/**
 * 監聽節點資料的變化
 */
@Test
public void testOnNodeDataChange() throws IOException {
    zkClient.subscribeDataChanges("/node60000000024", new IZkDataListener() {
        // 當節點的值在修改時,會自動呼叫這個方法  將當前修改節點的名字,和節點變化之後的資料傳遞給方法
        public void handleDataChange(String nodeName, Object result) throws Exception {
            System.out.println(nodeName);
            System.out.println(result);
        }

        // 當節點的值被刪除的時候,會自動呼叫這個方法,會將節點的名字已引數形式傳遞給方法
        public void handleDataDeleted(String nodename) throws Exception {
            System.out.println("節點的名字:" + nodename);
        }
    });
    //阻塞客戶端
    System.in.read();
}
  • 監聽節點目錄的變化
/**
 * 監聽節點的變化
 */
@Test
public void testOnNodesChange() throws IOException {
    zkClient.subscribeChildChanges("/node60000000024", new IZkChildListener() {
        // 當節點的發生變化時,會自動呼叫這個方法
        // 引數1:父節點名稱
        // 引數2:父節點中的所有子節點名稱
        public void handleChildChange(String nodeName, List<String> list) throws Exception {
            System.out.println("父節點名稱:" + nodeName);
            System.out.println("發生變更後位元組孩子節點名稱:");
            for (String name : list) {
                System.out.println(name);
            }
        }
    });
    // 阻塞客戶端
    System.in.read();
}

8. ZooKeeper 的叢集

8.1 叢集(Cluster)

# 1.叢集(Cluster)
- 集合同一種軟體服務的多個節點同時提供服務

# 2.叢集解決問題
-  單節點的併發訪問的壓力問題
-  單節點故障問題(如硬體老化、自然災害等)

8.2 叢集架構

8.3 叢集搭建

# 1.建立三個 dataDir
- mkdir zkdata1 zkdata2 zkdata3

# 2.分別在三個dataDir目錄下面myid檔案
- touch ./zkdata1/myid
    myid 的內容是 伺服器的  表示  1|2|3

# 3.在 /conf 目錄下建立三個 ZooKeeper 配置檔案,分別為 zoo1.cfg、zoo2.cfg、zoo3.cfg	
-	zoo1.cfg
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/root/zkdata1
    clientPort=3001
    server.1=10.15.0.5:3002:3003
    server.2=10.15.0.5:4002:4003
    server.3=10.15.0.5:5002:5003
- zoo2.cfg
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/root/zkdata2
    clientPort=4001
    server.1=10.15.0.5:3002:3003
    server.2=10.15.0.5:4002:4003
    server.3=10.15.0.5:5002:5003
- zoo3.cfg
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/root/zkdata3
    clientPort=5001
    server.1=10.15.0.5:3002:3003
    server.2=10.15.0.5:4002:4003
    server.3=10.15.0.5:5002:5003

解釋:
    1.server.X: x為伺服器的唯一標識。
    2.192.168.0.220: 伺服器所在的ip地址
    3.3002: 資料同步使用的埠號
    4.3003: 選舉使用的埠號

# 4.分別啟動各個 ZooKeeper 伺服器
- ./bin/zkServer.sh start /usr/zookeeper/conf/zoo1.cfg 
- ./bin/zkServer.sh start /usr/zookeeper/conf/zoo2.cfg 
- ./bin/zkServer.sh start /usr/zookeeper/conf/zoo3.cfg

# 5.檢視各個 ZooKeeper 伺服器的角色資訊
- ./bin/zkServer.sh status /usr/zookeeper/conf/zoo1.cfg

# 6.客戶端連線任意 ZooKeeper 伺服器進行節點操作
- ./bin/zkCli.sh -server 192.168.0.220:3001

# 7.停止特定 ZooKeeper 伺服器
- ./bin/zkServer.sh stop /usr/zookeeper/conf/zoo1.cfg