一.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 所示:服務器
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
當這些配置項配置好後,你如今就能夠啓動 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
除了修改 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常見應用場景
分佈式應用中,一般須要有一套完整的命名規則,既可以產生惟一的名稱又便於人識別和記住,一般狀況下用樹形的名稱結構是一個理想的選擇,樹形的名稱結構是一個有層次的目錄結構,既對人友好又不會重複。說到這裏你可能想到了 JNDI,沒錯 Zookeeper 的 Name Service 與 JNDI 可以完成的功能是差很少的,它們都是將有層次的目錄結構關聯到必定資源上,可是 Zookeeper 的 Name Service 更加是普遍意義上的關聯,也許你並不須要將名稱關聯到特定資源上,你可能只須要一個不會重複名稱,就像數據庫中產生一個惟一的數字主鍵同樣。
Name Service 已是 Zookeeper 內置的功能,你只要調用 Zookeeper 的 API 就能實現。如調用 create 接口就能夠很容易建立一個目錄節點。
配置的管理在分佈式應用環境中很常見,例如同一個應用系統須要多臺 PC Server 運行,可是它們運行的應用系統的某些配置項是相同的,若是要修改這些相同的配置項,那麼就必須同時修改每臺運行這個應用系統的 PC Server,這樣很是麻煩並且容易出錯。
像這樣的配置信息徹底能夠交給 Zookeeper 來管理,將配置信息保存在 Zookeeper 的某個目錄節點中,而後將全部須要修改的應用機器監控配置信息的狀態,一旦配置信息發生變化,每臺應用機器就會收到 Zookeeper 的通知,而後從 Zookeeper 獲取新的配置信息應用到系統中。
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 容易出現單點故障的問題。
這部分的示例代碼以下,完整的代碼請看附件:
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(); } } }
共享鎖在同一個進程中很容易實現,可是在跨進程或者在不一樣 Server 之間就很差實現了。Zookeeper 卻很容易實現這個功能,實現方式也是須要得到鎖的 Server 建立一個 EPHEMERAL_SEQUENTIAL 目錄節點,而後調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是否是就是本身建立的目錄節點,若是正是本身建立的,那麼它就得到了這個鎖,若是不是那麼它就調用 exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到本身建立的節點是列表中最小編號的目錄節點,從而得到鎖,釋放鎖很簡單,只要刪除前面它本身所建立的目錄節點就好了。
同步鎖的實現代碼以下,完整的代碼請看附件:
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 能夠處理兩種類型的隊列:
同步隊列用 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; } } } }
部份內容參考:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/
六.Zookeeper基本原理
1. 數據模型
如上圖所示,ZooKeeper數據模型的結構與Unix文件系統很相似,總體上能夠看做是一棵樹,每一個節點稱作一個ZNode。每一個ZNode均可以經過其路徑惟一標識,好比上圖中第三層的第一個ZNode, 它的路徑是/app1/c1。在每一個ZNode上可存儲少許數據(默認是1M, 能夠經過配置修改, 一般不建議在ZNode上存儲大量的數據),這個特性很是有用,在後面的典型應用場景中會介紹到。另外,每一個ZNode上還存儲了其Acl信息,這裏須要注意,雖然說ZNode的樹形結構跟Unix文件系統很相似,可是其Acl與Unix文件系統是徹底不一樣的,每一個ZNode的Acl的獨立的,子結點不會繼承父結點的。
2.重要概念
2.1 ZNode
前文已介紹了ZNode, ZNode根據其自己的特性,能夠分爲下面兩類:
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 Library提供了豐富直觀的API供用戶程序使用,下面是一些經常使用的API:
1. 名字服務(NameService)
分佈式應用中,一般須要一套完備的命令機制,既能產生惟一的標識,又方便人識別和記憶。 咱們知道,每一個ZNode均可以由其路徑惟一標識,路徑自己也比較簡潔直觀,另外ZNode上還能夠存儲少許數據,這些都是實現統一的NameService的基礎。下面以在HDFS中實現NameService爲例,來講明實現NameService的基本布驟:
2. 配置管理(Configuration Management)
在分佈式系統中,常會遇到這樣的場景: 某個Job的不少個實例在運行,它們在運行時大多數配置項是相同的,若是想要統一改某個配置,一個個實例去改,是比較低效,也是比較容易出錯的方式。經過ZooKeeper能夠很好的解決這樣的問題,下面的基本的步驟:
3. 組員管理(Group Membership)
在典型的Master-Slave結構的分佈式系統中,Master須要做爲「總管」來管理全部的Slave, 當有Slave加入,或者有Slave宕機,Master都須要感知到這個事情,而後做出對應的調整,以便不影響整個集羣對外提供服務。以HBase爲例,HMaster管理了全部的RegionServer,當有新的RegionServer加入的時候,HMaster須要分配一些Region到該RegionServer上去,讓其提供服務;當有RegionServer宕機時,HMaster須要將該RegionServer以前服務的Region都從新分配到當前正在提供服務的其它RegionServer上,以便不影響客戶端的正常訪問。下面是這種場景下使用ZooKeeper的基本步驟:
4. 簡單互斥鎖(Simple Lock)
咱們知識,在傳統的應用程序中,線程、進程的同步,均可以經過操做系統提供的機制來完成。可是在分佈式系統中,多個進程之間的同步,操做系統層面就無能爲力了。這時候就須要像ZooKeeper這樣的分佈式的協調(Coordination)服務來協助完成同步,下面是用ZooKeeper實現簡單的互斥鎖的步驟,這個能夠和線程間同步的mutex作類比來理解:
5. 互斥鎖(Simple Lock without Herd Effect)
上一節的例子中有一個問題,每次搶鎖都會有大量的進程去競爭,會形成羊羣效應(Herd Effect),爲了解決這個問題,咱們能夠經過下面的步驟來改進上述過程:
這裏須要補充一點,一般在分佈式系統中用ZooKeeper來作Leader Election(選主)就是經過上面的機制來實現的,這裏的持鎖者就是當前的「主」。
6. 讀寫鎖(Read/Write Lock)
咱們知道,讀寫鎖跟互斥鎖相比不一樣的地方是,它分紅了讀和寫兩種模式,多個讀能夠併發執行,但寫和讀、寫都互斥,不能同時執行行。利用ZooKeeper,在上面的基礎上,稍作修改也能夠實現傳統的讀寫鎖的語義,下面是基本的步驟:
7. 屏障(Barrier)
在分佈式系統中,屏障是這樣一種語義: 客戶端須要等待多個進程完成各自的任務,而後才能繼續往前進行下一步。下用是用ZooKeeper來實現屏障的基本步驟:
8. 雙屏障(Double Barrier)
雙屏障是這樣一種語義: 它能夠用來同步一個任務的開始和結束,當有足夠多的進程進入屏障後,纔開始執行任務;當全部的進程都執行完各自的任務後,屏障才撤銷。下面是用ZooKeeper來實現雙屏障的基本步驟:
綜上,Zookeeper 的更多知識,在深刻學習和使用以後,再作補充。