ZooKeeper初探

    -

參考博客

Zookeeper簡單介紹
Zookeeper安裝部署及hello world
zookeeper
zookeeper原理(轉)
netstat -ano 查看機器端口占用情況
Zookeeper的安裝和配置(單機模式)
zookeeper節點Watch機制實例展示
Zookeeper可以幹什麼
Zookeeper工作原理(詳細)
ZooKeeper監聽機制
淺析Zookeeper的一致性原理
ZooKeeper 節點類型
ZooKeeper系列3:ZooKeeper命令、命令行工具及簡單操作

整體思路

什麼是zookeeper?
zookeeper環境搭建和安裝啓動?
zookeeper常用命令?
zookeeper原理分析?
zookeeper有什麼作用?

    -

什麼是zookeeper

ZooKeeper是一個分佈式的,開放源碼的分佈式應用程序協調服務,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要組件。它是一個爲分佈式應用提供一致性服務的軟件,提供的功能包括:配置維護、域名服務、分佈式同步、組服務

    -

瞭解zookeeper,需要知道哪些知識

分佈式協調技術
分佈式鎖
數據模型Znode
Watcher機制
共享鎖
Zab協議
Zxid
單點故障
observer
節點

分佈式協調技術: 主要用來解決分佈式環境當中多個進程之間的同步控制,讓他們有序的去訪問某種臨界資源,防止造成”髒數據”的後果
這裏寫圖片描述
分佈式鎖:假設在第一臺機器上掛載了一個資源,然後這三個物理分佈的進程都要競爭這個資源,但我們又不希望他們同時進行訪問,這時候我們就需要一個協調器,來讓他們有序的來訪問這個資源。這個協調器就是我們經常提到的那個鎖,比如說”進程-1”在使用該資源的時候,會先去獲得鎖,”進程1”獲得鎖以後會對該資源保持獨佔,這樣其他進程就無法訪問該資源,”進程1”用完該資源以後就將鎖釋放掉,讓其他進程來獲得鎖,那麼通過這個鎖機制,我們就能保證了分佈式系統中多個進程能夠有序的訪問該臨界資源。那麼我們把這個分佈式環境下的這個鎖叫作分佈式鎖。

zookeeper性能特點:可靠性方面來說,它並不會因爲一個節點的錯誤而崩潰。除此之外,它嚴格的序列訪問控制意味着複雜的控制原語可以應用在客戶端上。ZooKeeper在一致性、可用性、容錯性的保證。它雖然不能避免網絡故障,但它能夠保證每時每刻只有一個Master。

Zonde:Zonde通過路徑引用,如同Unix中的文件路徑。路徑必須是絕對的,因此他們必須由斜槓字符來開頭。除此以外,他們必須是唯一的,也就是說每一個路徑只有一個表示,因此這些路徑不能改變。在ZooKeeper中,路徑由Unicode字符串組成,並且有一些限制。字符串”/zookeeper”用以保存管理信息,比如關鍵配額信息。每個Znode由3部分組成: ① stat:此爲狀態信息, 描述該Znode的版本, 權限等信息 ② data:與該Znode關聯的數據 ③ children:該Znode下的子節點。**數據節點(dataNode):zk數據模型中的最小數據單元,數據模型是一棵樹,由斜槓(/)分割的路徑名唯一標識,數據節點可以存儲數據內容及一系列屬性信息,同時還可以掛載子節點,構成一個層次化的命名空間。**ZooKeeper 實現的任何功能都離不開ZooKeeper的數據結構,任何功能的實現都是利用”Znode結構特性+節點關聯的數據”來實現的.
這裏寫圖片描述

Zonde結構特點:
① 每個子目錄項如 NameService 都被稱作爲 znode,這個 znode 是被它所在的路徑唯一標識,如 Server1 這個 znode 的標識爲 /NameService/Server1;
② znode 可以有子節點目錄,並且每個 znode 可以存儲數據,注意 EPHEMERAL 類型的目錄節點不能有子節點目錄;
③ znode 是有版本的,每個 znode 中存儲的數據可以有多個版本,也就是一個訪問路徑中可以存儲多份數據;
④ znode 可以是臨時節點,一旦創建這個 znode 的客戶端與服務器失去聯繫,這個 znode 也將自動刪除,Zookeeper 的客戶端和服務器通信採用長連接方式,每個客戶端和服務器通過心跳來保持連接,這個連接狀態稱爲 session,如果 znode 是臨時節點,這個 session 失效,znode 也就刪除了;
⑤ znode 的目錄名可以自動編號,如 App1 已經存在,再創建的話,將會自動命名爲 App2;
⑥ znode 可以被監控,包括這個目錄節點中存儲的數據的修改,子節點目錄的變化等,一旦變化可以通知設置監控的客戶端,這個是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基於這個特性實現的。

雙Master故障:在回覆的時候網絡發生故障,這樣我們的備用節點同樣收不到回覆,就會認爲主節點掛了,然後備用節點將他的Master實例啓動起來,這樣我們的分佈式系統當中就有了兩個主節點也就是—雙Master, 出現Master以後我們的從節點就會將它所做的事一部分彙報給了主節點,一部分彙報給了從節點,實際上出現2個Master彙報服務時候根本不知道交給誰處理,這樣服務就全亂了。爲了防止出現這種情況,我們引入了 ZooKeeper,它雖然不能避免網絡故障,但它能夠保證每時每刻只有一個Master。

單點故障:通常分佈式系統採用主從模式,就是一個主控機連接多個處理節點。主節點負責分發任務,從節點負責處理任務,當我們的主節點發生故障時,那麼整個系統就都癱瘓了,那麼我們把這種故障叫作單點故障。

Watcher機制:一個Watch事件是一個一次性的觸發器,當被設置了Watch的數據發生了改變的時候,則服務器將這個改變發送給設置了Watch的客戶端,以便通知它們。客戶端可以在節點上設置watch,我們稱之爲監視器。當節點狀態發生改變時(Znode的增、刪、改)將會觸發watch所對應的操作。當watch被觸發時,ZooKeeper將會向客戶端發送且僅發送一條通知,因爲watch只能被觸發一次,這樣可以減少網絡流量。

Zxid(時間):ZooKeeper節點狀態改變的每一個操作都將使節點接收到一個Zxid格式的時間戳,並且這個時間戳全局有序。也就是說,也就是說,每個對 節點的改變都將產生一個唯一的Zxid。如果Zxid1的值小於Zxid2的值,那麼Zxid1所對應的事件發生在Zxid2所對應的事件之前。實際 上,ZooKeeper的每個節點維護者三個Zxid值,爲別爲:cZxid、mZxid、pZxid。

數據快照:數據快照是zk數據存儲中另一個非常核心的運行機制。數據快照用來記錄zk服務器上某一時刻的全量內存數據內容,並將其寫入到指定的磁盤文件中,可通過dataDir配置文件目錄。可配置參數snapCount,設置兩次快照之間的事務操作個數,zk節點記錄完事務日誌時,會統計判斷是否需要做數據快照(距離上次快照,事務操作次數等於snapCount/2~snapCount 中的某個值時,會觸發快照生成操作,隨機值是爲了避免所有節點同時生成快照,導致集羣影響緩慢)。

選舉:在引入了Zookeeper以後我們啓動了兩個主節點,”主節點-A”和”主節點-B”他們啓動以後,都向ZooKeeper去註冊一個節點。我們 假設”主節點-A”鎖註冊地節點是”master-00001”,”主節點-B”註冊的節點是”master-00002”,註冊完以後進行選舉,編號最 小的節點將在選舉中獲勝獲得鎖成爲主節點,也就是我們的”主節點-A”將會獲得鎖成爲主節點,然後”主節點-B”將被阻塞成爲一個備用節點。那麼,通過這 種方式就完成了對兩個Master進程的調度。
這裏寫圖片描述

節點類型:
持久節點(PERSISTENT)
所謂持久節點,是指在節點創建後,就一直存在,直到有刪除操作來主動清除這個節點——不會因爲創建該節點的客戶端會話失效而消失。
持久順序節點(PERSISTENT_SEQUENTIAL)
這類節點的基本特性和上面的節點類型是一致的。額外的特性是,在ZK中,每個父節點會爲他的第一級子節點維護一份時序,會記錄每個子節點創建的先後順序。基於這個特性,在創建子節點的時候,可以設置這個屬性,那麼在創建節點過程中,ZK會自動爲給定節點名加上一個數字後綴,作爲新的節點名。這個數字後綴的範圍是整型的最大值。
臨時節點(EPHEMERAL)
和持久節點不同的是,臨時節點的生命週期和客戶端會話綁定。也就是說,如果客戶端會話失效,那麼這個節點就會自動被清除掉。注意,這裏提到的是會話失效,而非連接斷開。另外,在臨時節點下面不能創建子節點。
臨時順序節點(EPHEMERAL_SEQUENTIAL)
可以用來實現分佈式鎖
客戶端調用create()方法創建名爲“locknode/guid-lock-”的節點,需要注意的是,這裏節點的創建類型需要設置爲EPHEMERAL_SEQUENTIAL。
客戶端調用getChildren(“locknode”)方法來獲取所有已經創建的子節點,注意,這裏不註冊任何Watcher。
客戶端獲取到所有子節點path之後,如果發現自己在步驟1中創建的節點序號最小,那麼就認爲這個客戶端獲得了鎖。
如果在步驟3中發現自己並非所有子節點中最小的,說明自己還沒有獲取到鎖。此時客戶端需要找到比自己小的那個節點,然後對其調用exist()方法,同時註冊事件監聽。
之後當這個被關注的節點被移除了,客戶端會收到相應的通知。這個時候客戶端需要再次調用getChildren(“locknode”)方法來獲取所有已經創建的子節點,確保自己確實是最小的節點了,然後進入步驟3。

安裝配置

Zookeeper安裝方式有三種,單機模式和集羣模式以及僞集羣模式。

■ 單機模式:Zookeeper只運行在一臺服務器上,適合測試環境;
■ 僞集羣模式:就是在一臺物理機上運行多個Zookeeper 實例;
■ 集羣模式:Zookeeper運行於一個集羣上,適合生產環境,這個計算機集羣被稱爲一個“集合體”(ensemble)

單機模式(Windows):

  • 下載地址
  • 備份zoo_sample.cfg文件,修改爲zoo.cfg,修改其中的參數
    dataDir=C:\Users\Administrator\Downloads\zookeeper-3.4.8\tmp\zookeeper\data(自己的路徑)
    dataLogDir=\Users\Administrator\Downloads\zookeeper-3.4.8\tmp\zookeeper\data\dataLog (自己的路徑)
  • 啓動關閉:zkServer.cmd zkServer
  • 啓動完成後使用zkCli.cmd-server 127.0.0.1:2181查看運行情況這裏寫圖片描述出現welcome to Zookeeper證明啓動成功,也可以直接使用netstat -ano|findstr 2181根據端口來查看

僞集羣模式(Windows):

  • 複製zoo.cfg爲三份zoo1.cfg zoo2.cfg zoo3.cfg 複製zkServer.cmd 爲三份zkServer-1.cmd zkServer-2.cmd zkServer-3.cmd
  • 修改zoo1.cfg 內容
    dataDir=/tmp/zookeeper/1
    server.1=localhost:2887:3887
    server.1=localhost:2888:3888
    server.1=localhost:2889:3889這裏寫圖片描述
    依次類推
  • 修改zkServer-1.cmd內容
    加入set ZOOCFG=..\conf\zoo1.cfg這裏寫圖片描述
    依次類推
  • 創建myid文件,不要後綴
    /tmp/zookeeper/1,
    /tmp/zookeeper/2,
    /tmp/zookeeper/3
    分別啓動這3個cmd文件,在控制檯輸入jps會看到啓動的三個Java進程,說明啓動成功
    這裏寫圖片描述

代碼連接

package com.test.zookeeper;

import java.io.IOException;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class Test {
      public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
            ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 300000, new DemoWatcher());//連接zk server
            String node = "/app1";
            Stat stat = zk.exists(node, false);//檢測/app1是否存在
            if (stat == null) {
                //創建節點
                String createResult = zk.create(node, "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                System.out.println(createResult);
            }
            //獲取節點的值
            byte[] b = zk.getData(node, false, stat);
            System.out.println(new String(b));
            zk.close();
        }

        static class DemoWatcher implements Watcher {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("----------->");
                System.out.println("path:" + event.getPath());
                System.out.println("type:" + event.getType());
                System.out.println("stat:" + event.getState());
                System.out.println("<-----------");
            }
        }
}

我們使用zkCli.cmd -server localhost:2181連接獲取app1下的節點數據,產生數據test
這裏寫圖片描述

常用命令
查看節點下的內容:
ls /;
創建一個新的 znode:
create /zk myData;
get 命令來確認第二步中所創建的 znode 是否包含我們所創建的字符串:
get /zk;
set 命令來對 zk 所關聯的字符串進行設置:
set /zk shenlan211314;
將剛纔創建的 znode 刪除:
delete /zk

集羣下的測試類

package com.test.zookeeper;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.LoggerFactory;

public class ZkWatchAPI implements Watcher {

    public static final org.slf4j.Logger LOG =  LoggerFactory.getLogger(ZkWatchAPI.class);

    private static final int SESSION_TIMEOUT = 10000;

    private ZooKeeper zk = null;

    private CountDownLatch connectedSemaphore = new CountDownLatch( 1 );

    /**
     * 連接Zookeeper
     * @param connectString  Zookeeper服務地址
     */
    public void connectionZookeeper(String connectString){
        connectionZookeeper(connectString,SESSION_TIMEOUT);
    }

    /**
     * <p>連接Zookeeper</p>
     * <pre>
     *     [關於connectString服務器地址配置]
     *     格式: 192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181
     *     這個地址配置有多個ip:port之間逗號分隔,底層操作
     *     ConnectStringParser connectStringParser =  new ConnectStringParser(“192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181”);
     *     這個類主要就是解析傳入地址列表字符串,將其它保存在一個ArrayList中
     *     ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();
     *     接下去,這個地址列表會被進一步封裝成StaticHostProvider對象,並且在運行過程中,一直是這個對象來維護整個地址列表。
     *     ZK客戶端將所有Server保存在一個List中,然後隨機打亂(這個隨機過程是一次性的),並且形成一個環,具體使用的時候,從0號位開始一個一個使用。
     *     因此,Server地址能夠重複配置,這樣能夠彌補客戶端無法設置Server權重的缺陷,但是也會加大風險。
     *
     *     [客戶端和服務端會話說明]
     *     ZooKeeper中,客戶端和服務端建立連接後,會話隨之建立,生成一個全局唯一的會話ID(Session ID)。
     *     服務器和客戶端之間維持的是一個長連接,在SESSION_TIMEOUT時間內,服務器會確定客戶端是否正常連接(客戶端會定時向服務器發送heart_beat,服務器重置下次SESSION_TIMEOUT時間)。
     *     因此,在正常情況下,Session一直有效,並且ZK集羣所有機器上都保存這個Session信息。
     *     在出現網絡或其它問題情況下(例如客戶端所連接的那臺ZK機器掛了,或是其它原因的網絡閃斷),客戶端與當前連接的那臺服務器之間連接斷了,
     *     這個時候客戶端會主動在地址列表(實例化ZK對象的時候傳入構造方法的那個參數connectString)中選擇新的地址進行連接。
     *
     *     [會話時間]
     *     客戶端並不是可以隨意設置這個會話超時時間,在ZK服務器端對會話超時時間是有限制的,主要是minSessionTimeout和maxSessionTimeout這兩個參數設置的。
     *     如果客戶端設置的超時時間不在這個範圍,那麼會被強制設置爲最大或最小時間。 默認的Session超時時間是在2 * tickTime ~ 20 * tickTime
     * </pre>
     * @param connectString  Zookeeper服務地址
     * @param sessionTimeout Zookeeper連接超時時間
     */
    public void connectionZookeeper(String connectString, int sessionTimeout){
        this.releaseConnection();
        try {
            // ZK客戶端允許我們將ZK服務器的所有地址都配置在這裏
            zk = new ZooKeeper(connectString, sessionTimeout, this );
            // 使用CountDownLatch.await()的線程(當前線程)阻塞直到所有其它擁有CountDownLatch的線程執行完畢(countDown()結果爲0)
            connectedSemaphore.await();
        } catch ( InterruptedException e ) {
            LOG.error("連接創建失敗,發生 InterruptedException , e " + e.getMessage(), e);
        } catch ( IOException e ) {
            LOG.error( "連接創建失敗,發生 IOException , e " + e.getMessage(), e );
        }
    }

    /**
     * <p>創建zNode節點, String create(path<節點路徑>, data[]<節點內容>, List(ACL訪問控制列表), CreateMode<zNode創建類型>) </p><br/>
     * <pre>
     *     節點創建類型(CreateMode)
     *     1、PERSISTENT:持久化節點
     *     2、PERSISTENT_SEQUENTIAL:順序自動編號持久化節點,這種節點會根據當前已存在的節點數自動加 1
     *     3、EPHEMERAL:臨時節點客戶端,session超時這類節點就會被自動刪除
     *     4、EPHEMERAL_SEQUENTIAL:臨時自動編號節點
     * </pre>
     * @param path zNode節點路徑
     * @param data zNode數據內容
     * @return 創建成功返回true, 反之返回false.
     */
    public boolean createPath( String path, String data ) {
        try {
            String zkPath =  this.zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            LOG.info( "節點創建成功, Path: " + zkPath + ", content: " + data );
            return true;
        } catch ( KeeperException e ) {
            LOG.error( "節點創建失敗, 發生KeeperException! path: " + path + ", data:" + data
                    + ", errMsg:" + e.getMessage(), e );
        } catch ( InterruptedException e ) {
            LOG.error( "節點創建失敗, 發生 InterruptedException! path: " + path + ", data:" + data
                    + ", errMsg:" + e.getMessage(), e );
        }
        return false;
    }

    /**
     * <p>刪除一個zMode節點, void delete(path<節點路徑>, stat<數據版本號>)</p><br/>
     * <pre>
     *     說明
     *     1、版本號不一致,無法進行數據刪除操作.
     *     2、如果版本號與znode的版本號不一致,將無法刪除,是一種樂觀加鎖機制;如果將版本號設置爲-1,不會去檢測版本,直接刪除.
     * </pre>
     * @param path zNode節點路徑
     * @return 刪除成功返回true,反之返回false.
     */
    public boolean deletePath( String path ){
        try {
            this.zk.delete(path,-1);
            LOG.info( "節點刪除成功, Path: " + path);
            return true;
        } catch ( KeeperException e ) {
            LOG.error( "節點刪除失敗, 發生KeeperException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        } catch ( InterruptedException e ) {
            LOG.error( "節點刪除失敗, 發生 InterruptedException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        }
        return false;
    }

    /**
     * <p>更新指定節點數據內容, Stat setData(path<節點路徑>, data[]<節點內容>, stat<數據版本號>)</p>
     * <pre>
     *     設置某個znode上的數據時如果爲-1,跳過版本檢查
     * </pre>
     * @param path zNode節點路徑
     * @param data zNode數據內容
     * @return 更新成功返回true,返回返回false
     */
    public boolean writeData( String path, String data){
        try {
            Stat stat = this.zk.setData(path, data.getBytes(), -1);
            LOG.info( "更新數據成功, path:" + path + ", stat: " + stat );
            return true;
        } catch (KeeperException e) {
            LOG.error( "更新數據失敗, 發生KeeperException! path: " + path + ", data:" + data
                    + ", errMsg:" + e.getMessage(), e );
        } catch (InterruptedException e) {
            LOG.error( "更新數據失敗, 發生InterruptedException! path: " + path + ", data:" + data
                    + ", errMsg:" + e.getMessage(), e );
        }
        return false;
    }

    /**
     * <p>讀取指定節點數據內容,byte[] getData(path<節點路徑>, watcher<監視器>, stat<數據版本號>)</p>
     * @param path zNode節點路徑
     * @return 節點存儲的值,有值返回,無值返回null
     */
    public String readData( String path ){
        String data = null;
        try {
            data = new String( this.zk.getData( path, false, null ) );
            LOG.info( "讀取數據成功, path:" + path + ", content:" + data);
        } catch (KeeperException e) {
            LOG.error( "讀取數據失敗,發生KeeperException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        } catch (InterruptedException e) {
            LOG.error( "讀取數據失敗,發生InterruptedException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        }
        return  data;
    }

    /**
     * <p>獲取某個節點下的所有子節點,List getChildren(path<節點路徑>, watcher<監視器>)該方法有多個重載</p>
     * @param path zNode節點路徑
     * @return 子節點路徑集合 說明,這裏返回的值爲節點名
     * <pre>
     *     eg.
     *     /node
     *     /node/child1
     *     /node/child2
     *     getChild( "node" )戶的集合中的值爲["child1","child2"]
     * </pre>
     *
     *
     *
     * @throws KeeperException
     * @throws InterruptedException
     */
    public List<String> getChild( String path ){
        try{
            List<String> list=this.zk.getChildren( path, false );
            if(list.isEmpty()){
                LOG.info( "中沒有節點" + path );
            }
            return list;
        }catch (KeeperException e) {
            LOG.error( "讀取子節點數據失敗,發生KeeperException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        } catch (InterruptedException e) {
            LOG.error( "讀取子節點數據失敗,發生InterruptedException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        }
        return null;
    }

    /**
     * <p>判斷某個zNode節點是否存在, Stat exists(path<節點路徑>, watch<並設置是否監控這個目錄節點,這裏的 watcher 是在創建 ZooKeeper 實例時指定的 watcher>)</p>
     * @param path zNode節點路徑
     * @return 存在返回true,反之返回false
     */
    public boolean isExists( String path ){
        try {
            Stat stat = this.zk.exists( path, false );
            return null != stat;
        } catch (KeeperException e) {
            LOG.error( "讀取數據失敗,發生KeeperException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        } catch (InterruptedException e) {
            LOG.error( "讀取數據失敗,發生InterruptedException! path: " + path
                    + ", errMsg:" + e.getMessage(), e );
        }
        return false;
    }

    /**
     * Watcher Server,處理收到的變更
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        LOG.info("收到事件通知:" + watchedEvent.getState() );
        if ( Event.KeeperState.SyncConnected == watchedEvent.getState() ) {
            connectedSemaphore.countDown();
        }
    }

    /**
     * 關閉ZK連接
     */
    public void releaseConnection() {
        if ( null != zk ) {
            try {
                this.zk.close();
            } catch ( InterruptedException e ) {
                LOG.error("release connection error ," + e.getMessage() ,e);
            }
        }
    }

    public static void main(String [] args){

        // 定義父子類節點路徑
        String rootPath = "/nodeRoot";
        String child1Path = rootPath + "/nodeChildren1";
        String child2Path = rootPath + "/nodeChildren2";

        ZkWatchAPI zkWatchAPI = new ZkWatchAPI();

        // 連接zk服務器
//        zkWatchAPI.connectionZookeeper("192.168.43.110:2181"); 
        zkWatchAPI.connectionZookeeper("192.168.43.110:2181,192.168.43.110:2182,192.168.43.110:2183");

        // 創建節點數據
        if ( zkWatchAPI.createPath( rootPath, "<父>節點數據" ) ) {
            System.out.println( "節點[" + rootPath + "]數據內容[" + zkWatchAPI.readData( rootPath ) + "]" );
        }
        // 創建子節點, 讀取 + 刪除
        if ( zkWatchAPI.createPath( child1Path, "<父-子(1)>節點數據" ) ) {
            System.out.println( "節點[" + child1Path + "]數據內容[" + zkWatchAPI.readData( child1Path ) + "]" );
            zkWatchAPI.deletePath(child1Path);
            System.out.println( "節點[" + child1Path + "]刪除值後[" + zkWatchAPI.readData( child1Path ) + "]" );
        }

        // 創建子節點, 讀取 + 修改
        if ( zkWatchAPI.createPath( child2Path, "<父-子(2)>節點數據" ) ) {
            System.out.println( "節點[" + child2Path + "]數據內容[" + zkWatchAPI.readData( child2Path ) + "]" );
            zkWatchAPI.writeData( child2Path, "<父-子(2)>節點數據,更新後的數據" );
            System.out.println( "節點[" + child2Path+ "]數據內容更新後[" + zkWatchAPI.readData( child2Path ) + "]" );
        }

        // 獲取子節點
        List<String> childPaths = zkWatchAPI.getChild(rootPath);
        if(null != childPaths){
            System.out.println( "節點[" + rootPath + "]下的子節點數[" + childPaths.size() + "]" );
            for(String childPath : childPaths){
                System.out.println(" |--節點名[" +  childPath +  "]");
            }
        }

        // 判斷節點是否存在
        System.out.println( "檢測節點[" + rootPath + "]是否存在:" + zkWatchAPI.isExists(rootPath)  );
        System.out.println( "檢測節點[" + child1Path + "]是否存在:" + zkWatchAPI.isExists(child1Path)  );
        System.out.println( "檢測節點[" + child2Path + "]是否存在:" + zkWatchAPI.isExists(child2Path)  );


        zkWatchAPI.releaseConnection();
    }

}

zookeeper的具體作用

- 配置服務
在我們的應用中除了代碼外,還有一些就是各種配置。比如數據庫連接等。一般我們都是使用配置文件的方式,在代碼中引入這些配置文件。但是當我們只有一種配置,只有一臺服務器,並且不經常修改的時候,使用配置文件是一個很好的做法,但是如果我們配置非常多,有很多服務器都需要這個配置,而且還可能是動態的話使用配置文件就不是個好主意了。這個時候往往需要尋找一種集中管理配置的方法,我們在這個集中的地方修改了配置,所有對這個配置感興趣的都可以獲得變更。比如我們可以把配置放在數據庫裏,然後所有需要配置的服務都去這個數據庫讀取配置。但是,因爲很多服務的正常運行都非常依賴這個配置,所以需要這個集中提供配置服務的服務具備很高的可靠性。一般我們可以用一個集羣來提供這個配置服務,但是用集羣提升可靠性,那如何保證配置在集羣中的一致性呢? 這個時候就需要使用一種實現了一致性協議的服務了。Zookeeper就是這種服務,它使用Zab這種一致性協議來提供一致性。現在有很多開源項目使用Zookeeper來維護配置,比如在HBase中,客戶端就是連接一個Zookeeper,獲得必要的HBase集羣的配置信息,然後纔可以進一步操作。還有在開源的消息隊列Kafka中,也使用Zookeeper來維護broker的信息。在Alibaba開源的SOA框架Dubbo中也廣泛的使用Zookeeper管理一些配置來實現服務治理。
這裏寫圖片描述
Zookeeper很容易實現這種集中式的配置管理,比如將所需要的配置信息放到/Configuration 節點上,集羣中所有機器一啓動就會通過Client對/Configuration這個節點進行監控【zk.exist(“/Configuration″,true)】,並且實現Watcher回調方法process(),那麼在zookeeper上/Configuration節點下數據發生變化的時候,每個機器都會收到通知,Watcher回調方法將會被執行,那麼應用再取下數據即可【zk.getData(“/Configuration″,false,null)】。

- 統一命名服務(Name Service)
分佈式應用中,通常需要有一套完整的命名規則,既能夠產生唯一的名稱又便於人識別和記住,通常情況下用樹形的名稱結構是一個理想的選擇,樹形的名稱 結構是一個有層次的目錄結構,既對人友好又不會重複。說到這裏你可能想到了 JNDI,沒錯 Zookeeper 的 Name Service 與 JNDI 能夠完成的功能是差不多的,它們都是將有層次的目錄結構關聯到一定資源上,但是Zookeeper的Name Service 更加是廣泛意義上的關聯,也許你並不需要將名稱關聯到特定資源上,你可能只需要一個不會重複名稱,就像數據庫中產生一個唯一的數字主鍵一樣。
阿里開源的分佈式服務框架Dubbo中使用ZooKeeper來作爲其命名服務,維護全局的服務地址列表。在Dubbo實現中: 服務提供者在啓動的時候,向ZK上的指定節點/dubbo/{serviceName}/providers目錄下寫入自己的URL地址,這個操作就完成了服務的發佈。 服務消費者啓 動的時候,訂閱/dubbo/{serviceName}/providers目錄下的提供者URL地址, 並向/dubbo/{serviceName} /consumers目錄下寫入自己的URL地址。 注意,所有向ZK上註冊的地址都是臨時節點,這樣就能夠保證服務提供者和消費者能夠自動感應資源的變化。 另外,Dubbo還有針對服務粒度的監控,方法是訂閱/dubbo/{serviceName}目錄下所有提供者和消費者的信息。

- 分佈式鎖
分佈式鎖,這個主要得益於ZooKeeper爲我們保證了數據的強一致性,即用戶只要完全相信每時每刻,zk集羣中任意節點(一個zk server)上的相同znode的數據是一定是相同的。鎖服務可以分爲兩類,一個是保持獨佔,另一個是控制時序。

保持獨佔,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把 鎖。通常的做法是把ZK上的一個znode看作是一把鎖,通過create znode的方式來實現。所有客戶端都去創建 /distribute_lock 節點,最終成功創建的那個客戶端也即擁有了這把鎖。

控制時序,就是所有試圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有 個全局時序了。做法和上面基本類似,只是這裏 /distribute_lock 已經預先存在,客戶端在它下面創建臨時有序節點。Zk的父節點(/distribute_lock)維持一份sequence,保證子節點創建的時序性, 從而也形成了每個客戶端的全局時序。
這裏寫圖片描述

- 集羣管理
這通常用於那種對集羣中機器狀態,機器在線率有較高要求的場景,能夠快速對集羣中機器變化作出響應。這樣的場景中,往往有一個監控系統,實時檢測集 羣機器是否存活。過去的做法通常是:監控系統通過某種手段(比如ping)定時檢測每個機器,或者每個機器自己定時向監控系統彙報”我還活着”。 這種做法可行,但是存在兩個比較明顯的問題:

① 集羣中機器有變動的時候,牽連修改的東西比較多。

② 有一定的延時。

利用ZooKeeper中兩個特性,就可以實施另一種集羣機器存活性監控系統:

① 客戶端在節點 x 上註冊一個Watcher,那麼如果 x 的子節點變化了,會通知該客戶端。

② 創建EPHEMERAL類型的節點,一旦客戶端和服務器的會話結束或過期,那麼該節點就會消失。

應用集羣中,我們常常需要讓每一個機器知道集羣中或依賴的其他某一個集羣中哪些機器是活着的,並且在集羣機器因爲宕機,網絡斷鏈等原因能夠不在人工 介入的情況下迅速通知到每一個機器,Zookeeper 能夠很容易的實現集羣管理的功能,如有多臺 Server 組成一個服務集羣,那麼必須要一個”總管”知道當前集羣中每臺機器的服務狀態,一旦有機器不能提供服務,集羣中其它集羣必須知道,從而做出調整重新分配服 務策略。同樣當增加集羣的服務能力時,就會增加一臺或多臺 Server,同樣也必須讓”總管”知道,這就是ZooKeeper的集羣監控功能。
這裏寫圖片描述

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