Zookeeper 註冊中心解析

Zookeeper 註冊中心解析

 

        Zookeeper 做爲 Hadoop 項目中的一個子項目,是 Hadoop 集羣管理的一個必不可少的模塊,它主要用來控制集羣中的數據,如它管 理 Hadoop 集羣中的 NameNode,還有 Hbase 中 Master Election、Server 之間狀態同步等。html

本文介紹的 Zookeeper 的基本知識,以及介紹了幾個典型的應用場景。這些都 是 Zookeeper 的基本功能,最重要的是 Zoopkeeper 提供了一套很好的分佈式集羣管理的機制,就是它這種基於層次型的目錄樹的數據結 構,並對樹中的節點進行有效管理,從而能夠設計出多種多樣的分佈式的數據管理模型,java

        

 Zookeeper 的 配置文件在 conf 目錄下,這個目錄下有 zoo_sample.cfg 和 log4j.properties,你須要作的就是 將 zoo_sample.cfg 更名爲 zoo.cfg,由於 Zookeeper 在啓動時會找這個文件做爲默認配置文件。下面詳細介紹一下,這個 配置文件中各個配置項的意義。node

 tickTime=2000 數據庫

 dataDir=D:/devtools/zookeeper-3.2.2/build apache

 clientPort=2181 設計模式

 

· tickTime:這個時間是做爲 Zookeeper 服務器之間或客戶端與服務器之間維持心跳的時間間隔,也就是每一個 tickTime 時間就會發送一個心跳。api

· dataDir:顧名思義就是 Zookeeper 保存數據的目錄,默認狀況下,Zookeeper 將寫數據的日誌文件也保存在這個目錄裏。服務器

· clientPort:這個端口就是客戶端鏈接 Zookeeper 服務器的端口,Zookeeper 會監聽這個端口,接受客戶端的訪問請求。session

當這些配置項配置好後,你如今就能夠啓動 Zookeeper 了,啓動後要檢查 Zookeeper 是否已經在服務,能夠經過 netstat – ano 命令查看是否有你配置的 clientPort 端口號在監聽服務。數據結構

集羣模式

Zookeeper 不只能夠單機提供服務,同時也支持多機組成集羣來提供服務。實際上 Zookeeper 還支持另一種僞集羣的方式,也就是能夠在一臺物理機上運行多個 Zookeeper 實例,下面將介紹集羣模式的安裝和配置。

Zookeeper 的集羣模式的安裝和配置也不是很複雜,所要作的就是增長几個配置項。集羣模式除了上面的三個配置項還要增長下面幾個配置項:

 initLimit=5 

 syncLimit=2 

 server.1=192.168.211.1:2888:3888 

 server.2=192.168.211.2:2888:3888 

 

· initLimit: 這個配置項是用來配置 Zookeeper 接受客戶端(這裏所說的客戶端不是用戶鏈接 Zookeeper 服務器的客戶端,而 是 Zookeeper 服務器集羣中鏈接到 Leader 的 Follower 服務器)初始化鏈接時最長能忍受多少個心跳時間間隔數。當已經超 過 10 個心跳的時間(也就是 tickTime)長度後 Zookeeper 服務器尚未收到客戶端的返回信息,那麼代表這個客戶端鏈接失敗。總的 時間長度就是 5*2000=10 秒

· syncLimit:這個配置項標識 Leader 與 Follower 之間發送消息,請求和應答時間長度,最長不能超過多少個 tickTime 的時間長度,總的時間長度就是 2*2000=4 秒

· server.A=B:C:D: 其中 A 是一個數字,表示這個是第幾號服務器;B 是這個服務器的 ip 地址;C 表示的是這個服務器與集羣中的 Leader 服務器交換信息的端 口;D 表示的是萬一集羣中的 Leader 服務器掛了,須要一個端口來從新進行選舉,選出一個新的 Leader,而這個端口就是用來執行選舉時服務 器相互通訊的端口。若是是僞集羣的配置方式,因爲 B 都是同樣,因此不一樣的 Zookeeper 實例通訊端口號不能同樣,因此要給它們分配不一樣的端口 號。

除了修 改 zoo.cfg 配置文件,集羣模式下還要配置一個文件 myid,這個文件在 dataDir 目錄下,這個文件裏面就有一個數據就是 A 的 值,Zookeeper 啓動時會讀取這個文件,拿到裏面的數據與 zoo.cfg 裏面的配置信息比較從而判斷究竟是那個 server。

 

數據模型

Zookeeper 會維護一個具備層次關係的數據結構,它很是相似於一個標準的文件系統,如圖 1 所示:


圖 1 Zookeeper 數據結構

Zookeeper 這種數據結構有以下這些特色:

1. 每一個子目錄項如 NameService 都被稱做爲 znode,這個 znode 是被它所在的路徑惟一標識,如 Server1 這個 znode 的標識爲 /NameService/Server1

2. znode 能夠有子節點目錄,而且每一個 znode 能夠存儲數據,注意 EPHEMERAL 類型的目錄節點不能有子節點目錄

3. znode 是有版本的,每一個 znode 中存儲的數據能夠有多個版本,也就是一個訪問路徑中能夠存儲多份數據

4. znode 可 以是臨時節點,一旦建立這個 znode 的客戶端與服務器失去聯繫,這個 znode 也將自動刪除,Zookeeper 的客戶端和服務器通訊採用長 鏈接方式,每一個客戶端和服務器經過心跳來保持鏈接,這個鏈接狀態稱爲 session,若是 znode 是臨時節點,這個 session 失 效,znode 也就刪除了

5. znode 的目錄名能夠自動編號,如 App1 已經存在,再建立的話,將會自動命名爲 App2

6. znode 能夠被監控,包括這個目錄節點中存儲的數據的修改,子節點目錄的變化等,一旦變化能夠通知設置監控的客戶端,這個是 Zookeeper 的核心特性,Zookeeper 的不少功能都是基於這個特性實現的,後面在典型的應用場景中會有實例介紹

 

如何使用

Zookeeper 做 爲一個分佈式的服務框架,主要用來解決分佈式集羣中應用系統的一致性問題,它能提供基於相似於文件系統的目錄節點樹方式的數據存儲,但 是 Zookeeper 並非用來專門存儲數據的,它的做用主要是用來維護和監控你存儲的數據的狀態變化。經過監控這些數據狀態的變化,從而能夠達到基 於數據的集羣管理,後面將會詳細介紹 Zookeeper 可以解決的一些典型問題,這裏先介紹一下,Zookeeper 的操做接口和簡單使用示例。

經常使用接口列表

客戶端要鏈接 Zookeeper 服務器能夠經過建立 org.apache.zookeeper. ZooKeeper 的一個實例對象,而後調用這個類提供的接口來和服務器交互。

前面說 了 ZooKeeper 主要是用來維護和監控一個目錄節點樹中存儲的數據的狀態,全部咱們可以操做 ZooKeeper 的也和操做目錄節點樹大致一 樣,如建立一個目錄節點,給某個目錄節點設置數據,獲取某個目錄節點的全部子目錄節點,給某個目錄節點設置權限和監控這個目錄節點的狀態變化。

這些接口以下表所示:


表 1 org.apache.zookeeper. ZooKeeper 方法列表

 

方法名 

方法功能描述 

String create(String path, byte[] data, List<ACL> acl, CreateMode createMode) 

建立一個給定的目錄節點 path, 並給它設置數據,CreateMode 標 識有四種形式的目錄節點,分別是 PERSISTENT:持久化目錄節點,這個目錄節點存儲的數據不會丟 失;PERSISTENT_SEQUENTIAL:順序自動編號的目錄節點,這種目錄節點會根據當前已近存在的節點數自動加 1,而後返回給客戶端已經成 功建立的目錄節點名;EPHEMERAL:臨時目錄節點,一旦建立這個節點的客戶端與服務器端口也就是 session 超時,這種節點會被自動刪 除;EPHEMERAL_SEQUENTIAL:臨時自動編號節點 

Stat exists(String path, boolean watch) 

判斷某個 path 是否存在,並設置是否監控這個目錄節點,這裏的 watcher 是在建立 ZooKeeper 實例時指定的 watcher,exists方法還有一個重載方法,能夠指定特定的 watcher 

Stat exists(String path, Watcher watcher) 

重載方法,這裏給某個目錄節點設置特定 的 watcher,Watcher 在 ZooKeeper 是一個核心功能,Watcher 能夠監控目錄節點的數據變化以及子目錄的變化,一旦這些 狀態發生變化,服務器就會通知全部設置在這個目錄節點上的 Watcher,從而每一個客戶端都很快知道它所關注的目錄節點的狀態發生變化,而作出相應的反 應 

void delete(String path, int version) 

刪除 path 對應的目錄節點,version 爲 -1 能夠匹配任何版本,也就刪除了這個目錄節點全部數據 

List<StringgetChildren(String path, boolean watch) 

獲取指定 path 下的全部子目錄節點,一樣 getChildren方法也有一個重載方法能夠設置特定的 watcher 監控子節點的狀態 

Stat setData(String path, byte[] data, int version) 

給 path 設置數據,能夠指定這個數據的版本號,若是 version 爲 -1 怎能夠匹配任何版本 

byte[] getData(String path, boolean watch, Stat stat) 

獲取這個 path 對應的目錄節點存儲的數據,數據的版本等信息能夠經過 stat 來指定,同時還能夠設置是否監控這個目錄節點數據的狀態 

void addAuthInfo(String scheme, byte[] auth) 

客戶端將本身的受權信息提交給服務器,服務器將根據這個受權信息驗證客戶端的訪問權限。 

Stat setACL(String path, List<ACL> acl, int version) 

給某個目錄節點從新設置訪問權限,須要注意的是 Zookeeper 中的目錄節點權限不具備傳遞性,父目錄節點的權限不能傳遞給子目錄節點。目錄節點 ACL 由兩部分組成:perms 和 id。
Perms 有 ALL、READ、WRITE、CREATE、DELETE、ADMIN 幾種 
而 id 標識了訪問目錄節點的身份列表,默認狀況下有如下兩種:
ANYONE_ID_UNSAFE = new Id("world", "anyone") 和 AUTH_IDS = new Id("auth", "") 分別表示任何人均可以訪問和建立者擁有訪問權限。 

List<ACLgetACL(String path, Stat stat) 

獲取某個目錄節點的訪問權限列表 

 

 

除了以上這些上表中列出的方法以外還有一些重載方法,如都提供了一個回調類的重載方法以及能夠設置特定 Watcher 的重載方法,具體的方法能夠參考 org.apache.zookeeper. ZooKeeper 類的 API 說明。

基本操做

下面給出基本的操做 ZooKeeper 的示例代碼,這樣你就能對 ZooKeeper 有直觀的認識了。下面的清單包括了建立與 ZooKeeper 服務器的鏈接以及最基本的數據操做:


清單 2. ZooKeeper 基本的操做示例

 

 

 // 建立一個與服務器的鏈接

 ZooKeeper zk = new ZooKeeper("localhost:" + CLIENT_PORT, 

        ClientBase.CONNECTION_TIMEOUT, new Watcher() { 

            // 監控全部被觸發的事件

            public void process(WatchedEvent event) { 

                System.out.println("已經觸發了" + event.getType() + "事件!"); 

            } 

        }); 

 // 建立一個目錄節點

 zk.create("/testRootPath", "testRootData".getBytes(), Ids.OPEN_ACL_UNSAFE,

   CreateMode.PERSISTENT); 

 // 建立一個子目錄節點

 zk.create("/testRootPath/testChildPathOne", "testChildDataOne".getBytes(),

   Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 

 System.out.println(new String(zk.getData("/testRootPath",false,null))); 

 // 取出子目錄節點列表

 System.out.println(zk.getChildren("/testRootPath",true)); 

 // 修改子目錄節點數據

 zk.setData("/testRootPath/testChildPathOne","modifyChildDataOne".getBytes(),-1); 

 System.out.println("目錄節點狀態:["+zk.exists("/testRootPath",true)+"]"); 

 // 建立另一個子目錄節點

 zk.create("/testRootPath/testChildPathTwo", "testChildDataTwo".getBytes(), 

   Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 

 System.out.println(new String(zk.getData("/testRootPath/testChildPathTwo",true,null))); 

 // 刪除子目錄節點

 zk.delete("/testRootPath/testChildPathTwo",-1); 

 zk.delete("/testRootPath/testChildPathOne",-1); 

 // 刪除父目錄節點

 zk.delete("/testRootPath",-1); 

 // 關閉鏈接

 zk.close(); 

 

 

輸出的結果以下:

 

已經觸發了 None 事件!

 testRootData 

 [testChildPathOne] 

目錄節點狀態:[5,5,1281804532336,1281804532336,0,1,0,0,12,1,6] 

已經觸發了 NodeChildrenChanged 事件!

 testChildDataTwo 

已經觸發了 NodeDeleted 事件!

已經觸發了 NodeDeleted 事件!

 

 

當對目錄節點監控狀態打開時,一旦目錄節點的狀態發生變化,Watcher 對象的 process 方法就會被調用。

 

ZooKeeper 典型的應用場景

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

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

統一命名服務(Name Service

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

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

配置管理(Configuration Management

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

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


圖 2. 配置管理結構圖

集羣管理(Group Membership

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 容易出現單點故障的問題。


圖 3. 集羣管理結構圖

這部分的示例代碼以下,完整的代碼請看附件:


清單 3. 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(); 

            } 

        } 

    } 

 

 

共享鎖(Locks

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


圖 4. Zookeeper 實現 Locks 的流程圖

同步鎖的實現代碼以下,完整的代碼請看附件:


清單 4. 同步鎖的關鍵代碼

 

 

 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(); 

        } 

    } 

 

 

隊列管理

Zookeeper 能夠處理兩種類型的隊列:

1. 當一個隊列的成員都聚齊時,這個隊列纔可用,不然一直等待全部成員到達,這種是同步隊列。

2. 隊列按照 FIFO 方式進行入隊和出隊操做,例如實現生產者和消費者模型。

同步隊列用 Zookeeper 實現的實現思路以下:

建立一個 父目錄 /synchronizing,每一個成員都監控標誌(Set Watch)位目錄 /synchronizing/start 是否存在,而後每 個成員都加入這個隊列,加入隊列的方式就是建立 /synchronizing/member_i 的臨時目錄節點,而後每一個成員獲 取 / synchronizing 目錄的全部目錄節點,也就是 member_i。判斷 i 的值是否已是成員的個數,若是小於成員個數等 待 /synchronizing/start 的出現,若是已經相等就建立 /synchronizing/start。

用下面的流程圖更容易理解:


圖 5. 同步隊列流程圖

同步隊列的關鍵代碼以下,完整的代碼請看附件:


清單 5. 同步隊列

 

 

 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。

下面是生產者和消費者這種隊列形式的示例代碼,完整的代碼請看附件:


清單 6. 生產者代碼

 

 

 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; 

    } 

 



清單 7. 消費者代碼

 

 

 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; 

                } 

            } 

        } 

 } 

相關文章
相關標籤/搜索