分布式環(huán)境中大多數(shù)服務是允許部分失敗,也允許數(shù)據(jù)不一致,但有些最基礎的服務是需要高可靠性,高一致性的,這些服務是其他分布式服務運轉(zhuǎn)的基礎,比如naming service、分布式lock等,這些分布式的基礎服務有以下要求:
對于這種有些挑戰(zhàn)CAP原則 的服務該如何設計,是一個挑戰(zhàn),也是一個不錯的研究課題,Apache的ZooKeeper也許給了我們一個不錯的答案。ZooKeeper是一個分布式的,開放源碼的分布式應用程序協(xié)調(diào)服務, 它暴露了一個簡單的原語集,分布式應用程序可以基于它實現(xiàn)同步服務,配置維護和命名服務等。關于ZooKeeper更多信息可以參見 官方文檔
ZooKeeper的基本使用
搭一個分布式的ZooKeeper環(huán)境比較簡單,基本步驟如下:
1)在各服務器安裝 ZooKeeper
下載ZooKeeper后在各服務器上進行解壓即可
tar -xzf zookeeper-3.2.2.tar.gz
2)配置集群環(huán)境
分別各服務器的zookeeper安裝目錄下創(chuàng)建名為zoo.cfg的配置文件,內(nèi)容填寫如下:
其中zoo1和zoo2分別對應集群中各服務器的機器名或ip,server.1和server.2中1和2分別對應各服務器的zookeeper id,id的設置方法為在dataDir配置的目錄下創(chuàng)建名為myid的文件,并把id作為其文件內(nèi)容即可,在本例中就分為設置為1和2。其他配置具體含 義可見官方文檔。
3)啟動集群環(huán)境
分別在各服務器下運行zookeeper啟動腳本
/home/admin/zookeeper-3.2.2/bin/zkServer.sh start
4)應用zookeeper
應用zookeeper可以在是shell中執(zhí)行命令,也可以在java或c中調(diào)用程序接口。
在shell中執(zhí)行命令,可運行以下命令:
bin/zkCli.sh -server 10.20.147.35:2181
其中 10.20.147.35為集群中任一臺機器的ip或機器名。執(zhí)行后可進入zookeeper的操作面板,具體如何操作可見官方文檔
在java中通過調(diào)用程序接口來應用zookeeper較為復雜一點,需要了解watch和callback等概念,不過試驗最簡單的CURD倒不需要這些,只需要使用ZooKeeper這個類即可,具體測試代碼如下:
以上代碼比較簡單,查看一下zooKeeper的api doc就知道如何使用了
ZooKeeper的實現(xiàn)機理
ZooKeeper的實現(xiàn)機理是我看過的開源框架中最復雜的,它的解決是分布式環(huán)境中的一致性問題,這個場景也決定了其實現(xiàn)的復雜性。看了兩三天的源碼還是有些摸不著頭腦,有些超出了我的能力,不過通過看文檔和其他高人寫的文章大致清楚它的原理和基本結(jié)構(gòu)。
1)ZooKeeper的基本原理
ZooKeeper是以Fast Paxos算法為基礎的,在前一篇 blog 中大致介紹了一下paxos,而沒有提到的是paxos存在活鎖的問題,也就是當有多個 proposer交錯提交時,有可能互相排斥導致沒有一個proposer能提交成功,而Fast Paxos作了一些優(yōu)化,通過選舉產(chǎn)生一個leader,只有l(wèi)eader才能提交propose,具體算法可見Fast Paxos 。因此,要想弄得ZooKeeper首先得對Fast Paxos有所了解。
2)ZooKeeper的基本運轉(zhuǎn)流程
ZooKeeper主要存在以下兩個流程:
選舉Leader過程中算法有很多,但要達到的選舉標準是一致的:
同步數(shù)據(jù)這個流程是ZooKeeper的精髓所在,并且就是Fast Paxos算法的具體實現(xiàn)。一個牛人畫了一個ZooKeeper數(shù)據(jù)流動圖,比較直觀地描述了ZooKeeper是如何同步數(shù)據(jù)的。

以上兩個核心流程我暫時還不能悟透其中的精髓,這也和我還沒有完全理解Fast Paxos算法有關,有待后續(xù)深入學習
ZooKeeper的應用領域
Tim在blog中提到了Paxos所能應用的幾個主要場景,包括database replication、naming service、config配置管理、access control list等等,這也是ZooKeeper可以應用的幾個主要場景。此外, ZooKeeper官方文檔中提到了幾個更為基礎的分布式應用,這也算是ZooKeeper的妙用吧
1)分布式Barrier
Barrier是一種控制和協(xié)調(diào)多個任務觸發(fā)次序的機制,簡單說來就是搞個閘門把欲執(zhí)行的任務給攔住,等所有任務都處于可以執(zhí)行的狀態(tài)時,才放開閘門。它的機理可以見下圖所示:

在單機上JDK提供了CyclicBarrier這個類來實現(xiàn)這個機制,但在分布式環(huán)境中JDK就無能為力了。在分布式里實現(xiàn)Barrer需要高一致性做保障,因此 ZooKeeper可以派上用場,所采取的方案就是用一個Node作為Barrer的實體,需要被Barrer的任務通過調(diào)用 exists()檢測這個Node的存在,當需要打開Barrier的時候,刪掉這個Node,ZooKeeper的watch機制會通知到各個任務可以 開始執(zhí)行。
2) 分布式 Queue
與 Barrier類似 分布式環(huán)境中 實現(xiàn)Queue也需要高一致性做保障, ZooKeeper提供了一個種簡單的方式, ZooKeeper通過一個Node來維護Queue的實體,用其children來存儲Queue的內(nèi)容,并且 ZooKeeper的create方法中提供了順序遞增的模式,會自動地在name后面加上一個遞增的數(shù)字來插入新元素。可以用其 children來構(gòu)建一個queue的數(shù)據(jù)結(jié)構(gòu),offer的時候使用create,take的時候按照children的順序刪除第一個即可。 ZooKeeper保障了各個server上數(shù)據(jù)是一致的,因此也就實現(xiàn)了一個 分布式 Queue。take和offer的實例代碼如下所示:
3)分布式lock
利用 ZooKeeper實現(xiàn) 分布式lock,主要是通過一個Node來代表一個Lock,當一個client去拿鎖的時候,會在這個Node下創(chuàng)建一個自增序列的 child,然后通過getChildren()方式來check創(chuàng)建的child是不是最靠前的,如果是則拿到鎖,否則就調(diào)用exist()來 check第二靠前的child,并加上watch來監(jiān)視。當拿到鎖的child執(zhí)行完后歸還鎖,歸還鎖僅僅需要刪除自己創(chuàng)建的child,這時 watch機制會通知到所有沒有拿到鎖的client,這些child就會根據(jù)前面所講的拿鎖規(guī)則來競爭鎖。
新聞熱點
疑難解答