題外話:從字面上來看,ZooKeeper表示動物園管理員,而Hadoop生態系統中,許多項目的Logo都採用了動物,好比Hadoop採用了大象的形象,因此能夠ZooKeeper就是對這些動物進行一些管理工做的。html
對於單機環境進程內的協調方法,咱們通常經過線程鎖來協調對共享數據的訪問以保證狀態的一致性。 可是分佈式環境如何進行協調呢?因而,Google創造了Chubby,而ZooKeeper則是對於Chubby的一個開源實現。
ZooKeeper是一種爲分佈式應用所設計的高可用、高性能且一致的開源協調服務,它提供了一項基本服務:分佈式鎖服務
。因爲ZooKeeper的開源特性,後來咱們的開發者在分佈式鎖的基礎上,摸索了出了其餘的使用方法:配置維護、組服務、分佈式消息隊列、分佈式通知/協調
等。它被設計爲易於編程,使用文件系統目錄樹做爲數據模型。java
Zookeeper服務自身組成一個集羣(2n+1個服務容許n個失效)。Zookeeper服務有兩個角色,一個是leader,負責寫服務和數據同步,剩下的是follower,提供讀服務,leader失效後會在follower中從新選舉新的leader。
node
保證shell
順序一致性:按照客戶端發送請求的順序更新數據。數據庫
原子性:更新要麼成功,要麼失敗,不會出現部分更新。apache
單一性 :不管客戶端鏈接哪一個server,都會看到同一個視圖。編程
可靠性:一旦數據更新成功,將一直保持,直到新的更新。vim
及時性:客戶端會在一個肯定的時間內獲得最新的數據。api
一、Zookeeper數據模型Znode
Zookeeper表現爲一個分層的文件系統目錄樹結構(不一樣於文件系統的是,節點能夠有本身的數據,而文件系統中的目錄節點只有子節點)。
服務器
Zookeeper Stat 結構 —— Zookeeper中的每一個znode的stat都由下面的字段組成:
czxid - 引發這個znode建立的zxid
mzxid - znode最後更新的zxid
ctime - znode被建立的毫秒數(從1970年開始)
mtime - znode最後修改的毫秒數(從1970年開始)
version - znode數據變化號
cversion - znode子節點變化號
aversion - znode訪問控制列表的變化號
ephemeralOwner - 若是是臨時節點這個是znode擁有者的session id。若是不是臨時節點則是0。
dataLength - znode的數據長度
numChildren - znode子節點數量
關於數據模型的理解,建議參考:http://www.cnblogs.com/wuxl36...
簡單API——Zookeeper的設計目標的其中之一就是提供一個簡單的程序接口。所以,它只支持這些操做:
create - 在樹形結構的位置中建立節點
delete - 刪除一個節點
exists - 測試節點在指定位置上是否存在
get data - 從節點上讀取數據
set data - 往節點寫入數據
get chilren - 檢索節點的子節點列表
sync - 等待傳輸數據
(1)統一命名服務
分佈式應用中,一般須要有一套完整的命名規則,既可以產生惟一的名稱又便於人識別和記住,一般狀況下用樹形的名稱結構是一個理想的選擇,樹形的名稱結構是一個有層次的目錄結構,既對人友好又不會重複。說到這裏你可能想到了 JNDI,沒錯 Zookeeper 的 Name Service 與 JNDI 可以完成的功能是差很少的,它們都是將有層次的目錄結構關聯到必定資源上,可是 Zookeeper 的 Name Service 更加是普遍意義上的關聯,也許你並不須要將名稱關聯到特定資源上,你可能只須要一個不會重複名稱,就像數據庫中產生一個惟一的數字主鍵同樣。
Name Service 已是 Zookeeper 內置的功能,你只要調用 Zookeeper 的 API 就能實現。如調用 create 接口就能夠很容易建立一個目錄節點。
案例:有一組服務器向客戶端提供某種服務(例如:使用LVS技術構建的Web網站集羣,就是由N臺服務器組成的集羣,爲用戶提供Web服務)。對於這種場景,咱們的程序中必定有一份這組服務器的列表,每次客戶端請求時候,都是從這份列表裏讀取這份服務器列表。那麼這分列表顯然不能存儲在一臺單節點的服務器上,不然這個節點掛掉了,整個集羣都會發生故障,咱們但願這份列表時高可用的。高可用的解決方案是:這份列表是分佈式存儲的,它是由存儲這份列表的服務器共同管理的,若是存儲列表裏的某臺服務器壞掉了,其餘服務器立刻能夠替代壞掉的服務器,而且能夠把壞掉的服務器從列表裏刪除掉,讓故障服務器退出整個集羣的運行,而這一切的操做又不會由故障的服務器來操做,而是集羣里正常的服務器來完成。這是一種主動的分佈式數據結構,可以在外部狀況發生變化時候主動修改數據項狀態的數據機構。
(2)分佈式鎖服務
共享鎖在同一個進程中很容易實現,可是在跨進程或者在不一樣 Server 之間就很差實現了。Zookeeper 卻很容易實現這個功能,實現方式也是須要得到鎖的 Server 建立一個 EPHEMERAL_SEQUENTIAL 目錄節點,而後調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是否是就是本身建立的目錄節點,若是正是本身建立的,那麼它就得到了這個鎖,若是不是那麼它就調用exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到本身建立的節點是列表中最小編號的目錄節點,從而得到鎖,釋放鎖很簡單,只要刪除前面它本身所建立的目錄節點就好了。
具體步驟以下:
加鎖: ZooKeeper 將按照以下方式實現加鎖的操做:
1 ) ZooKeeper 調用 create ()方法來建立一個路徑格式爲「 _locknode_/lock- 」的節點,此節點類型爲sequence (連續)和 ephemeral (臨時)。也就是說,建立的節點爲臨時節點,而且全部的節點連續編號,即「 lock-i 」的格式。
2 )在建立的鎖節點上調用 getChildren ()方法,來獲取鎖目錄下的最小編號節點,而且不設置 watch 。
3 )步驟 2 中獲取的節點剛好是步驟 1 中客戶端建立的節點,那麼此客戶端得到此種類型的鎖,而後退出操做。
4 )客戶端在鎖目錄上調用 exists ()方法,而且設置 watch 來監視鎖目錄下比本身小一個的連續臨時節點的狀態。
5 )若是監視節點狀態發生變化,則跳轉到第 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(); } }
(3)配置管理(數據發佈與訂閱)
在分佈式系統裏,咱們會把一個服務應用分別部署到n臺服務器上,這些服務器的配置文件是相同的,若是配置文件的配置選項發生變化,那麼咱們就得一個個去改這些配置文件,若是咱們須要改的服務器比較少,這些操做還不是太麻煩,若是咱們分佈式的服務器特別多,那麼更改配置選項就是一件麻煩並且危險的事情。這時咱們能夠將配置信息保存在 Zookeeper 的某個目錄節點中,而後將全部須要修改的應用機器監控配置信息的狀態,一旦配置信息發生變化,每臺應用機器就會收到 Zookeeper 的通知,而後從 Zookeeper 獲取新的配置信息應用到系統中。
(4)集羣管理
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 容易出現單點故障的問題。
PS:關於Master的選舉,能夠參考:http://www.cnblogs.com/sundde...。
注意:ZooKeeper所提供的服務主要是經過:數據結構+原語(一些關於該數據結構的一些操做)+watcher機制,三個部分來實現的
(5)、隊列管理 Zookeeper 能夠處理兩種類型的隊列:
當一個隊列的成員都聚齊時,這個隊列纔可用,不然一直等待全部成員到達,這種是同步隊列。
隊列按照 FIFO 方式進行入隊和出隊操做,例如實現生產者和消費者模型。
A、同步隊列 用 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); } } } public void process(WatchedEvent event) { if(event.getPath().equals(root + "/start") && event.getType() == Event.EventType.NodeCreated){ System.out.println("獲得通知"); super.process(event); doAction(); } }
B、FIFO隊列: 實現的思路也很是簡單,就是在特定的目錄下建立 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; } } } }
注意:ZooKeeper服務器集羣規模不小於3個節點,要求各服務器之間系統時間要保持一致
;
一、下載解壓安裝後,修改環境變量:vim /etc/profile
增長一行:export ZOOKEEPER_HOME=/usr/local/zookeeper
修改PATH:export PATH=.:$HADOOP_HOME/bin:$ZOOKEEPER_HOME/bin:$JAVA_HOME/bin:$PATH
使配置生效:source /etc/profile
二、進入zookeeper的conf目錄下,修改文件名:mv zoo_sample.cfg zoo.cfg
編輯zoo.cfg:vim zoo.cfg
修改dataDir=/usr/local/zookeeper/data
新增server.0=hadoop-master:2888:3888
server.1=hadoop-slave1:2888:3888 server.2=hadoop-slave2:2888:3888
三、建立data文件夾,並建立myid文件:
新建data文件夾:mkdir /usr/local/zookeeper/data
新建myid文件:vim myid,並設置第一臺server爲0。
四、複製zookeeper目錄至其他兩臺服務器中:
scp /usr/local/zookeeper hadoop-slave1:/usr/local/
scp /usr/local/zookeeper hadoop-slave2:/usr/local/
五、複製環境變量配置文件至其他兩臺服務器中:
scp /etc/profile hadoop-slave1:/etc
scp /etc/profile hadoop-slave2:/etc六、在其他兩臺服務器中修改myid文件:設置爲1和2;
七、啓動ZooKeeper,分別在三個節點中執行命令:zkServer.sh start
八、檢驗ZooKeeper集羣節點角色狀態,分別在三個節點中執行命令:zkServer.sh status (能夠看出哪一個節點是leader,follower,observer等)
ZooKeeper中包含如下角色:
領導者(leader),負責進行投票的發起和決議,更新系統狀態;
學習者(learner),包括跟隨者(follower)和觀察者(observer),follower用於接受客戶端請求並向客戶端返回結果,在選主過程當中參與投票;observer能夠接受客戶端鏈接,將寫請求轉發給leader,但observer不參加投票過程,只同步leader的狀態,observer的目的是爲了擴展系統,提升讀取速度;
九、ZooKeeper簡單測試
搭建好集羣環境後,就能夠進行簡單的讀寫一致性測試了,這裏咱們經過進入zookeeper的bin目錄下的zkCli.sh來完成下面的操做:
(1)在其中一個節點192.168.80.100上執行一個寫操做:create /MyTest test
(2)在其餘兩個節點上執行讀操做:get /MyTest
TIP:能夠在一個節點中經過zkCli.sh -server hadoop-slave1:2181來遠程登陸
(3)在其中一個節點192.168.80.101上執行一個修改操做:set /MyTest new-test ,在其餘兩個節點上執行讀操做查看數據是否一致
[zkshell: 0] help ZooKeeper host:port cmd args get path [watch] ls path [watch] set path data [version] delquota [-n|-b] path quit printwatches on|off createpath data acl stat path [watch] listquota path history setAcl path acl getAcl path sync path redo cmdno addauth scheme auth delete path [version] setquota -n|-b val path
APIDOC: https://zookeeper.apache.org/...
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.6</version> </dependency>
Zookeeper主要用來解決分佈式集羣中應用系統的一致性問題,它能提供基於相似於文件系統的目錄節點樹方式的數據存儲,可是 Zookeeper 並非用來專門存儲數據的,它的做用主要是用來維護和監控你存儲的數據的狀態變化。經過監控這些數據狀態的變化,從而能夠達到基於數據的集羣管理
客戶端要鏈接 Zookeeper 服務器能夠經過建立 org.apache.zookeeper.ZooKeeper
的一個實例對象,而後調用這個類提供的接口來和服務器交互。
前面說了 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
Stat exists(String path,Watcher watcher) 重載方法,這裏給某個目錄節點設置特定的 watcher,Watcher 在 ZooKeeper 是一個核心功能,Watcher 能夠監控目錄節點的數據變化以及子目錄的變化,一旦這些狀態發生變化,服務器就會通知全部設置在這個目錄節點上的 Watcher,從而每一個客戶端都很快知道它所關注的目錄節點的狀態發生變化,而作出相應的反應
void delete(String path, int version) 刪除 path 對應的目錄節點,version 爲 -1 能夠匹配任何版本,也就刪除了這個目錄節點全部數據
List<String> getChildren(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<ACL> getACL(String path,Stat stat) 獲取某個目錄節點的訪問權限列表
// 建立一個與服務器的鏈接 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();
官網例子請看:http://zookeeper.majunwei.com...
其餘例子:
http://www.uml.org.cn/zjjs/20...
http://www.uml.org.cn/zjjs/20...
http://zookeeper.majunwei.com...
官方文檔:https://zookeeper.apache.org/...
小馬過河翻譯社,Zookeeper文檔中文版:http://zookeeper.majunwei.com...
周旭龍,ZooKeeper環境搭建:http://www.cnblogs.com/edison...
Zookeeper 的學習與運用:http://www.oschina.net/questi...
鄔興亮,Zookeeper隨筆系列:http://www.cnblogs.com/wuxl36...
ggjucheng,Zookeeper Api(java)入門與應用:http://www.cnblogs.com/ggjuch...
其餘推薦:
http://cailin.iteye.com/blog/...
http://www.uml.org.cn/wenzhan...