Zookeeper 教程(超詳細)

1. Zookeeper 入門

1.1 概述

  Zookeeper 是一個開源的分佈式的,爲分佈式應用提供協調服務的 Apache 項目。

  Zookeeper 從設計模式角度來理解:是一個基於觀案者模式設計的分佈式服務管理框架,它負責存儲和管理大家都關心的數據,然後接受觀察者的註冊,一旦這些數據的狀態發生變化,Zookeeper 就將負責通知已經在 Zookeeper 上註冊的那些觀察者做出相應的反應。

在這裏插入圖片描述

1.2 特點

在這裏插入圖片描述

  1. Zookeeper:一個領導者(Leader) ,多個跟隨者(Follower)組成的集羣。
  2. 集羣中只要有半數以上節點存活,Zookeeper 集羣就能正常服務。
  3. 全局數據一致:每個 Server 保存一份相同的數據副本,Client 無論連接到哪個 Server,數據都是一致的。
  4. 更新請求順序進行,來自同一個 Client 的更新請求按其發送順序依次執行。
  5. 數據更新原子性,一次數據更新要麼成功,要麼失敗。
  6. 實時性,在一定時間範圍內,Client 能讀到最新數據。

1.3 數據結構

  ZooKeeper 數據模型的結構與 Unix 文件系統很類似,整體上可以看作是一棵樹,每個節點稱做一個 ZNode。每一個 ZNode 默認能夠存儲 1 MB 的數據,每個 ZNode 都可以通過其路徑唯一標識。
在這裏插入圖片描述

1.4 應用場景

  提供的服務包括:統一命名服務、統一配置管理、統一集羣管理、服務器節點動態上下線、軟負載均衡等。

  1. 統一命名服務

    在分佈式環境下,經常需要對應用/服務進行統一命名 ,便於識別。例如:IP 不容易記住,而域名容易記住。
    在這裏插入圖片描述

  2. 統一配置管理

    (1)分佈式環境下,配置文件同步非常常見。

      ① 一般要求一個集羣中,所有節點的配置信息是一致的,比如 Kafka 集羣。
      ② 對配置文件修改後,希望能夠快速同步到各個節點上。

    (2)配置管理可交由 ZooKeeper 實現。

      ① 可將配置信息寫入 ZooKeeper 上的一個 Znode 。
      ② 各個客戶端服務器監聽這個 Znode。
      ③ 一旦 Znode 中的數據被修改,ZooKeeper 將通知各個客戶端服務器。
    在這裏插入圖片描述

  3. 統一集羣管理

    (1)分佈式環境中,實時掌握每個節點的狀態是必要的。

      可根據節點實時狀態做出一些調整。

    (2)ZooKeeper 可以實現實時監控節點狀態變化

      ① 可將節點信息寫入Z ooKeeper 上的一個 ZNode。
      ② 監聽這個 ZNode 可獲取它的實時狀態變化。
    在這裏插入圖片描述

  4. 服務器動態上下線

    在這裏插入圖片描述

  5. 軟負載均衡

    在 Zookeeper 中記錄每臺服務器的訪問數,讓訪問數最少的服務器去處理最新的客戶端請求。
    在這裏插入圖片描述

2. Zookeeper 安裝

2.1 下載地址

  zookeeper 官網

2.2 本地模式安裝部署

  1. 準備工作

    ① 拷貝 Zookeeper 安裝包到 Linux 系統下(apache-zookeeper-3.5.6-bin.tar.gz)
    ② 解壓到指定目錄

tar -zxvf apache-zookeeper-3.5.6-bin.tar.gz -C /hadoop/

    ③ 重命名

mv apache-zookeeper-3.5.6-bin/ zookeeper-3.5.6
  1. 修改配置

    ① 將 /hadoop/zookeeper-3.5.6/conf 這個路徑下的 zoo_sample.cfg 修改爲 zoo.cfg

mv zoo_sample.cfg zoo.cfg

    ② 打開 zoo.cfg 文件,修改 dataDir 路徑

dataDir=/hadoop/zookeeper-3.5.6/zkData

    ③ 在 /hadoop/zookeeper-3.5.6/ 這個目錄上創建 zkData 文件夾

mkdir zkData
  1. 修改環境變量

    ① 打開配置文件

vim /etc/profile

    ② 在配置文件中添加以下內容

#ZOOKEEPER
export ZOOKEEPER_HOME=/hadoop/zookeeper-3.5.6
export PATH=$PATH:$ZOOKEEPER_HOME/bin

    ③ 使配置文件生效

source /etc/profile
  1. 操作 Zookeeper

    ① 啓動 Zookeeper

zkServer.sh start

    ② 查看進程是否啓動

jps

    在這裏插入圖片描述
    ③ 查看狀態

zkServer.sh status

    ④ 啓動客戶端

zkCli.sh

    ⑤ 退出客戶端

quit

    ⑥ 停止 Zookeeper

zkServer.sh stop

2.3 分佈式安裝部署

  1. 集羣規劃

    在 master、slave1 和 slave2 三個節點上部署 Zookeeper。

  2. 解壓安裝

    ① 解壓 Zookeeper 安裝包到 /hadoop/ 目錄下

tar -zxvf apache-zookeeper-3.5.6-bin.tar.gz -C /hadoop/

    ③ 重命名

mv apache-zookeeper-3.5.6-bin/ zookeeper-3.5.6

    ③ 同步 /hadoop/zookeeper-3.5.6 目錄內容到 slave1、slave2

 xsync zookeeper-3.5.6/
  1. 配置服務器編號

    ① 在 /hadoop/zookeeper-3.5.6/ 這個目錄下創建 zkData

mkdir zkData

    ② /hadoop/zookeeper-3.5.6/zkData 目錄下創建一個 myid 的文件

 touch myid

    ③ 編輯 myid 文件

vim myid

     在文件中添加與 server 對應的編號:

0

    ④ 分發到其他機器上

xsync myid

     並分別在 slave1、slave2 上修改 myid 文件中內容爲 1、2

  1. 配置 zoo.cfg 文件

    ① 將 /hadoop/zookeeper-3.5.6/conf 這個路徑下的 zoo_sample.cfg 修改爲 zoo.cfg

mv zoo_sample.cfg zoo.cfg

    ② 打開 zoo.cfg 文件,修改 dataDir 路徑

dataDir=/hadoop/zookeeper-3.5.6/zkData

    增加如下配置

server.0=master:2888:3888
server.1=slave1:2888:3888
server.2=slave2:2888:3888

    ③ 同步 zoo.cfg 配置文件

xsync zoo.cfg
  1. 修改環境變量

    ① 打開配置文件

vim /etc/profile

    ② 在配置文件中添加以下內容

#ZOOKEEPER
export ZOOKEEPER_HOME=/hadoop/zookeeper-3.5.6
export PATH=$PATH:$ZOOKEEPER_HOME/bin

    ③ 同步配置文件

xsync /etc/profile

    ④ 使配置文件生效(三臺機器)

source /etc/profile
  1. 集羣操作

    ① 三臺機器分別啓動 Zookeeper

zkServer.sh start

    ② 三臺機器分別關閉 Zookeeper

zkServer.sh stop
  1. 編寫 Zookeeper 的羣起羣關腳本

    ① 在 /usr/local/bin 目錄下創建 zk 文件

vim zk

     在文件中輸入以下內容:

#!/bin/bash

case $1 in
"start"){
  for i in master slave1 slave2
    do
      echo "****************** $i *********************"
      ssh $i "source /etc/profile && zkServer.sh start"
    done
};;

"stop"){
  for i in master slave1 slave2
    do
      echo "****************** $i *********************"
      ssh $i "source /etc/profile && zkServer.sh stop"
    done
};;

esac

    ② 修改腳本 zk 具有執行權限

chmod 777 zk

    ③ 調用腳本形式:zk startzk stop

2.4 配置參數解讀

  Zookeeper 中的配置文件 zoo.cfg 中參數含義解讀如下:

  1. tickTime =2000:通信心跳數,Zookeeper 服務器與客戶端心跳時間,單位毫秒

    Zookeeper 使用的基本時間,服務器之間或客戶端與服務器之間維持心跳的時間間隔,也就是每個tickTime 時間就會發送一個心跳,時間單位爲毫秒。它用於心跳機制,並且設置最小的 session 超時時間爲兩倍心跳時間。(session 的最小超時時間是 2*tickTime)

  2. initLimit =10:LF 初始通信時限

    集羣中的 Follower 跟隨者服務器與 Leader 領導者服務器之間初始連接時能容忍的最多心跳數(tickTime的數量),用它來限定集羣中的 Zookeeper 服務器連接到 Leader 的時限。

  3. syncLimit =5:LF 同步通信時限

    集羣中 Leader 與 Follower 之間的最大響應時間單位,假如響應超過 syncLimit * tickTime,Leader 認爲 Follwer 死掉,從服務器列表中刪除 Follwer。

  4. dataDir:數據文件目錄+數據持久化路徑

    主要用於保存 Zookeeper 中的數據。

  5. clientPort =2181:客戶端連接端口

    監聽客戶端連接的端口。

  6. server.A=B:C:D

    A 是一個數字,表示這個是第幾號服務器;集羣模式下配置一個文件 myid,這個文件在 dataDir 目錄下,這個文件裏面有一個數據就是 A 的值,Zookeeper 啓動時讀取此文件,拿到裏面的數據與 zoo.cfg 裏面的配置信息比較從而判斷到底是哪個server。
    B 是這個服務器的 ip 地址;
    C 是這個服務器與集羣中的 Leader 服務器交換信息的端口;
    D 是萬一集羣中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader,而這個端口就是用來執行選舉時服務器相互通信的端口。

3. Zookeeper 內部原理

3.1 選舉機制

  1. 半數機制

    集羣中半數以上機器存活,集羣可用。所以 Zookeeper 適合安裝奇數臺服務器。

  2. Zookeeper 雖然在配置文件中並沒有指定 Master 和 Slave。但是,Zookeeper 工作時,是有一個節點爲 Leader,其他則爲 Follower,Leader 是通過內部的選舉機制臨時產生的。

  3. 選舉過程例子

    假設有五臺服務器組成的 Zookeeper 集羣,它們的 id 從1-5,同時它們都是最新啓動的,也就是沒有歷史數據,在存放數據量這一點上,都是一樣的。假設這些服務器依序啓動。
    在這裏插入圖片描述
    ① 服務器 1 啓動,此時只有它一臺服務器啓動了,它發出去的報文沒有任何響應,所以它的選舉狀態一直是 LOOKING 狀態。

    ② 服務器 2 啓動,它與最開始啓動的服務器 1 進行通信,互相交換自己的選舉結果,由於兩者都沒有歷史數據,所以 id 值較大的服務器 2 勝出,但是由於沒有達到超過半數以上的服務器都同意選舉它(這個例子中的半數以上是 3),所以服務器 1、2 還是繼續保持 LOOKING 狀態。

    ③ 服務器 3 啓動,根據前面的理論分析,服務器 3 成爲服務器 1、2、3 中的老大,而與上面不同的是,此時有三臺服務器選舉了它,所以它成爲了這次選舉的 Leader。

    ④ 服務器 4 啓動,根據前面的分析,理論上服務器4應該是服務器 1、2、3、4 中最大的,但是由於前面已經有半數以上的服務器選舉了服務器 3,所以它只能接收當小弟的命了。

    ⑤ 服務器 5 啓動,同 4 一樣當小弟。

3.2 節點類型

  1. 持久(Persistent)

    客戶端和服務器端斷開連接後,創建的節點不刪除

  2. 短暫(Ephemeral)

    客戶端和服務器端斷開連接後,創建的節點自己刪除

  3. 節點類型
    在這裏插入圖片描述
    持久化目錄節點

      客戶端與 Zookeeper 斷開連接後,該節點依舊存在。

    持久化順序編號目錄節點

      客戶端與 Zookeeper 斷開連接後,該節點依舊存在,只是 Zookeeper 給該節點名稱進行順序編號

    臨時目錄節點

      客戶端與 Zookeeper 斷開連接後,該節點被刪除

    臨時順序編號目錄節點

      客戶端與 Zookeeper 斷開連接後,該節點被刪除,只是 Zookeeper 給該節點名稱進行順序編號。

    說明: 創建 znode 時設置順序標識,znode 名稱後會附加一個值,順序號是一個單調遞增的計數器,由父節點維護。
    注意: 在分佈式系統中,順序號可以被用於爲所有的事件進行全局排序,這樣客戶端可以通過順序號推斷事件的順序。

3.3 Stat 結構體

  在這裏插入圖片描述

  1. czxid: 創建節點的事務 zxid

    每次修改 ZooKeeper 狀態都會收到一個 zxid 形式的時間戳,也就是 ZooKeepe r事務 ID。
    事務 ID 是 ZooKeeper 中所有修改總的次序。每個修改都有唯一的 zxid,若 zxid1 小於 zxid2,那麼 zxid1 在 zxid2 之前發生。

  2. ctime: znode 被創建的毫秒數(從 1970 年開始)

  3. mzxid: znode 最後更新的事務 zxid

  4. mtime: znode 最後修改的毫秒數(從 1970 年開始)

  5. pZxid: znode 最後更新的子節點 zxid

  6. cversion : znode 子節點變化號,znode 子節點修改次數

  7. dataversion: znode 數據變化號

  8. aclVersion: znode 訪問控制列表的變化號

  9. ephemeralOwner: 如果是臨時節點,這個是 znode 擁有者的 session id。如果不是臨時節點則是 0。

  10. dataLength: znode 的數據長度

  11. numChildren: znode 子節點數量

3.4 監聽器原理

在這裏插入圖片描述

  1. 監聽原理詳解:

    ① 首先要有一個 main() 線程
    ② 在 main 線程中創建 Zokeeper 客戶端,這時就會創建兩個線程,一個負責網絡連接通信(connet),一個負責監聽(listener) 。
    ③ 通過 connect 線程將註冊的監聽事件發送給 Zookeeper。
    ④ 在 Zookeeper 的註冊監聽器列表中將註冊的監聽事件添加到列表中。
    ⑤ Zookeeper 監聽到有數據或路徑變化,就會將這個消息發送給 listener 線程。
    ⑥ listener 線程內部調用了 process() 方法。

  2. 常見的監聽

    ① 監聽節點數據的變化

get -w path

  ② 監聽子節點增減的變化

ls -w path

3.5 寫數據流程

在這裏插入圖片描述

4. Zookeeper 實戰

4.1 客戶端命令行操作

  1. 啓動客戶端
zkCli.sh
  1. 顯示所有操作命令
help
  1. 查看當前 znode 中所包含的內容
ls /
  1. 查看當前節點詳細數據
ls -s /

在這裏插入圖片描述

  1. 分別創建 2 個普通節點
create /animals "dog"
 create /animals/small "ant"
  1. 獲得節點的值
get /animals
get /animals/small

在這裏插入圖片描述

  1. 創建短暫節點
 create -e /animals/big "elephant"
  1. 創建帶序號的節點
create -s /animals/middle "hourse"

在這裏插入圖片描述

  1. 修改節點數據值
set /animals/small "bug"
  1. 節點的值變化監聽

    ① 在 slave1 主機上註冊監聽 /animals 節點數據變化

get -w /animals

在這裏插入圖片描述
    ② 在 slave2 主機上修改 /animals 節點的數據

set /animals "cat"

    ③ 觀察 slave1 主機收到子節點變化的監聽

在這裏插入圖片描述

  1. 節點的子節點變化監聽(路徑變化)

    ① 在 slave1 主機上註冊監聽 /animals 節點的子節點變化

ls -w /animals

在這裏插入圖片描述
    ② 在 slave2 主機 /animals 節點上創建子節點

create /animals/mini "fly"

    ③ 觀察 slave1 主機收到子節點變化的監聽

在這裏插入圖片描述

  1. 刪除節點
delete /animals/big
  1. 遞歸刪除節點
deleteall /animals/mini
  1. 查看節點狀態
stat /animals

在這裏插入圖片描述

4.2 API 操作

4.3.1 IDEA 環境搭建

  1. 創建一個 Maven 工程
  2. 在 pom 文件中添加依賴
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.8.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.5.6</version>
    </dependency>
</dependencies>
  1. 在項目的 src/main/resources 目錄下,新建一個文件,命名爲 “log4j.properties”,在文件中填入:
log4j.rootLogger=INFO, stdout  
log4j.appender.stdout=org.apache.log4j.ConsoleAppender  
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n  
log4j.appender.logfile=org.apache.log4j.FileAppender  
log4j.appender.logfile.File=target/spring.log  
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout  
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n  

4.3.2 創建 ZooKeeper 客戶端

package zookeeper;

import org.apache.zookeeper.*;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;

public class TestZookeeper {

    private String connectString = "master:2181,slave1:2181,slave2:2181";
    private int sessionTimeout = 2000;
    private ZooKeeper zkClient;


    @Test
    public void init() throws IOException {
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
            }
        });
    }

4.3.3 創建子節點

先將上面的 init() 方法前面的註解 @Test 改爲 @Before

// 創建子節點
@Test
public void createNode() throws Exception {
    // 參數1:要創建的節點的路徑; 參數2:節點數據 ; 參數3:節點權限 ;參數4:節點的類型
    String path = zkClient.create("/demo", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    System.out.println(path);
}

4.3.4 獲取子節點並監聽節點變化

// 獲取子節點並監聽節點變化
@Test
public void getChildrenAndWatch() throws Exception {

    List<String> children = zkClient.getChildren("/", true);
    for (String child : children) {
        System.out.println(child);
    }
    // 延時阻塞
    Thread.sleep(Long.MAX_VALUE);
}
@Before
public void init() throws IOException {
    zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
        public void process(WatchedEvent watchedEvent) {
            List<String> children = null;
            try {
                children = zkClient.getChildren("/", true);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (String child : children) {
                System.out.println(child);
            }
        }
    });
}

4.3.5 判斷 Znode 是否存在

// 判斷znode是否存在
@Test
 public void exist() throws Exception {
     Stat stat = zkClient.exists("/animals", false);
     System.out.println(stat == null ? "not exist" : "exist");
 }

4.3 監聽服務器節點動態上下線案例

  1. 需求

    某分佈式系統中,主節點可以有多臺,可以動態上下線,任意一臺客戶端都能實時感知到主節點服務器的上下線。

  2. 需求分析
    在這裏插入圖片描述

  3. 代碼實現

    ① 先在集羣上創建 /servers 節點

 create /servers "servers"

    ② 服務器端向 Zookeeper 註冊代碼

package zookeeper;

import org.apache.zookeeper.*;

import java.io.IOException;

public class DistributeServer {

    private String connectString = "master:2181,slave1:2181,slave2:2181";
    private int sessionTimeout = 2000;
    private ZooKeeper zkClient;

    public static void main(String[] args) throws Exception {
        args = new String[]{"slave1"};
        DistributeServer server = new DistributeServer();
        // 1.連接zookeeper集羣
        server.getConnect();
        // 2.註冊節點
        server.register(args[0]);
        // 3.業務邏輯處理
        server.business();
    }

    private void getConnect() throws IOException {
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            public void process(WatchedEvent event) {

            }
        });
    }

    private void register(String hostname) throws KeeperException, InterruptedException {
        String path = zkClient.create("/servers/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println(hostname + " is online");
    }

    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }
}

    ③ 客戶端代碼

package zookeeper;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class DistributeClient {

    private String connectString = "master:2181,slave1:2181,slave2:2181";
    private int sessionTimeout = 2000;
    private ZooKeeper zkClient;


    public static void main(String[] args) throws Exception {
        DistributeClient client = new DistributeClient();
        // 1.連接zookeeper集羣
        client.getConnect();
        // 2.註冊監聽
        client.getChildren();
        // 3.業務邏輯處理
        client.business();
    }

    private void getConnect() throws IOException {
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            public void process(WatchedEvent event) {
                try {
                    getChildren();
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void getChildren() throws KeeperException, InterruptedException {
        List<String> children = zkClient.getChildren("/servers", true);
        // 存儲服務器節點主機名稱集合
        ArrayList<String> hosts = new ArrayList<String>();
        for (String child : children) {
            byte[] data = zkClient.getData("/servers/" + child, false, null);
            hosts.add(new String(data));
        }
        System.out.println(hosts);
    }

    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章