Zookeeper相關知識

一.Zookeeper是什麼?html

Zookeeper 分佈式服務框架是 Apache Hadoop 的一個子項目,它主要是用來解決分佈式應用中常常遇到的一些數據管理問題,如:統一命名服務、狀態同步服務、集羣管理、分佈式應用配置項的管理等。java

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

二.Zookeeper能作什麼?shell

Zookeeper 做爲一個分佈式的服務框架,主要用來解決分佈式集羣中應用系統的一致性問題,它能提供基於相似於文件系統的目錄節點樹方式的數據存儲,可是 Zookeeper 並非用來專門存儲數據的,它的做用主要是用來維護和監控你存儲的數據的狀態變化。經過監控這些數據狀態的變化,從而能夠達到基於數據的集羣管理。數據庫

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

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

三.Zookeeper環境如何搭建?設計模式

安裝和配置詳解

本文介紹的 Zookeeper 是以 3.2.2 這個穩定版本爲基礎,最新的版本能夠經過官網http://hadoop.apache.org/zookeeper/來獲取,Zookeeper 的安裝很是簡單,下面將從單機模式和集羣模式兩個方面介紹 Zookeeper 的安裝和配置。api

單機模式

單機安裝很是簡單,只要獲取到 Zookeeper 的壓縮包並解壓到某個目錄如:/home/zookeeper-3.2.2 下,Zookeeper 的啓動腳本在 bin 目錄下,Linux 下的啓動腳本是 zkServer.sh,在 3.2.2 這個版本 Zookeeper 沒有提供 windows 下的啓動腳本,因此要想在 windows 下啓動 Zookeeper 要本身手工寫一個,如清單 1 所示:服務器

清單 1. Windows 下 Zookeeper 啓動腳本
 setlocal 
 set ZOOCFGDIR=%~dp0%..\conf 
 set ZOO_LOG_DIR=%~dp0%.. 
 set ZOO_LOG4J_PROP=INFO,CONSOLE 
 set CLASSPATH=%ZOOCFGDIR% 

 set CLASSPATH=%~dp0..\*;%~dp0..\lib\*;%CLASSPATH% 
 set CLASSPATH=%~dp0..\build\classes;%~dp0..\build\lib\*;%CLASSPATH% 
 set ZOOCFG=%ZOOCFGDIR%\zoo.cfg 
 set ZOOMAIN=org.apache.zookeeper.server.ZooKeeperServerMain 
 java "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" 
 -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %* 
 endlocal

 

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

 tickTime=2000 
 dataDir=D:/devtools/zookeeper-3.2.2/build 
 clientPort=2181

 

  • tickTime:這個時間是做爲 Zookeeper 服務器之間或客戶端與服務器之間維持心跳的時間間隔,也就是每一個 tickTime 時間就會發送一個心跳。
  • dataDir:顧名思義就是 Zookeeper 保存數據的目錄,默認狀況下,Zookeeper 將寫數據的日誌文件也保存在這個目錄裏。
  • clientPort:這個端口就是客戶端鏈接 Zookeeper 服務器的端口,Zookeeper 會監聽這個端口,接受客戶端的訪問請求。

當這些配置項配置好後,你如今就能夠啓動 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維護及其餘操做經常使用命令

4.1 Zookeeper Server經常使用命令

啓動Zookeeper服務:   bin/zkServer.sh start
查看Zookeeper服務狀態: bin/zkServer.sh status
中止Zookeeper服務:   bin/zkServer.sh stop
重啓Zookeeper服務:   bin/zkServer.sh restart


鏈接服務器:  zkCli.sh -server 127.0.0.1:2181
查看根目錄 ls /
建立 testnode節點,關聯字符串"HAO"    create /zk/testnode "HAO"


查看節點內容   get /zk/testnode
設置節點內容  set /zk/testnode abc
刪除節點    delete /zk/testnode

 

4.2 Zookeeper客戶端命令
ZooKeeper命令行工具相似於Linux的shell環境,不過功能確定不及shell啦,可是使用它咱們能夠簡單的對ZooKeeper進行訪問,數據建立,數據修改等操做. 使用 zkCli.sh -server 127.0.0.1:2181 鏈接到 ZooKeeper 服務,鏈接成功後,系統會輸出 ZooKeeper 的相關環境以及配置信息。命令行工具的一些簡單操做以下:

1. 顯示根目錄下、文件: ls / 使用 ls 命令來查看當前 ZooKeeper 中所包含的內容
2. 顯示根目錄下、文件: ls2 / 查看當前節點數據並能看到更新次數等數據
3. 建立文件,並設置初始內容: create /zk "test" 建立一個新的 znode節點「 zk 」以及與它關聯的字符串
4. 獲取文件內容: get /zk 確認 znode 是否包含咱們所建立的字符串
5. 修改文件內容: set /zk "zkbak" 對 zk 所關聯的字符串進行設置
6. 刪除文件: delete /zk 將剛纔建立的 znode 刪除
7. 退出客戶端: quit
8. 幫助命令: help

 

4.3 ZooKeeper 經常使用四字命令

ZooKeeper 支持某些特定的四字命令字母與其的交互。它們大可能是查詢命令,用來獲取 ZooKeeper 服務的當前狀態及相關信息。用戶在客戶端能夠經過 telnet 或 nc 向 ZooKeeper 提交相應的命令

1. 能夠經過命令:echo stat|nc 127.0.0.1 2181 來查看哪一個節點被選擇做爲follower或者leader
2. 使用echo ruok|nc 127.0.0.1 2181 測試是否啓動了該Server,若回覆imok表示已經啓動。
3. echo dump| nc 127.0.0.1 2181 ,列出未經處理的會話和臨時節點。
4. echo kill | nc 127.0.0.1 2181 ,關掉server


5. echo conf | nc 127.0.0.1 2181 ,輸出相關服務配置的詳細信息。
6. echo cons | nc 127.0.0.1 2181 ,列出全部鏈接到服務器的客戶端的徹底的鏈接 / 會話的詳細信息。
7. echo envi |nc 127.0.0.1 2181 ,輸出關於服務環境的詳細信息(區別於 conf 命令)。
8. echo reqs | nc 127.0.0.1 2181 ,列出未經處理的請求。


9. echo wchs | nc 127.0.0.1 2181 ,列出服務器 watch 的詳細信息。
10. echo wchc | nc 127.0.0.1 2181 ,經過 session 列出服務器 watch 的詳細信息,它的輸出是一個與 watch 相關的會話的列表。
11. echo wchp | nc 127.0.0.1 2181 ,經過路徑列出服務器 watch 的詳細信息。它輸出一個與 session 相關的路徑。

 

五.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. 配置管理結構圖

圖 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. 集羣管理結構圖

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

 

清單 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. 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. 同步隊列流程圖

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

 

清單 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; 
                } 
            } 
        } 
 }

部份內容參考:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/ 

 

六.Zookeeper基本原理

1. 數據模型
zookeeper-tree
如上圖所示,ZooKeeper數據模型的結構與Unix文件系統很相似,總體上能夠看做是一棵樹,每一個節點稱作一個ZNode。每一個ZNode均可以經過其路徑惟一標識,好比上圖中第三層的第一個ZNode, 它的路徑是/app1/c1。在每一個ZNode上可存儲少許數據(默認是1M, 能夠經過配置修改, 一般不建議在ZNode上存儲大量的數據),這個特性很是有用,在後面的典型應用場景中會介紹到。另外,每一個ZNode上還存儲了其Acl信息,這裏須要注意,雖然說ZNode的樹形結構跟Unix文件系統很相似,可是其Acl與Unix文件系統是徹底不一樣的,每一個ZNode的Acl的獨立的,子結點不會繼承父結點的。

2.重要概念 
2.1 ZNode
前文已介紹了ZNode, ZNode根據其自己的特性,能夠分爲下面兩類:

  • Regular ZNode: 常規型ZNode, 用戶須要顯式的建立、刪除
  • Ephemeral ZNode: 臨時型ZNode, 用戶建立它以後,能夠顯式的刪除,也能夠在建立它的Session結束後,由ZooKeeper Server自動刪除

ZNode還有一個Sequential的特性,若是建立的時候指定的話,該ZNode的名字後面會自動Append一個不斷增長的SequenceNo。

2.2 Session
Client與ZooKeeper之間的通訊,須要建立一個Session,這個Session會有一個超時時間。由於ZooKeeper集羣會把Client的Session信息持久化,因此在Session沒超時以前,Client與ZooKeeper Server的鏈接能夠在各個ZooKeeper Server之間透明地移動。

在實際的應用中,若是Client與Server之間的通訊足夠頻繁,Session的維護就不須要其它額外的消息了。不然,ZooKeeper Client會每t/3 ms發一次心跳給Server,若是Client 2t/3 ms沒收到來自Server的心跳回應,就會換到一個新的ZooKeeper Server上。這裏t是用戶配置的Session的超時時間。

2.3 Watcher
ZooKeeper支持一種Watch操做,Client能夠在某個ZNode上設置一個Watcher,來Watch該ZNode上的變化。若是該ZNode上有相應的變化,就會觸發這個Watcher,把相應的事件通知給設置Watcher的Client。須要注意的是,ZooKeeper中的Watcher是一次性的,即觸發一次就會被取消,若是想繼續Watch的話,須要客戶端從新設置Watcher。這個跟epoll裏的oneshot模式有點相似。

3. ZooKeeper特性 
3.1 讀、寫(更新)模式
在ZooKeeper集羣中,讀能夠從任意一個ZooKeeper Server讀,這一點是保證ZooKeeper比較好的讀性能的關鍵;寫的請求會先Forwarder到Leader,而後由Leader來經過ZooKeeper中的原子廣播協議,將請求廣播給全部的Follower,Leader收到一半以上的寫成功的Ack後,就認爲該寫成功了,就會將該寫進行持久化,並告訴客戶端寫成功了。

3.2 WAL和Snapshot
和大多數分佈式系統同樣,ZooKeeper也有WAL(Write-Ahead-Log),對於每個更新操做,ZooKeeper都會先寫WAL, 而後再對內存中的數據作更新,而後向Client通知更新結果。另外,ZooKeeper還會按期將內存中的目錄樹進行Snapshot,落地到磁盤上,這個跟HDFS中的FSImage是比較相似的。這麼作的主要目的,一固然是數據的持久化,二是加快重啓以後的恢復速度,若是所有經過Replay WAL的形式恢復的話,會比較慢。

3.3 FIFO
對於每個ZooKeeper客戶端而言,全部的操做都是遵循FIFO順序的,這一特性是由下面兩個基本特性來保證的:一是ZooKeeper Client與Server之間的網絡通訊是基於TCP,TCP保證了Client/Server之間傳輸包的順序;二是ZooKeeper Server執行客戶端請求也是嚴格按照FIFO順序的。

3.4 Linearizability
在ZooKeeper中,全部的更新操做都有嚴格的偏序關係,更新操做都是串行執行的,這一點是保證ZooKeeper功能正確性的關鍵。

ZooKeeper Client API

ZooKeeper Client Library提供了豐富直觀的API供用戶程序使用,下面是一些經常使用的API:

  • create(path, data, flags): 建立一個ZNode, path是其路徑,data是要存儲在該ZNode上的數據,flags經常使用的有: PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
  • delete(path, version): 刪除一個ZNode,能夠經過version刪除指定的版本, 若是version是-1的話,表示刪除全部的版本
  • exists(path, watch): 判斷指定ZNode是否存在,並設置是否Watch這個ZNode。這裏若是要設置Watcher的話,Watcher是在建立ZooKeeper實例時指定的,若是要設置特定的Watcher的話,能夠調用另外一個重載版本的exists(path, watcher)。如下幾個帶watch參數的API也都相似
  • getData(path, watch): 讀取指定ZNode上的數據,並設置是否watch這個ZNode
  • setData(path, watch): 更新指定ZNode的數據,並設置是否Watch這個ZNode
  • getChildren(path, watch): 獲取指定ZNode的全部子ZNode的名字,並設置是否Watch這個ZNode
  • sync(path): 把全部在sync以前的更新操做都進行同步,達到每一個請求都在半數以上的ZooKeeper Server上生效。path參數目前沒有用
  • setAcl(path, acl): 設置指定ZNode的Acl信息
  • getAcl(path): 獲取指定ZNode的Acl信息

ZooKeeper典型應用場景

1. 名字服務(NameService) 
分佈式應用中,一般須要一套完備的命令機制,既能產生惟一的標識,又方便人識別和記憶。 咱們知道,每一個ZNode均可以由其路徑惟一標識,路徑自己也比較簡潔直觀,另外ZNode上還能夠存儲少許數據,這些都是實現統一的NameService的基礎。下面以在HDFS中實現NameService爲例,來講明實現NameService的基本布驟:

  • 目標:經過簡單的名字來訪問指定的HDFS機羣
  • 定義命名規則:這裏要作到簡潔易記憶。下面是一種可選的方案: [serviceScheme://][zkCluster]-[clusterName],好比hdfs://lgprc-example/表示基於lgprc ZooKeeper集羣的用來作example的HDFS集羣
  • 配置DNS映射: 將zkCluster的標識lgprc經過DNS解析到對應的ZooKeeper集羣的地址
  • 建立ZNode: 在對應的ZooKeeper上建立/NameService/hdfs/lgprc-example結點,將HDFS的配置文件存儲於該結點下
  • 用戶程序要訪問hdfs://lgprc-example/的HDFS集羣,首先經過DNS找到lgprc的ZooKeeper機羣的地址,而後在ZooKeeper的/NameService/hdfs/lgprc-example結點中讀取到HDFS的配置,進而根據獲得的配置,獲得HDFS的實際訪問入口

2. 配置管理(Configuration Management) 
在分佈式系統中,常會遇到這樣的場景: 某個Job的不少個實例在運行,它們在運行時大多數配置項是相同的,若是想要統一改某個配置,一個個實例去改,是比較低效,也是比較容易出錯的方式。經過ZooKeeper能夠很好的解決這樣的問題,下面的基本的步驟:

  • 將公共的配置內容放到ZooKeeper中某個ZNode上,好比/service/common-conf
  • 全部的實例在啓動時都會傳入ZooKeeper集羣的入口地址,而且在運行過程當中Watch /service/common-conf這個ZNode
  • 若是集羣管理員修改了了common-conf,全部的實例都會被通知到,根據收到的通知更新本身的配置,並繼續Watch /service/common-conf

3. 組員管理(Group Membership) 
在典型的Master-Slave結構的分佈式系統中,Master須要做爲「總管」來管理全部的Slave, 當有Slave加入,或者有Slave宕機,Master都須要感知到這個事情,而後做出對應的調整,以便不影響整個集羣對外提供服務。以HBase爲例,HMaster管理了全部的RegionServer,當有新的RegionServer加入的時候,HMaster須要分配一些Region到該RegionServer上去,讓其提供服務;當有RegionServer宕機時,HMaster須要將該RegionServer以前服務的Region都從新分配到當前正在提供服務的其它RegionServer上,以便不影響客戶端的正常訪問。下面是這種場景下使用ZooKeeper的基本步驟:

  • Master在ZooKeeper上建立/service/slaves結點,並設置對該結點的Watcher
  • 每一個Slave在啓動成功後,建立惟一標識本身的臨時性(Ephemeral)結點/service/slaves/${slave_id},並將本身地址(ip/port)等相關信息寫入該結點
  • Master收到有新子結點加入的通知後,作相應的處理
  • 若是有Slave宕機,因爲它所對應的結點是臨時性結點,在它的Session超時後,ZooKeeper會自動刪除該結點
  • Master收到有子結點消失的通知,作相應的處理

4. 簡單互斥鎖(Simple Lock) 
咱們知識,在傳統的應用程序中,線程、進程的同步,均可以經過操做系統提供的機制來完成。可是在分佈式系統中,多個進程之間的同步,操做系統層面就無能爲力了。這時候就須要像ZooKeeper這樣的分佈式的協調(Coordination)服務來協助完成同步,下面是用ZooKeeper實現簡單的互斥鎖的步驟,這個能夠和線程間同步的mutex作類比來理解:

  • 多個進程嘗試去在指定的目錄下去建立一個臨時性(Ephemeral)結點 /locks/my_lock
  • ZooKeeper能保證,只會有一個進程成功建立該結點,建立結點成功的進程就是搶到鎖的進程,假設該進程爲A
  • 其它進程都對/locks/my_lock進行Watch
  • 當A進程再也不須要鎖,能夠顯式刪除/locks/my_lock釋放鎖;或者是A進程宕機後Session超時,ZooKeeper系統自動刪除/locks/my_lock結點釋放鎖。此時,其它進程就會收到ZooKeeper的通知,並嘗試去建立/locks/my_lock搶鎖,如此循環反覆

5. 互斥鎖(Simple Lock without Herd Effect) 
上一節的例子中有一個問題,每次搶鎖都會有大量的進程去競爭,會形成羊羣效應(Herd Effect),爲了解決這個問題,咱們能夠經過下面的步驟來改進上述過程:

  • 每一個進程都在ZooKeeper上建立一個臨時的順序結點(Ephemeral Sequential) /locks/lock_${seq}
  • ${seq}最小的爲當前的持鎖者(${seq}是ZooKeeper生成的Sequenctial Number)
  • 其它進程都對只watch比它次小的進程對應的結點,好比2 watch 1, 3 watch 2, 以此類推
  • 當前持鎖者釋放鎖後,比它次大的進程就會收到ZooKeeper的通知,它成爲新的持鎖者,如此循環反覆

這裏須要補充一點,一般在分佈式系統中用ZooKeeper來作Leader Election(選主)就是經過上面的機制來實現的,這裏的持鎖者就是當前的「主」。

6. 讀寫鎖(Read/Write Lock) 
咱們知道,讀寫鎖跟互斥鎖相比不一樣的地方是,它分紅了讀和寫兩種模式,多個讀能夠併發執行,但寫和讀、寫都互斥,不能同時執行行。利用ZooKeeper,在上面的基礎上,稍作修改也能夠實現傳統的讀寫鎖的語義,下面是基本的步驟:

  • 每一個進程都在ZooKeeper上建立一個臨時的順序結點(Ephemeral Sequential) /locks/lock_${seq}
  • ${seq}最小的一個或多個結點爲當前的持鎖者,多個是由於多個讀能夠併發
  • 須要寫鎖的進程,Watch比它次小的進程對應的結點
  • 須要讀鎖的進程,Watch比它小的最後一個寫進程對應的結點
  • 當前結點釋放鎖後,全部Watch該結點的進程都會被通知到,他們成爲新的持鎖者,如此循環反覆

7. 屏障(Barrier) 
在分佈式系統中,屏障是這樣一種語義: 客戶端須要等待多個進程完成各自的任務,而後才能繼續往前進行下一步。下用是用ZooKeeper來實現屏障的基本步驟:

  • Client在ZooKeeper上建立屏障結點/barrier/my_barrier,並啓動執行各個任務的進程
  • Client經過exist()來Watch /barrier/my_barrier結點
  • 每一個任務進程在完成任務後,去檢查是否達到指定的條件,若是沒達到就啥也不作,若是達到了就把/barrier/my_barrier結點刪除
  • Client收到/barrier/my_barrier被刪除的通知,屏障消失,繼續下一步任務

8. 雙屏障(Double Barrier)
雙屏障是這樣一種語義: 它能夠用來同步一個任務的開始和結束,當有足夠多的進程進入屏障後,纔開始執行任務;當全部的進程都執行完各自的任務後,屏障才撤銷。下面是用ZooKeeper來實現雙屏障的基本步驟:

        進入屏障:

 

  • Client Watch /barrier/ready結點, 經過判斷該結點是否存在來決定是否啓動任務
  • 每一個任務進程進入屏障時建立一個臨時結點/barrier/process/${process_id},而後檢查進入屏障的結點數是否達到指定的值,若是達到了指定的值,就建立一個/barrier/ready結點,不然繼續等待
  • Client收到/barrier/ready建立的通知,就啓動任務執行過程
        離開屏障:

 

  • Client Watch /barrier/process,若是其沒有子結點,就能夠認爲任務執行結束,能夠離開屏障 
  • 每一個任務進程執行任務結束後,都須要刪除本身對應的結點/barrier/process/${process_id} 

 

綜上,Zookeeper 的更多知識,在深刻學習和使用以後,再作補充。

相關文章
相關標籤/搜索