Zookeeper——一、Zookeeper基本概念

1.什麼是Zookeeper?

 

Zookeeper是一個高效的分佈式協調服務,它暴露了一些公用服務,好比命名/配置管理/同步控制/羣組服務等。咱們可使用Zookeeper來實現好比達成共識/集羣管理/leader選舉等。java

Zookeeper是一個高可用的分佈式管理與協調框架,基於ZAB算法(原子消息廣播協議)的實現。該框架可以很好的保證分佈式環境中數據的一致性。也只是基於這樣的特性,使得Zookeeper成爲了解決分佈式一致性問題的利器。node

Zookeeper的特性:mysql

①順序一致性:從一個客戶端發起的事務請求,最終將會嚴格的按照其發起的順序被應用到zookeeper中去。算法

②原子性:全部事務請求的處理結果在整個集羣中全部機器上的應用狀況是一致的,也就是說,要麼整個集羣全部的機器都成功應用了某一事務,要麼沒有應用,必定不會出現部分機器應用了該事務,而另外一部分沒有應用的狀況。sql

③單一視圖:不管客戶端鏈接的是哪個zookeeper服務器,其看到的服務器端數據模型都是一致的。shell

④可靠性:一旦服務器成功地應用了一個事務,並完成對客戶端的響應,那麼該事務所引發的服務器端狀態將會被一直保留,除非有另外一個事務對其更改。數據庫

⑤實時性:一般所說的實時性就是指一旦事務被成功應用,那麼客戶端就能馬上從服務器上獲取變動後的新數據,zookeeper僅僅能保證在一段時間內,客戶端最終能從服務器端讀取最新的數據狀態。apache

2.Zookeeper設計目標

①簡單的數據結構。zookeeper就是以簡單的樹形結構來進行相互協調的(也叫樹形名字空間)。設計模式

其中圓形節點能夠含有子節點,多邊形節點不能含有子節點。一個節點對應一個應用,節點存儲的數據就是應用須要的配置信息。服務器

②能夠構建集羣。通常zookeeper集羣一般由一組機器構成,通常3~5臺機器就能夠組成一個zookeeper集羣了。只要集羣中超過半數以上的機器可以正常工做,那麼整個集羣就可以正常對外提供服務。

③順序訪問。對於來自每個客戶端的每個請求,zookeeper都會分配一個全局惟一的遞增編號,這個編號反應了全部事務操做的前後順序,應用程序可使用zookeeper的這個特性來實現更高層次的同步。

④高性能。因爲zookeeper將全局數據存儲在內存中,並直接服務於全部的非事務請求,所以尤爲是在以讀操做爲主的場景下性能很是突出。在JMater壓力測試下(100%讀請求場景下),其結果大約在12~13W的QPS。

3.Zookeeper的數據模型

①每一個子目錄項如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的不少功能都是基於這個特性實現的。

4.Zookeeper的組成

ZK Server根據其身份特性分爲三種:leader、follower、observer,其中follower和observer又統稱爲learner(學習者)。

leader:負責客戶端的writer類型請求。

follower:負責客戶端的reader類請求,參與leader的選舉等。

observe:特殊的」follower「,其能夠接受客戶端reader請求,但不參與選舉,只負責與leader同步數據。

5.Zookeeper的應用場景(該部分出自分佈式服務框架 Zookeeper -- 管理分佈式環境中的數據

Zookeeper從設計模式角度來看,是一個基於觀察者模式設計的分佈式服務管理框架,它負責存儲和管理你們都關心的數據,而後接受觀察者的註冊,一旦這些數據的狀態發生變化,Zookeeper就將負責通知已經在Zookeeper上註冊的那些觀察者作出相應的反應,從而實現集羣中相似Master/Slave的管理模式。

下面將詳細介紹這些典型的應用場景,也就是Zookeeper到底能幫咱們解決哪些問題?

1)統一命名服務(Name Service)

分佈式應用中,一般須要有一套完整的命名規則,既可以產生惟一的名稱又便於人們識別和記憶,一般狀況下用樹形的名稱結構是一個理想的選擇,樹形的名稱結構是一個有層次的目錄結構,既對人友好又不會重複。說到這裏你可能會想到JNDI,沒錯Zookeeper的Name Service與JNDI更夠完成的功能是差很少的,它們都是將有層次的目錄結構關聯到必定的資源上,可是Zookeeper的Name Service更加是普遍意義上的關聯,也許你並不須要將名稱關聯到特定資源上,你可能只是須要一個不會重複的名稱,就像數據庫中產生的一個惟一數字主鍵同樣。

Name Service已是Zookeeper內置的功能,只須要調用Zookeeper的API就能實現。如調用Create接口就能夠很容易建立一個目錄節點。

2)配置管理(Configuration Management)

配置管理在分佈式應用環境中很常見,例如同一個系統須要多臺PC Server運行,可是它們運行的應用系統的某些配置項是相同的,若是要修改這些相同的配置項,那麼就必須同時修改每臺運行了這個應用系統的PC Server,這樣很是麻煩並且容易出錯。

像這樣的配置信息徹底能夠交給Zookeeper來管理,將配置信息保存在Zookeeper的某個目錄節點中,而後將全部須要修改的應用機器監控配置信息的狀態,一旦配置信息發生變化,每臺應用機器就會收到Zookeeper的通知,而後從Zookeeper獲取新的配置信息應用到系統中。

配置管理結構圖:

3)集羣管理

Zookeeper可以很容易的實現集羣管理功能,若有多臺Server組成一個服務集羣,那麼必需要有一個「總管」知道當前集羣中每臺機器的服務狀態,一旦有機器不能提供服務,集羣中其餘集羣必須知道,從而作出調整從新分配服務策略。一樣當增長集羣的服務能力時,就會增長一臺或多臺Server,一樣也必須讓「總管」知道。

Zookeeper不只可以維護當前集羣中機器的服務狀態,並且可以選出一個「總管」,讓這個總管來管理集羣,這就是Zookeeper的另外一個功能Leader Election。

它們的實現方式都是在Zookeeper上建立一個EPHEMERAL類型的目錄節點,而後每一個Server在它們建立目錄節點的父目錄節點上調用getChildren(String path, boolean watch)方法並設置watch爲true,因爲是EPHEMERAL類型的節點,當建立它的Server死去,這個目錄節點也隨之被刪除,因此Children將會變化,這時getChildren上的Watch將會被調用,因此其它Server就知道已經有某臺Server死去了。新增Server也是一樣的原理。

Zookeeper 如何實現 Leader Election,也就是選出一個 Master Server。和前面的同樣每臺 Server 建立一個 EPHEMERAL 目錄節點,不一樣的是它仍是一個 SEQUENTIAL 目錄節點,因此它是個 EPHEMERAL_SEQUENTIAL 目錄節點。之因此它是 EPHEMERAL_SEQUENTIAL 目錄節點,是由於咱們能夠給每臺 Server 編號,咱們能夠選擇當前是最小編號的 Server 爲 Master,假如這個最小編號的 Server 死去,因爲是 EPHEMERAL 節點,死去的 Server 對應的節點也被刪除,因此當前的節點列表中又出現一個最小編號的節點,咱們就選擇這個節點爲當前 Master。這樣就實現了動態選擇 Master,避免了傳統意義上單 Master 容易出現單點故障的問題。

集羣管理結構圖:

Leader Election關鍵代碼:

void findLeader() throws InterruptedException {   
        byte[] leader = null;   
        try {   
            leader = zk.getData(root + "/leader", true, null);   
        } catch (Exception e) {   
            logger.error(e);   
        }   
        if (leader != null) {   
            following();   
        } else {   
            String newLeader = null;   
            try {   
                byte[] localhost = InetAddress.getLocalHost().getAddress();   
                newLeader = zk.create(root + "/leader", localhost,   
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);   
            } catch (Exception e) {   
                logger.error(e);   
            }   
            if (newLeader != null) {   
                leading();   
            } else {   
                mutex.wait();   
            }   
        }   
    }

 4)共享鎖

共享鎖在同一個進程中很容易實現,可是在跨進程或者在不一樣 Server 之間就很差實現了。Zookeeper 卻很容易實現這個功能,實現方式也是須要得到鎖的 Server 建立一個 EPHEMERAL_SEQUENTIAL 目錄節點,而後調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是否是就是本身建立的目錄節點,若是正是本身建立的,那麼它就得到了這個鎖,若是不是那麼它就調用exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到本身建立的節點是列表中最小編號的目錄節點,從而得到鎖,釋放鎖很簡單,只要刪除前面它本身所建立的目錄節點就好了。

Zookeeper實現Locks的流程圖:

同步鎖的關鍵思路:

加鎖:
ZooKeeper 將按照以下方式實現加鎖的操做:
①ZooKeeper 調用 create()方法來建立一個路徑格式爲「 _locknode_/lock- 」的節點,此節點類型爲sequence (連續)和 ephemeral (臨時)。也就是說,建立的節點爲臨時節點,而且全部的節點連續編號,即「 lock-i 」的格式。
②在建立的鎖節點上調用 getChildren()方法,來獲取鎖目錄下的最小編號節點,而且不設置 watch 。
③步驟 2 中獲取的節點剛好是步驟 1 中客戶端建立的節點,那麼此客戶端得到此種類型的鎖,而後退出操做。
④客戶端在鎖目錄上調用exists()方法,而且設置 watch 來監視鎖目錄下比本身小一個的連續臨時節點的狀態。
⑤若是監視節點狀態發生變化,則跳轉到第 2 步,繼續進行後續的操做,直到退出鎖競爭。
解鎖: 
ZooKeeper 解鎖操做很是簡單,客戶端只須要將加鎖操做步驟 1 中建立的臨時節點刪除便可。

同步鎖關鍵代碼:

void getLock() throws KeeperException, InterruptedException{   
        List<String> list = zk.getChildren(root, false);   
        String[] nodes = list.toArray(new String[list.size()]);   
        Arrays.sort(nodes);   
        if(myZnode.equals(root+"/"+nodes[0])){   
            doAction();   
        }   
        else{   
            waitForLock(nodes[0]);   
        }   
    }   
    void waitForLock(String lower) throws InterruptedException, KeeperException {  
        Stat stat = zk.exists(root + "/" + lower,true);   
        if(stat != null){   
            mutex.wait();   
        }   
        else{   
            getLock();   
        }   
    }

5)隊列管理

 

Zookeeper 能夠處理兩種類型的隊列:
①當一個隊列的成員都聚齊時,這個隊列纔可用,不然一直等待全部成員到達,這種是同步隊列。
②隊列按照 FIFO 方式進行入隊和出隊操做,例如實現生產者和消費者模型。
同步隊列用 Zookeeper 實現的實現思路以下:
建立一個父目錄 /synchronizing,每一個成員都監控標誌(Set Watch)位目錄 /synchronizing/start 是否存在,而後每一個成員都加入這個隊列,加入隊列的方式就是建立 /synchronizing/member_i 的臨時目錄節點,而後每一個成員獲取 / synchronizing 目錄的全部目錄節點,也就是 member_i。判斷 i 的值是否已是成員的個數,若是小於成員個數等待 /synchronizing/start 的出現,若是已經相等就建立 /synchronizing/start。
用下面的流程圖更容易理解:

同步隊列關鍵代碼:

 

void addQueue() throws KeeperException, InterruptedException{   
        zk.exists(root + "/start",true);   
        zk.create(root + "/" + name, new byte[0], Ids.OPEN_ACL_UNSAFE,   
        CreateMode.EPHEMERAL_SEQUENTIAL);   
        synchronized (mutex) {   
            List<String> list = zk.getChildren(root, false);   
            if (list.size() < size) {   
                mutex.wait();   
            } else {   
                zk.create(root + "/start", new byte[0], Ids.OPEN_ACL_UNSAFE,  
                 CreateMode.PERSISTENT);   
            }   
        }   
 }

當隊列沒滿時進入wait(),而後會一直等待Watch通知,Watch的代碼以下:

public void process(WatchedEvent event) {   
        if(event.getPath().equals(root + "/start") &&  
         event.getType() == Event.EventType.NodeCreated){   
            System.out.println("獲得通知");   
            super.process(event);   
            doAction();   
        }   
    }


FIFO 隊列用 Zookeeper 實現思路以下:
實現的思路也很是簡單,就是在特定的目錄下建立 SEQUENTIAL 類型的子目錄 /queue_i,這樣就能保證全部成員加入隊列時都是有編號的,出隊列時經過 getChildren( ) 方法能夠返回當前全部的隊列中的元素,而後消費其中最小的一個,這樣就能保證 FIFO。

 

生產者代碼:

boolean produce(int i) throws KeeperException, InterruptedException{   
        ByteBuffer b = ByteBuffer.allocate(4);   
        byte[] value;   
        b.putInt(i);   
        value = b.array();   
        zk.create(root + "/element", value, ZooDefs.Ids.OPEN_ACL_UNSAFE,   
                    CreateMode.PERSISTENT_SEQUENTIAL);   
        return true;   
    }

消費者代碼:

int consume() throws KeeperException, InterruptedException{   
        int retvalue = -1;   
        Stat stat = null;   
        while (true) {   
            synchronized (mutex) {   
                List<String> list = zk.getChildren(root, true);   
                if (list.size() == 0) {   
                    mutex.wait();   
                } else {   
                    Integer min = new Integer(list.get(0).substring(7));   
                    for(String s : list){   
                        Integer tempValue = new Integer(s.substring(7));   
                        if(tempValue < min) min = tempValue;   
                    }   
                    byte[] b = zk.getData(root + "/element" + min,false, stat);   
                    zk.delete(root + "/element" + min, 0);   
                    ByteBuffer buffer = ByteBuffer.wrap(b);   
                    retvalue = buffer.getInt();   
                    return retvalue;   
                }   
            }   
        }   
 }

 

6.Zookeeper的安裝

①先去zookeeper官網下載zookeeper的安裝包,並將zookeeper經過工具上傳到服務器。

②使用mv zookeeper-3.5.2-alpha.tar.gz  /usr/local/命令將zookeeper移動到/usr/local/目錄下,並使用tar -zxvf zookeeper-3.5.2-alpha.tar.gz解壓,最後使用mv zookeeper-3.5.2-alpha zookeeper重命名zookeeper的文件夾,方便後面環境變量的配置。

③使用vi /etc/profile命令配置zookeeper的環境變量,在profile文件最後加入下面兩行:

export ZOOKEEPER_HOME=/usr/local/zookeeper

export PATH=$ZOOKEEPER_HOME/bin:$PATH

爲了使環境變量生效,使用source /etc/profile命令刷新環境變量。

④使用cd /usr/local/zookeeper/conf目錄下修改zookeeper的配置文件。由於zookeeper默認使用的配置文件是zoo.cfg,因此使用mv zoo_sample.cfg zoo.cfg命令重命名zookeeper的配置文件。

⑤使用vi zoo.cfg命令修改zoo.cfg文件,修改以下一處:

dataDir=/usr/local/zookeeper/data

並在文件最後添加下面三行(由於我在學習zookeeper時使用了三臺虛擬機):

server.0=192.168.1.102:2888:3888

server.1=192.168.1.103:2888:3888

server.2=192.168.1.104:2888:3888

退出並保存zoo.cfg文件。

zookeeper配置文件詳解:

tickTime:基本事件單元,以毫秒爲單位。這個時間是做爲zookeeper服務器與服務器之間或客戶端與服務器之間維持心跳的事件間隔。也就是每隔tickTime發送一個心跳。

dataDir:存儲內存中數據快照的位置,就是zookeeper保存數據的目錄。默認狀況下,zookeeper將寫數據的日誌文件也保存在這個位置。

clientPort:客戶端鏈接zookeeper服務器的端口,zookeeper會監聽該端口,接受客戶端的訪問請求。

initLimit:用來配置zookeeper接受客戶端初始化鏈接時最長能忍受多少個心跳時間間隔,當超過10個心跳時間(也就是tickTime)長度後,zookeeper服務器尚未收到客戶端的返回信息,則認爲該客戶端鏈接失敗。總的時間長度就是10 * 2000=20秒(默認狀況下)。

syncLimit:leader與follower之間發送消息,請求和應答時間長度,最長不能超過多少個tickTime的時間長度,總的時間長度就是5 * 2000=10秒(默認狀況下)。

server.A=B:C:D:A表示這個是第幾號服務器,B表示該服務器的ip地址,C表示該服務器與集羣中的leader服務器蔣歡信息的端口,D表示若是集羣中的leader服務器掛了,則經過該端口進行選舉,選出一個新的leader。

⑥服務器標識配置:

在/usr/local/zookeeper目錄下使用mkdir data目錄,並進入data目錄,接着使用vi myid建立myid文件,該文件中的內容爲0(另外兩臺虛擬機爲1,2)。

⑦啓動zookeeper

進入/usr/local/zookeeper/bin目錄,使用zkServer.sh start啓動zookeeper(若是想看日誌或者想看一下在啓動過程當中是否報錯,可使用zkServer.sh start-foreground命令啓動zookeeper)。

使用zkServer.sh tatus來查看zookeeper的狀態,我這裏由於有三個節點,因此其一個爲leader,另外兩個爲follower。

PS:在安裝zookeeper的過程當中,我遇到了下面的兩個問題。

Java.NET.ConnectException: 拒絕鏈接,若是出現這個問題,刪除hosts文件中的第一行,也就是127.0.0.1這行。

②java.Net.NoRouteToHostException: 沒有到主機的路由,若是出現這個問題,則須要關閉防火牆。

7.操做zookeeper(shell)

在ZOOKEEPER_HOME/bin目錄下輸入zkCli.sh能夠進入zookeeper客戶端。

查找:ls /

建立並賦值:create /hyy "mydata"

獲取:get /hyy

設置值:set /hyy "hello"

能夠看到192.168.1.10二、192.168.1.10三、192.168.1.104的zookeeper集羣中的數據是一致的。

deleteall /path刪除遞歸節點(當前要刪除的節點下還有子節點則要使用rmr命令)

delete /path/child刪除指定的節點

建立節點有兩種類型:短暫(ephemeral)、持久(persistent)

相關文章
相關標籤/搜索