Zookeeper學習記錄及Java客戶端連接示例

1. Zookeeper

在這裏插入圖片描述

1.1 簡介

ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. All of these kinds of services are used in some form or another by distributed applications. Each time they are implemented there is a lot of work that goes into fixing the bugs and race conditions that are inevitable. Because of the difficulty of implementing these kinds of services, applications initially usually skimp on them, which make them brittle in the presence of change and difficult to manage. Even when done correctly, different implementations of these services lead to management complexity when the applications are deployed.

1.2 樹形目錄結構

在這裏插入圖片描述

ZooKeeper使用樹形結構管理數據。而且以“/”作爲樹形結構的根節點。樹形結構中的每一個節點都稱爲“znode”。文件系統中的目錄可以存放其他目錄和文件,znode中可以存放其他znode,也可以對應一個具體的值。znode和它對應的值之間是鍵值對的關係。

每一個znode上同時還有一套狀態信息,稱爲:stat。

1.3 異步通知機制

在這裏插入圖片描述

在分佈式項目中隨着業務功能越來越多,具體的功能模塊也會越來越多,一個大型的電商項目能達到幾十個模塊甚至更多。這麼多業務模塊的工程有可能需要共享一些信息,這些信息一旦發生變化,在各個相關模塊工程中手動逐一修改會非常麻煩,甚至可能發生遺漏,嚴重的可能導致系統崩潰,造成經濟損失。

使用ZooKeeper的通知機制後,各個模塊工程在特定znode上設置Watcher(觀察者)來監控當前節點上值的變化。一旦Watcher檢測到了數據變化就會立即通知模塊工程,從而自動實現“一處修改,處處生效”的效果。

1.4 leader-follower集羣

在這裏插入圖片描述

2. Zookeeper安裝

2.1 環境準備——JDK

·Zookeeper需要在JVM虛擬機上運行,所以一定要保證有JDK支持。

  1. 下載jdk-8u231-linux-x64.tar.gz到/opt目錄

  2. 解壓到/opt目錄下

    tar -zxvf /opt/jdk-8u231-linux-x64.tar.gz
    
  3. 配置環境變量

    # JAVA_HOME 目錄:/opt/jdk1.8.0_231
    vim /etc/profile
    
    # 最後加上
    JAVA_HOME=/opt/jdk1.8.0_231
    PATH=$JAVA_HOME/bin:$PATH
    export JAVA_HOME PATH
    # :wq 保存退出
    
    source /etc/profile
    
  4. 驗證

    echo $JAVA_HOME
    # /opt/jdk1.8.0_231
    echo $PATH
    # /opt/jdk1.8.0_231/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
    
    java -version
    # java version "1.8.0_231"
    # Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
    # Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)
    

2.2 下載Zookeeper

下載zookeeper-3.4.9.tar.gz到/opt目錄

2.3 解壓

tar -zxvf /opt/zookeeper-3.4.9.tar.gz

2.4 準備配置文件

將解壓完文件的配置文件名稱改爲zoo.cfg,這是Zookeeper的默認要求。

cp /opt/zookeeper-3.4.9/conf/zoo_sample.cfg /opt/zookeeper-3.4.9/conf/zoo.cfg

Zookeeper要求配置文件的文件名必須是:zoo.cfg

2.5 創建數據目錄

mkdir /opt/zookeeper-3.4.9/data

2.6 在zoo.cfg中配置數據目錄的位置

vim /opt/zookeeper-3.4.9/conf/zoo.cfg
dataDir=/opt/zookeeper-3.4.9/data

3. Zookeeper服務器端操作

3.1 zoo.cfg文件解讀

tickTime
通信心跳數,ZooKeeper服務器心跳時間,單位毫秒
ZooKeeper使用的基本時間,服務器之間或客戶端與服務器之間維持心跳的時間間隔,也就是每個tickTime時間就會發送一個心跳,時間單位爲毫秒。
用於心跳機制,並且設置最小的session超時時間爲兩倍心跳時間(session的最小超時時間是2*tickTime)。
initLimit
LF初始通信時限
集羣中的Follower跟隨者服務器(F)與Leader領導者服務器(L)之間初始連接時能容忍的最多心跳數(tickTime的數量)。
投票選舉新Leader的初始化時間,Follower在啓動過程中,會從Leader同步所有最新數據,然後確定自己能夠對外服務的起始狀態。
Leader允許Follower在initLimit時間內完成這個工作。
syncLimit
LF同步通信時限
集羣中Leader與Follower之間的最大響應時間單位,假如響應超過syncLimit * tickTime,Leader認爲Follwer死掉,從服務器列表中刪除Follwer。
在運行過程中,Leader負責與ZooKeeper集羣中所有機器進行通信,例如通過一些心跳檢測機制,來檢測機器的存活狀態。
如果L發出心跳包在syncLimit之後,還沒有從F那收到響應,那麼就認爲這個F已經不在線了。
dataDir
數據文件目錄+數據持久化路徑
保存內存數據庫快照信息的位置,如果沒有其他說明,更新的事務日誌也保存到數據庫。
clientPort
客戶端連接端口

3.2 常用命令

服務器與客戶端

啓動服務器:./zkServer.sh start

停止服務器:./zkServer.sh stop

啓動客戶端:./zkCli.sh

退出客戶端:[zk: localhost:2181(CONNECTED) 6] quit

ls

查看當前znode中所包含的內容

ls2

查看當前節點數據並能看到更新次數等數據

stat

查看節點狀態

create

create [-s] [-e] path data acl

普通創建:不帶有-s、-e參數

-s:含有序列

-e:臨時(重啓或者超時消失)

set

設置節點的具體值

set 節點 value值

get

獲得節點的值

get節點

delete

可以刪除指定znode,當該znode擁有子znode時,必須先刪除其所有子znode,否則操作將失敗。

rmr

rmr命令可用於代替delete命令,rmr是一個遞歸刪除命令,如果發生指定節點擁有子節點時,rmr命令會首先刪除子節點。

3.3 Zookeeper節點類型

  1. PERSISTENT-持久化目錄節點

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

  2. PERSISTENT_SEQUENTIAL-持久化順序編號目錄節點

    create -s

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

  3. EPHEMERAL-臨時目錄節點

    create -e

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

  4. EPHEMERAL_SEQUENTIAL-臨時順序編號目錄節點

    create -s -e

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

3.4 Zookeeper節點狀態

介紹

znode維護了一個stat結構,這個stat包含數據變化的版本號、訪問控制列表變化、還有時間戳。版本號和時間戳一起,可讓ZooKeeper驗證緩存和協調更新。每次znode的數據發生了變化,版本號就增加。

例如:無論何時客戶端檢索數據,它也一起檢索數據的版本號。並且當客戶端執行更新或刪除時,客戶端必須提供他正在改變的znode的版本號。如果它提供的版本號和真實的數據版本號不一致,更新將會失敗。

屬性

czxid:引起這個znode創建的zxid,創建節點的事務的zxid(ZooKeeper Transaction Id)

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

mzxid:znode最後更新的zxid

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

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

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

dataversion:znode數據變化號

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

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

dataLength:znode的數據長度

numChildren:znode子節點數量

3.5 四字命令

介紹

ZooKeeper支持某些特定的四字命令,他們大多是用來查詢ZooKeeper服務的當前狀態及相關信息的,使用時通過telnet或nc向ZooKeeper提交相應命令。

[root@right bin]# echo ruok | nc localhost 2181

imok[root@right bin]#

nc命令

nc命令需要安裝對應的程序纔可以使用。

yum install -y nc

常用四字命令

ruok:測試服務是否處於正確狀態。如果確實如此,那麼服務返回“imok ”,否則不做任何響應

stat:輸出關於性能和連接的客戶端的列表

conf:輸出相關服務配置的詳細信息

cons:列出所有連接到服務器的客戶端的完全的連接 /會話的詳細信息。包括“接受 / 發送”的包數量、會話id 、操作延遲、最後的操作執行等等信息

dump:列出未經處理的會話和臨時節點

envi:輸出關於服務環境的詳細信息(區別於conf命令)

reqs:列出未經處理的請求

wchs:列出服務器watch的詳細信息

wchc:通過session列出服務器watch的詳細信息,它的輸出是一個與watch相關的會話的列表

wchp:通過路徑列出服務器 watch的詳細信息。它輸出一個與 session相關的路徑

4. Java客戶端

示例代碼:github

4.1 依賴信息

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

4.2 使用Junit測試類方法

  • 創建Zookeeper對象:用於連接Zookeeper服務;
  • Zookeeper構造器參數:
    • connectString:192.168.252.128:2181
    • sessionTimeOut:5000
    • Watcher對象:監控對象,數據有變化時調用方法返回通知

修改節點數據:

public class ZkTest {
    // 連接Zookeeper服務的對象
    ZooKeeper zooKeeper;
    // 連接信息
    String connectString = "192.168.252.128:2181";
    // 連接超時時間 ms
    int sessionTimeOut = 5000;
	// Watcher對象用於檢測節點是否變更,變更時調用對象內的方法異步通知
    Watcher watcher = new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
        }
    };

    {
        try {
            // 在代碼塊中初始化Zookeeper對象
            zooKeeper = new ZooKeeper(connectString, sessionTimeOut, watcher);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testUpdateNodeData() throws KeeperException, InterruptedException {
        // 要操作的節點的路徑
        String path = "/animal/cat";
        // 獲取當前節點值
        byte[] resultByteArray = zooKeeper.getData(path, false, new Stat());
        // 將字節數組封裝爲字符串,輸出
        String result = new String(resultByteArray);
        System.out.println(result);

        // 獲取新值字符串對應的字節數組
        byte[] newValueByteArray = new String("mimi").getBytes();
        // 指定當前操作所基於的版本號,不確定可使用-1
        int version = -1;
        // 執行節點值的修改
        Stat stat = zooKeeper.setData(path, newValueByteArray, version);
        // 獲取最新的版本號
        int newVersion = stat.getVersion();
        System.out.println("newVersion=" + newVersion);

        //獲取最新的值,輸出
        resultByteArray = zooKeeper.getData(path,false,new Stat());
        System.out.println(new String(resultByteArray));
        zooKeeper.close();
    }
}

5. 異步通知機制

5.1 工作機制介紹

客戶端註冊監聽它關心的目錄節點,當目錄節點發生變化(數據改變、被刪除、子目錄節點增加刪除)時,zookeeper會通知客戶端。

ZooKeeper支持Watch(觀察)機制,客戶端可以在每個znode結點上設置一個Watcher(觀察者)。如果被觀察服務端的znode結點有變更,那麼Watcher就會被觸發,這個Watcher所屬的客戶端將接收到一個通知包被告知結點已經發生變化,這就是把相應的事件通知給設置過Watcher的Client端。

ZooKeeper裏的所有讀取操作:getData(),getChildren()和exists()都有設置Watch的選項。

總結成一句話:ZooKeeper的觀察機制是一種異步回調的觸發機制

當數據有了變化時zkServer向客戶端發送一個Watch通知,這是個一次性動作,觸發一次就失效了。

如果想繼續Watch的話,需要客戶端重新設置Watcher。因此如果你得到了一個Watch事件,並且在將來繼續得到節點變化通知,那麼就必須另外設置一個新的Watcher繼續觀察。

節點有不同的改動方式。可以認爲ZooKeeper維護兩個觀察列表:數據觀察和子節點觀察。getData()和exists()設置數據觀察。getChildren()設置子節點觀察。此外,還可以認爲不同的返回數據有不同的觀察。getData()和exists()返回節點的數據,而getChildren()返回子節點列表。所以,setData()將爲znode觸發數據觀察。成功的create()將爲新創建的節點觸發數據觀察,爲其父節點觸發子節點觀察。成功的delete()將會爲被刪除的節點觸發數據觀察以及子節點觀察(因爲節點不能再有子節點了),爲其父節點觸發子節點觀察。如果一個節點設置存在觀察時尚未創建,並且在斷開連接後執行節點創建以及刪除操作,那麼這個節點上設置的觀察事件客戶端接收不到,事件會丟失。

5.2 一次性通知

下面的代碼測試繼續在上面使用Java客戶端連接Zookeeper的測試類中添加,共用測試類中聲明的Zookeeper對象,新增測試方法測試一次性通知。

Watcher調用一次方法後會釋放資源,所以只能進行一次性通知。

注意:異步通知機制要依賴於log4j的日誌信息查看具體內容,這裏需要導入log4j的配置文件。

@Test
public void testNoticeOnce() throws KeeperException, InterruptedException {
    // 要操作的節點路徑
    String path = "/animal/cat";
    Watcher watcher = new Watcher() {
        // 當前Watcher檢測到節點值的變化,會調用process方法(異步通知)
        @Override
        public void process(WatchedEvent watchedEvent) {
            System.err.println("接收到了通知,值發生了修改!");
        }
    };
    // 修改前的值
    byte[] oldValue = zooKeeper.getData(path, watcher, new Stat());
    System.out.println("oldValue=" + oldValue);

    // 持續運行程序,等待Zookeeper修改值進行異步通知
    while (true){
        Thread.sleep(5000);
        System.err.println("當前方法要執行的業務邏輯");
    }
}

5.3 持續通知

下面的代碼測試繼續在上面使用Java客戶端連接Zookeeper的測試類中添加,共用測試類中聲明的Zookeeper對象,新增測試方法測試持續通知。

@Test
public void testNoticeForever() throws KeeperException, InterruptedException {
    // 要操作的節點路徑
    String path = "/animal/cat";
    getDataWithNotice(zooKeeper, path);

    // 持續運行程序,等待Zookeeper修改值進行異步通知
    while (true) {
        Thread.sleep(5000);
        System.err.println("當前方法要執行的業務邏輯");
    }
}

public void getDataWithNotice(ZooKeeper zooKeeper, String path) throws KeeperException, InterruptedException {
    byte[] resultByteArray = zooKeeper.getData(path, new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
            // 以類似遞歸的方式調用getDataWithNotice()方法實現持續監控
            /**
                 * 這裏不是真正的遞歸,這個process()方法是異步執行;
                 * 當getDataWithNotice()執行時,在創建完Watcher對象之後,繼續執行到結束並釋放資源
                 * 此時Watcher對象還在內存中,當接收到了修改時,再異步調用process()方法;
                 * 然後重新調用getDataWithNotice()方法,創建Watcher對象,實現持續監控。
                 * */
            try {
                System.out.println("*接收到了修改*");
                getDataWithNotice(zooKeeper, path);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, new Stat());

    String result = new String(resultByteArray);
    System.out.println("當前節點值=" + result);
}

6. 集羣

6.1 數據通信機制

同一臺服務器上:IP地址一樣,端口號必須得不一樣

不同的服務器上:IP地址不一樣,端口號可以用同一個

注意:啓動完成後,會自動選舉出leader/follower,leader和follower均可讀可寫,並進行數據的同步。

6.2 搭建步驟

第1步:創建集羣所在目錄

mkdir /opt/cluster-zk

第2步:重新解壓tar包到集羣目錄

tar -zxvf /opt/zookeeper-3.4.9.tar.gz -C /opt/cluster-zk/

第3步:複製新的解壓目錄

cp -r /opt/cluster-zk/zookeeper-3.4.9/ /opt/cluster-zk/zkone

第4步:配置zkone

  1. 創建zoo.cfg配置文件

    cp /opt/cluster-zk/zkone/conf/zoo_sample.cfg /opt/cluster-zk/zkone/conf/zoo.cfg
    
  2. 創建數據目錄

    mkdir /opt/cluster-zk/zkone/data
    
  3. 在數據目錄中創建編號文件

    vim /opt/cluster-zk/zkone/data/myid
    
  4. 編輯編號文件,內容就是當前服務器實例的編號

    #myid
    1
    
  5. 配置zoo.cfg

    vim /opt/cluster-zk/zkone/conf/zoo.cfg
    
    # zoo.cfg
    dataDir=/opt/cluster-zk/zkone/data
    clientPort=1000
    # 文件末尾追加
    server.1=127.0.0.1:1001:1002
    server.2=127.0.0.1:2001:2002
    server.3=127.0.0.1:3001:3002
    #格式解釋:server.服務器實例編號=IP地址:數據通信端口號:選舉端口號
    

第5步:配置zktwo

  1. 把zktwo複製出來

    cp -r /opt/cluster-zk/zkone /opt/cluster-zk/zktwo
    
  2. 修改myid文件中編號值

    vim /opt/cluster-zk/zktwo/data/myid
    
    #myid
    2
    
  3. 修改zoo.cfg

    vim /opt/cluster-zk/zktwo/conf/zoo.cfg
    
    #zoo.cfg
    dataDir=/opt/cluster-zk/zktwo/data
    clientPort=2000
    

第6步:配置zkthree

  1. 把zkthree複製出來

    cp -r /opt/cluster-zk/zktwo /opt/cluster-zk/zkthree
    
  2. 修改myid文件中編號值

    vim /opt/cluster-zk/zkthree/data/myid
    
    #myid
    3
    
  3. 修改zoo.cfg

    vim /opt/cluster-zk/zkthree/conf/zoo.cfg
    
    #zoo.cfg
    dataDir=/opt/cluster-zk/zkthree/data
    clientPort=3000
    

6.3 創建操作服務器的可執行腳本

6.3.1 創建啓動服務器命令腳本文件

# 創建並編輯文件
vim /opt/cluster-zk/start.sh

#start.sh
/opt/cluster-zk/zkone/bin/zkServer.sh start
/opt/cluster-zk/zktwo/bin/zkServer.sh start
/opt/cluster-zk/zkthree/bin/zkServer.sh start

6.3.2 創建停止服務器命令腳本文件

# 創建並編輯文件
vim /opt/cluster-zk/stop.sh

#stop.sh
/opt/cluster-zk/zkone/bin/zkServer.sh stop
/opt/cluster-zk/zktwo/bin/zkServer.sh stop
/opt/cluster-zk/zkthree/bin/zkServer.sh stop

6.3.3 創建查看服務器狀態命令腳本文件

# 創建並編輯文件
vim /opt/cluster-zk/status.sh

#status.sh
/opt/cluster-zk/zkone/bin/zkServer.sh status
/opt/cluster-zk/zktwo/bin/zkServer.sh status
/opt/cluster-zk/zkthree/bin/zkServer.sh status

6.3.4 給腳本文件設置可執行權限

chmod 755 /opt/cluster-zk/start.sh 
chmod 755 /opt/cluster-zk/stop.sh 
chmod 755 /opt/cluster-zk/status.sh 

6.3.5 執行腳本命令

/opt/cluster-zk/start.sh 
/opt/cluster-zk/stop.sh 
/opt/cluster-zk/status.sh 
# 啓動命令執行完畢後查看狀態,會自動選舉出leader/follower

6.3.6 客戶端登錄

/opt/zookeeper-3.4.9/bin/zkCli.sh -server 127.0.0.1:1000
/opt/zookeeper-3.4.9/bin/zkCli.sh -server 127.0.0.1:2000
/opt/zookeeper-3.4.9/bin/zkCli.sh -server 127.0.0.1:3000

6.3.7 客戶端的測試

此時zkone(127.0.0.1:1000)爲leader,zktwo和zkthree爲follower。

  • 測試1:follower-zktwo中寫入數據

    follower可讀可寫,follower寫的數據,其他follower(zkthree)和leader(zkone)都可以進行數據同步。

  • 測試2:follower-zktwo宕機

    /opt/cluster-zk/zktwo/bin/zkServer.sh stop

    對其他follower(zkthree)和leader(zkone)無影響,數據同步正常。

  • 測試3:follower-zktwo重新啓動

    /opt/cluster-zk/zktwo/bin/zkServer.sh start

    未自動連接時執行connect 127.0.0.1:2000

    啓動後,follower-zktwo自動進行數據同步,並自動設置自己爲follower。

  • 測試4:leader-zkone宕機

    /opt/cluster-zk/zkone/bin/zkServer.sh stop

    此時follower(zktwo,zkthree)連接正常,數據同步正常,並自動選舉出新的leader。

  • 測試5:zk-one(曾經的leader)重新啓動

    /opt/cluster-zk/zkone/bin/zkServer.sh start

    未自動連接時執行connect 127.0.0.1:1000

    啓動後數據同步正常,並自動設置自己爲follower。

6.4 集羣中服務器數量

結論:一般來說,集羣中服務器數量最好設置爲單數。

原則:集羣中有超過一半的服務器正常工程,則整個集羣判斷爲正常工作。對外提供服務時大致滿足預期。

推導:

共2實例:宕機1實例,剩下1=2/2,沒有超過一半。死亡容忍度爲0。

共3實例:宕機1實例,剩下2>3/2,超過一半。死亡容忍度爲1。

共4實例:宕機1實例,剩下3>4/2,超過一半。死亡容忍度爲1。

共5實例:宕機2實例,剩下3>5/2,超過一半。死亡容忍度爲2。

-zktwo重新啓動

/opt/cluster-zk/zktwo/bin/zkServer.sh start

未自動連接時執行connect 127.0.0.1:2000

啓動後,follower-zktwo自動進行數據同步,並自動設置自己爲follower。

  • 測試4:leader-zkone宕機

    /opt/cluster-zk/zkone/bin/zkServer.sh stop

    此時follower(zktwo,zkthree)連接正常,數據同步正常,並自動選舉出新的leader。

  • 測試5:zk-one(曾經的leader)重新啓動

    /opt/cluster-zk/zkone/bin/zkServer.sh start

    未自動連接時執行connect 127.0.0.1:1000

    啓動後數據同步正常,並自動設置自己爲follower。

6.4 集羣中服務器數量

結論:一般來說,集羣中服務器數量最好設置爲單數。

原則:集羣中有超過一半的服務器正常工程,則整個集羣判斷爲正常工作。對外提供服務時大致滿足預期。

推導:

共2實例:宕機1實例,剩下1=2/2,沒有超過一半。死亡容忍度爲0。

共3實例:宕機1實例,剩下2>3/2,超過一半。死亡容忍度爲1。

共4實例:宕機1實例,剩下3>4/2,超過一半。死亡容忍度爲1。

共5實例:宕機2實例,剩下3>5/2,超過一半。死亡容忍度爲2。

共6實例:宕機2實例,剩下4>6/2,超過一半。死亡容忍度爲2。

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