新聞中心
「文章較長,可以點(diǎn)贊,收藏再看!」

文章內(nèi)容會(huì)同步到個(gè)人網(wǎng)站上,方便閱讀:https://xiaoflyfish.cn/,(「可以訪問了!」)
基本介紹
Apache ZooKeeper 是由Apache Hadoop的子項(xiàng)目發(fā)展而來,為分布式應(yīng)用提供高效且可靠的分布式協(xié)調(diào)服務(wù)。
在解決分布式數(shù)據(jù)一致性方面,ZK沒有直接采用Paxos算法,而是采用了ZAB(ZooKeeper Atomic Broadcast)協(xié)議。
ZK可以提供諸如數(shù)據(jù)發(fā)布/訂閱、負(fù)載均衡、命名服務(wù)、分布式協(xié)調(diào)/通知,集群管理,Master選舉,分布式鎖,分布式隊(duì)列等功能。
「它具有以下特性:」
- 「順序一致性」:從一個(gè)客戶端發(fā)起的事務(wù)請(qǐng)求,最終都會(huì)嚴(yán)格按照其發(fā)起順序被應(yīng)用到 Zookeeper 中;
- 「原子性」:要么所有應(yīng)用,要么不應(yīng)用;不存在部分機(jī)器應(yīng)用了該事務(wù),而「另一部分沒有應(yīng)用」的情況;
- 「單一視圖」:所有客戶端看到的服務(wù)端數(shù)據(jù)模型都是一致的,無論客戶連接的是哪個(gè)ZK服務(wù)器;
- 「可靠性」:一旦服務(wù)端成功應(yīng)用了一個(gè)事務(wù),則其引起的改變會(huì)一直保留,直到被另外一個(gè)事務(wù)所更改;
- 「實(shí)時(shí)性」:一旦一個(gè)事務(wù)被成功應(yīng)用后,Zookeeper 可以保證客戶端立即可以讀取到這個(gè)事務(wù)變更后的最新狀態(tài)的數(shù)據(jù)(「一段時(shí)間」)。
數(shù)據(jù)模型
ZooKeeper 中的數(shù)據(jù)模型是一種樹形結(jié)構(gòu),非常像電腦中的文件系統(tǒng),有一個(gè)根文件夾,下面還有很多子文件夾。
- ZooKeeper的數(shù)據(jù)模型也具有一個(gè)固定的根節(jié)點(diǎn)(/),我們可以在根節(jié)點(diǎn)下創(chuàng)建子節(jié)點(diǎn),并在子節(jié)點(diǎn)下繼續(xù)創(chuàng)建下一級(jí)節(jié)點(diǎn)。
- ZooKeeper 樹中的每一層級(jí)用斜杠(/)分隔開,且只能用絕對(duì)路徑(如get /work/task)的方式查詢 ZooKeeper 節(jié)點(diǎn),而不能使用相對(duì)路徑。
「為什么 ZooKeeper 不能采用相對(duì)路徑查找節(jié)點(diǎn)呢?」
這是因?yàn)?ZooKeeper 大多是應(yīng)用場景是定位數(shù)據(jù)模型上的節(jié)點(diǎn),并在相關(guān)節(jié)點(diǎn)上進(jìn)行操作。
像這種查找與給定值相等的記錄問題最適合用散列來解決。
因此 ZooKeeper 在底層實(shí)現(xiàn)的時(shí)候,使用了一個(gè) hashtable,即 hashtableConcurrentHashMap nodes,用節(jié)點(diǎn)的完整路徑來作為 key 存儲(chǔ)節(jié)點(diǎn)數(shù)據(jù)。
這樣就大大提高了 ZooKeeper 的性能。
「節(jié)點(diǎn)類型」
ZooKeeper 中的數(shù)據(jù)節(jié)點(diǎn)也分為持久節(jié)點(diǎn)、臨時(shí)節(jié)點(diǎn)和有序節(jié)點(diǎn)三種類型:
1、持久節(jié)點(diǎn)
一旦將節(jié)點(diǎn)創(chuàng)建為持久節(jié)點(diǎn),該數(shù)據(jù)節(jié)點(diǎn)會(huì)一直存儲(chǔ)在 ZooKeeper 服務(wù)器上,即使創(chuàng)建該節(jié)點(diǎn)的客戶端與服務(wù)端的會(huì)話關(guān)閉了,該節(jié)點(diǎn)依然不會(huì)被刪除。如果我們想刪除持久節(jié)點(diǎn),就要顯式調(diào)用 delete 函數(shù)進(jìn)行刪除操作。
2、臨時(shí)節(jié)點(diǎn)
如果將節(jié)點(diǎn)創(chuàng)建為臨時(shí)節(jié)點(diǎn),那么該節(jié)點(diǎn)數(shù)據(jù)不會(huì)一直存儲(chǔ)在 ZooKeeper 服務(wù)器上。
當(dāng)創(chuàng)建該臨時(shí)節(jié)點(diǎn)的客戶端會(huì)話因超時(shí)或發(fā)生異常而關(guān)閉時(shí),該節(jié)點(diǎn)也相應(yīng)在 ZooKeeper 服務(wù)器上被刪除,同樣,我們可以像刪除持久節(jié)點(diǎn)一樣主動(dòng)刪除臨時(shí)節(jié)點(diǎn)。
在平時(shí)的開發(fā)中,我們可以利用臨時(shí)節(jié)點(diǎn)的這一特性來做服務(wù)器集群內(nèi)機(jī)器運(yùn)行情況的統(tǒng)計(jì),將集群設(shè)置為/servers節(jié)點(diǎn),并為集群下的每臺(tái)服務(wù)器創(chuàng)建一個(gè)臨時(shí)節(jié)點(diǎn)/servers/host,當(dāng)服務(wù)器下線時(shí)該節(jié)點(diǎn)自動(dòng)被刪除,最后統(tǒng)計(jì)臨時(shí)節(jié)點(diǎn)個(gè)數(shù)就可以知道集群中的運(yùn)行情況。
3、有序節(jié)點(diǎn)
節(jié)點(diǎn)有序是說在我們創(chuàng)建有序節(jié)點(diǎn)的時(shí)候,ZooKeeper 服務(wù)器會(huì)自動(dòng)使用一個(gè)單調(diào)遞增的數(shù)字作為后綴,追加到我們創(chuàng)建節(jié)點(diǎn)的后邊。
例如一個(gè)客戶端創(chuàng)建了一個(gè)路徑為 works/task-的有序節(jié)點(diǎn),那么 ZooKeeper 將會(huì)生成一個(gè)序號(hào)并追加到該節(jié)點(diǎn)的路徑后,最后該節(jié)點(diǎn)的路徑為works/task-1。
通過這種方式我們可以直觀的查看到節(jié)點(diǎn)的創(chuàng)建順序。
ZooKeeper 中的每個(gè)節(jié)點(diǎn)都維護(hù)有這些內(nèi)容:一個(gè)二進(jìn)制數(shù)組(byte data[]),用來存儲(chǔ)節(jié)點(diǎn)的數(shù)據(jù)、ACL 訪問控制信息、子節(jié)點(diǎn)數(shù)據(jù)(因?yàn)榕R時(shí)節(jié)點(diǎn)不允許有子節(jié)點(diǎn),所以其子節(jié)點(diǎn)字段為 null),除此之外每個(gè)數(shù)據(jù)節(jié)點(diǎn)還有一個(gè)記錄自身狀態(tài)信息的字段 stat。
「節(jié)點(diǎn)的狀態(tài)結(jié)構(gòu)」
執(zhí)行stat /zk_test,可以看到控制臺(tái)輸出了一些信息,這些就是節(jié)點(diǎn)狀態(tài)信息。
每一個(gè)節(jié)點(diǎn)都有一個(gè)自己的狀態(tài)屬性,記錄了節(jié)點(diǎn)本身的一些信息:
|
「狀態(tài)屬性」 |
「說明」 |
|
czxid |
數(shù)據(jù)節(jié)點(diǎn)創(chuàng)建時(shí)的事務(wù) ID |
|
ctime |
數(shù)據(jù)節(jié)點(diǎn)創(chuàng)建時(shí)的時(shí)間 |
|
mzxid |
數(shù)據(jù)節(jié)點(diǎn)最后一次更新時(shí)的事務(wù) ID |
|
mtime |
數(shù)據(jù)節(jié)點(diǎn)最后一次更新時(shí)的時(shí)間 |
|
pzxid |
數(shù)據(jù)節(jié)點(diǎn)的子節(jié)點(diǎn)最后一次被修改時(shí)的事務(wù) ID |
|
「cversion」 |
「子節(jié)點(diǎn)的版本」 |
|
「version」 |
「當(dāng)前節(jié)點(diǎn)數(shù)據(jù)的版本」 |
|
「aversion」 |
「節(jié)點(diǎn)的 ACL 的版本」 |
|
ephemeralOwner |
如果節(jié)點(diǎn)是臨時(shí)節(jié)點(diǎn),則表示創(chuàng)建該節(jié)點(diǎn)的會(huì)話的 SessionID;如果節(jié)點(diǎn)是持久節(jié)點(diǎn),則該屬性值為 0 |
|
dataLength |
數(shù)據(jù)內(nèi)容的長度 |
|
numChildren |
數(shù)據(jù)節(jié)點(diǎn)當(dāng)前的子節(jié)點(diǎn)個(gè)數(shù) |
「數(shù)據(jù)節(jié)點(diǎn)的版本」
在 ZooKeeper 中為數(shù)據(jù)節(jié)點(diǎn)引入了版本的概念,每個(gè)數(shù)據(jù)節(jié)點(diǎn)有 3 種類型的版本信息,對(duì)數(shù)據(jù)節(jié)點(diǎn)的任何更新操作都會(huì)引起版本號(hào)的變化。
ZooKeeper 的版本信息表示的是對(duì)節(jié)點(diǎn)數(shù)據(jù)內(nèi)容、子節(jié)點(diǎn)信息或者是 ACL 信息的修改次數(shù)。
數(shù)據(jù)存儲(chǔ)
從存儲(chǔ)位置上來說,事務(wù)日志和數(shù)據(jù)快照一樣,都存儲(chǔ)在本地磁盤上;而從業(yè)務(wù)角度來講,內(nèi)存數(shù)據(jù)就是我們創(chuàng)建數(shù)據(jù)節(jié)點(diǎn)、添加監(jiān)控等請(qǐng)求時(shí)直接操作的數(shù)據(jù)。
事務(wù)日志數(shù)據(jù)主要用于記錄本地事務(wù)性會(huì)話操作,用于 ZooKeeper 集群服務(wù)器之間的數(shù)據(jù)同步。
事務(wù)快照則是將內(nèi)存數(shù)據(jù)持久化到本地磁盤。
這里要注意的一點(diǎn)是,數(shù)據(jù)快照是每間隔一段時(shí)間才把內(nèi)存數(shù)據(jù)存儲(chǔ)到本地磁盤,因此數(shù)據(jù)并不會(huì)一直與內(nèi)存數(shù)據(jù)保持一致。
在單臺(tái) ZooKeeper 服務(wù)器運(yùn)行過程中因?yàn)楫惓6P(guān)閉時(shí),可能會(huì)出現(xiàn)數(shù)據(jù)丟失等情況。
「內(nèi)存數(shù)據(jù)」
ZooKeeper 的數(shù)據(jù)模型可以看作一棵樹形結(jié)構(gòu),而數(shù)據(jù)節(jié)點(diǎn)就是這棵樹上的葉子節(jié)點(diǎn)。
從數(shù)據(jù)存儲(chǔ)的角度看,ZooKeeper 的數(shù)據(jù)模型是存儲(chǔ)在內(nèi)存中的。
我們可以把 ZooKeeper 的數(shù)據(jù)模型看作是存儲(chǔ)在內(nèi)存中的數(shù)據(jù)庫,而這個(gè)數(shù)據(jù)庫不但存儲(chǔ)數(shù)據(jù)的節(jié)點(diǎn)信息,還存儲(chǔ)每個(gè)數(shù)據(jù)節(jié)點(diǎn)的 ACL 權(quán)限信息以及 stat 狀態(tài)信息等。
而在底層實(shí)現(xiàn)中,ZooKeeper 數(shù)據(jù)模型是通過 DataTree 類來定義的。
DataTree 類定義了一個(gè) ZooKeeper 數(shù)據(jù)的內(nèi)存結(jié)構(gòu)。
DataTree 的內(nèi)部定義類 nodes 節(jié)點(diǎn)類型、root 根節(jié)點(diǎn)信息、子節(jié)點(diǎn)的 WatchManager 監(jiān)控信息等數(shù)據(jù)模型中的相關(guān)信息。
可以說,一個(gè) DataTree 類定義了 ZooKeeper 內(nèi)存數(shù)據(jù)的邏輯結(jié)構(gòu)。
「事務(wù)日志」
為了整個(gè) ZooKeeper 集群中數(shù)據(jù)的一致性,Leader 服務(wù)器會(huì)向 ZooKeeper 集群中的其他角色服務(wù)發(fā)送數(shù)據(jù)同步信息,在接收到數(shù)據(jù)同步信息后, ZooKeeper 集群中的 Follow 和 Observer 服務(wù)器就會(huì)進(jìn)行數(shù)據(jù)同步。
而這兩種角色服務(wù)器所接收到的信息就是 Leader 服務(wù)器的事務(wù)日志。
在接收到事務(wù)日志后,并在本地服務(wù)器上執(zhí)行。這種數(shù)據(jù)同步的方式,避免了直接使用實(shí)際的業(yè)務(wù)數(shù)據(jù),減少了網(wǎng)絡(luò)傳輸?shù)拈_銷,提升了整個(gè) ZooKeeper 集群的執(zhí)行性能。
Watch機(jī)制
ZooKeeper 的客戶端可以通過 Watch 機(jī)制來訂閱當(dāng)服務(wù)器上某一節(jié)點(diǎn)的數(shù)據(jù)或狀態(tài)發(fā)生變化時(shí)收到相應(yīng)的通知;
「如何實(shí)現(xiàn):」
我們可以通過向 ZooKeeper 客戶端的構(gòu)造方法中傳遞 Watcher 參數(shù)的方式實(shí)現(xiàn):
new ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
上面代碼的意思是定義了一個(gè)了 ZooKeeper 客戶端對(duì)象實(shí)例,并傳入三個(gè)參數(shù):
- connectString 服務(wù)端地址
- sessionTimeout:超時(shí)時(shí)間
- Watcher:監(jiān)控事件
這個(gè) Watcher 將作為整個(gè) ZooKeeper 會(huì)話期間的上下文 ,一直被保存在客戶端 ZKWatchManager 的 defaultWatcher 中。
除此之外,ZooKeeper 客戶端也可以通過 getData、exists 和 getChildren 三個(gè)接口來向 ZooKeeper 服務(wù)器注冊(cè) Watcher,從而方便地在不同的情況下添加 Watch 事件:
getData(String path, Watcher watcher, Stat stat)
觸發(fā)通知的條件:
上圖中列出了客戶端在不同會(huì)話狀態(tài)下,相應(yīng)的在服務(wù)器節(jié)點(diǎn)所能支持的事件類型。
例如在客戶端連接服務(wù)端的時(shí)候,可以對(duì)數(shù)據(jù)節(jié)點(diǎn)的創(chuàng)建、刪除、數(shù)據(jù)變更、子節(jié)點(diǎn)的更新等操作進(jìn)行監(jiān)控。
「當(dāng)服務(wù)端某一節(jié)點(diǎn)發(fā)生數(shù)據(jù)變更操作時(shí),所有曾經(jīng)設(shè)置了該節(jié)點(diǎn)監(jiān)控事件的客戶端都會(huì)收到服務(wù)器的通知嗎?」
答案是否定的,Watch 事件的觸發(fā)機(jī)制取決于會(huì)話的連接狀態(tài)和客戶端注冊(cè)事件的類型,所以當(dāng)客戶端會(huì)話狀態(tài)或數(shù)據(jù)節(jié)點(diǎn)發(fā)生改變時(shí),都會(huì)觸發(fā)對(duì)應(yīng)的 Watch 事件。
「訂閱發(fā)布場景實(shí)現(xiàn)」
提到 ZooKeeper 的應(yīng)用場景,你可能第一時(shí)間會(huì)想到最為典型的發(fā)布訂閱功能。
發(fā)布訂閱功能可以看作是一個(gè)一對(duì)多的關(guān)系,即一個(gè)服務(wù)或數(shù)據(jù)的發(fā)布者可以被多個(gè)不同的消費(fèi)者調(diào)用。
一般一個(gè)發(fā)布訂閱模式的數(shù)據(jù)交互可以分為消費(fèi)者主動(dòng)請(qǐng)求生產(chǎn)者信息的拉取模式,和生產(chǎn)者數(shù)據(jù)變更時(shí)主動(dòng)推送給消費(fèi)者的推送模式。
ZooKeeper 采用了兩種模式結(jié)合的方式實(shí)現(xiàn)訂閱發(fā)布功能。
下面我們來分析一個(gè)具體案例:
在系統(tǒng)開發(fā)的過程中會(huì)用到各種各樣的配置信息,如數(shù)據(jù)庫配置項(xiàng)、第三方接口、服務(wù)地址等,這些配置操作在我們開發(fā)過程中很容易完成,但是放到一個(gè)大規(guī)模的集群中配置起來就比較麻煩了。
通常這種集群中,我們可以用配置管理功能自動(dòng)完成服務(wù)器配置信息的維護(hù),利用ZooKeeper 的發(fā)布訂閱功能就能解決這個(gè)問題。
我們可以把諸如數(shù)據(jù)庫配置項(xiàng)這樣的信息存儲(chǔ)在 ZooKeeper 數(shù)據(jù)節(jié)點(diǎn)中。
如/confs/data_item1。
- 服務(wù)器集群客戶端對(duì)該節(jié)點(diǎn)添加 Watch 事件監(jiān)控,當(dāng)集群中的服務(wù)啟動(dòng)時(shí),會(huì)讀取該節(jié)點(diǎn)數(shù)據(jù)獲取數(shù)據(jù)配置信息。
- 而當(dāng)該節(jié)點(diǎn)數(shù)據(jù)發(fā)生變化時(shí),ZooKeeper 服務(wù)器會(huì)發(fā)送 Watch 事件給各個(gè)客戶端,集群中的客戶端在接收到該通知后,重新讀取節(jié)點(diǎn)的數(shù)據(jù)庫配置信息。
我們使用 Watch 機(jī)制實(shí)現(xiàn)了一個(gè)分布式環(huán)境下的配置管理功能,通過對(duì) ZooKeeper 服務(wù)器節(jié)點(diǎn)添加數(shù)據(jù)變更事件,實(shí)現(xiàn)當(dāng)數(shù)據(jù)庫配置項(xiàng)信息變更后,集群中的各個(gè)客戶端能接收到該變更事件的通知,并獲取最新的配置信息。
要注意一點(diǎn)是,我們提到 Watch 具有一次性,所以當(dāng)我們獲得服務(wù)器通知后要再次添加 Watch 事件。
會(huì)話機(jī)制
ZooKeeper 的工作方式一般是通過客戶端向服務(wù)端發(fā)送請(qǐng)求而實(shí)現(xiàn)的。
而在一個(gè)請(qǐng)求的發(fā)送過程中,首先,客戶端要與服務(wù)端進(jìn)行連接,而一個(gè)連接就是一個(gè)會(huì)話。
在 ZooKeeper 中,一個(gè)會(huì)話可以看作是一個(gè)用于表示客戶端與服務(wù)器端連接的數(shù)據(jù)結(jié)構(gòu) Session。
這個(gè)數(shù)據(jù)結(jié)構(gòu)由三個(gè)部分組成:分別是會(huì)話 ID(sessionID)、會(huì)話超時(shí)時(shí)間(TimeOut)、會(huì)話關(guān)閉狀態(tài)(isClosing)
- 會(huì)話 ID:會(huì)話 ID 作為一個(gè)會(huì)話的標(biāo)識(shí)符,當(dāng)我們創(chuàng)建一次會(huì)話的時(shí)候,ZooKeeper 會(huì)自動(dòng)為其分配一個(gè)唯一的 ID 編碼。
- 會(huì)話超時(shí)時(shí)間:一般來說,一個(gè)會(huì)話的超時(shí)時(shí)間就是指一次會(huì)話從發(fā)起后到被服務(wù)器關(guān)閉的時(shí)長。而設(shè)置會(huì)話超時(shí)時(shí)間后,服務(wù)器會(huì)參考設(shè)置的超時(shí)時(shí)間,最終計(jì)算一個(gè)服務(wù)端自己的超時(shí)時(shí)間。而這個(gè)超時(shí)時(shí)間則是最終真正用于 ZooKeeper 中服務(wù)端用戶會(huì)話管理的超時(shí)時(shí)間。
- 會(huì)話關(guān)閉狀態(tài):會(huì)話關(guān)閉 isClosing 狀態(tài)屬性字段表示一個(gè)會(huì)話是否已經(jīng)關(guān)閉。如果服務(wù)器檢查到一個(gè)會(huì)話已經(jīng)因?yàn)槌瑫r(shí)等原因失效時(shí), ZooKeeper 會(huì)在該會(huì)話的 isClosing 屬性值標(biāo)記為關(guān)閉,再之后就不對(duì)該會(huì)話進(jìn)行操作了。
「會(huì)話狀態(tài)」
在 ZooKeeper 服務(wù)的運(yùn)行過程中,會(huì)話會(huì)經(jīng)歷不同的狀態(tài)變化。
這些狀態(tài)包括:
正在連接(CONNECTING)、已經(jīng)連接(CONNECTIED)、正在重新連接(RECONNECTING)、已經(jīng)重新連接(RECONNECTED)、會(huì)話關(guān)閉(CLOSE)等。
當(dāng)客戶端開始創(chuàng)建一個(gè)與服務(wù)端的會(huì)話操作時(shí),它的會(huì)話狀態(tài)就會(huì)變成 CONNECTING,之后客戶端會(huì)根據(jù)服務(wù)器地址列表中的服務(wù)器 IP 地址分別嘗試進(jìn)行連接。如果遇到一個(gè) IP 地址可以連接到服務(wù)器,那么客戶端會(huì)話狀態(tài)將變?yōu)?CONNECTIED。
如果因?yàn)榫W(wǎng)絡(luò)原因造成已經(jīng)連接的客戶端會(huì)話斷開時(shí),客戶端會(huì)重新嘗試連接服務(wù)端。而對(duì)應(yīng)的客戶端會(huì)話狀態(tài)又變成 CONNECTING ,直到該會(huì)話連接到服務(wù)端最終又變成 CONNECTIED。
在 ZooKeeper 服務(wù)的整個(gè)運(yùn)行過程中,會(huì)話狀態(tài)經(jīng)常會(huì)在 CONNECTING 與 CONNECTIED 之間進(jìn)行切換。
最后,當(dāng)出現(xiàn)超時(shí)或者客戶端主動(dòng)退出程序等情況時(shí),客戶端會(huì)話狀態(tài)則會(huì)變?yōu)?CLOSE 狀態(tài)。
「會(huì)話異常」
在 ZooKeeper 中,會(huì)話的超時(shí)異常包括客戶端 readtimeout 異常和服務(wù)器端 sessionTimeout 異常。
在我們平時(shí)的開發(fā)中,要明確這兩個(gè)異常的不同之處在于一個(gè)是發(fā)生在客戶端,而另一個(gè)是發(fā)生在服務(wù)端。
而對(duì)于那些對(duì) ZooKeeper 接觸不深的開發(fā)人員來說,他們常常踩坑的地方在于,雖然設(shè)置了超時(shí)間,但是在實(shí)際服務(wù)運(yùn)行的時(shí)候 ZooKeeper 并沒有按照設(shè)置的超時(shí)時(shí)間來管理會(huì)話。
這是因?yàn)?ZooKeeper 實(shí)際起作用的超時(shí)時(shí)間是通過客戶端和服務(wù)端協(xié)商決定。
ZooKeeper 客戶端在和服務(wù)端建立連接的時(shí)候,會(huì)提交一個(gè)客戶端設(shè)置的會(huì)話超時(shí)時(shí)間,而該超時(shí)時(shí)間會(huì)和服務(wù)端設(shè)置的最大超時(shí)時(shí)間和最小超時(shí)時(shí)間進(jìn)行比對(duì),如果正好在其允許的范圍內(nèi),則采用客戶端的超時(shí)時(shí)間管理會(huì)話。
如果大于或者小于服務(wù)端設(shè)置的超時(shí)時(shí)間,則采用服務(wù)端設(shè)置的值管理會(huì)話。
「分桶策略」
我們知道在 ZooKeeper 中為了保證一個(gè)會(huì)話的存活狀態(tài),客戶端需要向服務(wù)器周期性地發(fā)送心跳信息。
而客戶端所發(fā)送的心跳信息可以是一個(gè) ping 請(qǐng)求,也可以是一個(gè)普通的業(yè)務(wù)請(qǐng)求。
ZooKeeper 服務(wù)端接收請(qǐng)求后,會(huì)更新會(huì)話的過期時(shí)間,來保證會(huì)話的存活狀態(tài)。
所以在 ZooKeeper 的會(huì)話管理中,最主要的工作就是管理會(huì)話的過期時(shí)間。
ZooKeeper 中采用了獨(dú)特的會(huì)話管理方式來管理會(huì)話的過期時(shí)間。
在 ZooKeeper 中,會(huì)話將按照不同的時(shí)間間隔進(jìn)行劃分,超時(shí)時(shí)間相近的會(huì)話將被放在同一個(gè)間隔區(qū)間中,這種方式避免了 ZooKeeper 對(duì)每一個(gè)會(huì)話進(jìn)行檢查,而是采用分批次的方式管理會(huì)話。
這就降低了會(huì)話管理的難度,因?yàn)槊看涡∨康奶幚頃?huì)話過期也提高了會(huì)話處理的效率。
「ZooKeeper 這種會(huì)話管理的好處?」
ZooKeeper 這種分段的會(huì)話管理策略大大提高了計(jì)算會(huì)話過期的效率,如果是在一個(gè)實(shí)際生產(chǎn)環(huán)境中,一個(gè)大型的分布式系統(tǒng)往往具有很高的訪問量。
而 ZooKeeper 作為其中的組件,對(duì)外提供服務(wù)往往要承擔(dān)數(shù)千個(gè)客戶端的訪問,這其中就要對(duì)這幾千個(gè)會(huì)話進(jìn)行管理。
在這種場景下,要想通過對(duì)每一個(gè)會(huì)話進(jìn)行管理和檢查并不合適,所以采用將同一個(gè)時(shí)間段的會(huì)話進(jìn)行統(tǒng)一管理,這樣就大大提高了服務(wù)的運(yùn)行效率。
「底層實(shí)現(xiàn)」
ZooKeeper 底層實(shí)現(xiàn)的原理,核心的一點(diǎn)就是過期隊(duì)列這個(gè)數(shù)據(jù)結(jié)構(gòu)。所有會(huì)話過期的相關(guān)操作都是圍繞這個(gè)隊(duì)列進(jìn)行的。
可以說 ZooKeeper 底層就是采用這個(gè)隊(duì)列結(jié)構(gòu)來管理會(huì)話過期的。
「一個(gè)會(huì)話過期隊(duì)列是由若干個(gè) bucket 組成的?!?/p>
- bucket 是一個(gè)按照時(shí)間劃分的區(qū)間。
- 在 ZooKeeper 中,通常以 expirationInterval 為單位進(jìn)行時(shí)間區(qū)間的劃分,它是 ZooKeeper 分桶策略中用于劃分時(shí)間區(qū)間的最小單位。
- 在 ZooKeeper 中,一個(gè)過期隊(duì)列由不同的 bucket 組成。
- 每個(gè) bucket 中存放了在某一時(shí)間內(nèi)過期的會(huì)話。
將會(huì)話按照不同的過期時(shí)間段分別維護(hù)到過期隊(duì)列之后,在 ZooKeeper 服務(wù)運(yùn)行的過程中,具體的執(zhí)行過程如下圖所示。
首先,ZooKeeper 服務(wù)會(huì)開啟一個(gè)線程專門用來檢索過期隊(duì)列,找出要過期的 bucket,而 ZooKeeper 每次只會(huì)讓一個(gè) bucket 的會(huì)話過期,每當(dāng)要進(jìn)行會(huì)話過期操作時(shí),ZooKeeper 會(huì)喚醒一個(gè)處于休眠狀態(tài)的線程進(jìn)行會(huì)話過期操作,之后會(huì)按照上面介紹的操作檢索過期隊(duì)列,取出過期的會(huì)話后會(huì)執(zhí)行過期操作。
ACL權(quán)限
ZooKeeper的ACL可針對(duì)znodes設(shè)置相應(yīng)的權(quán)限信息。
一個(gè) ACL 權(quán)限設(shè)置通??梢苑譃?3 部分,分別是:權(quán)限模式(Scheme)、授權(quán)對(duì)象(ID)、權(quán)限信息(Permission)。
最終組成一條例如scheme:id:permission格式的 ACL 請(qǐng)求信息。
「權(quán)限模式:Scheme」
ZooKeeper 的權(quán)限驗(yàn)證方式大體分為兩種類型,一種是范圍驗(yàn)證,另外一種是口令驗(yàn)證。
范圍驗(yàn)證
所謂的范圍驗(yàn)證就是說 ZooKeeper 可以針對(duì)一個(gè) IP 或者一段 IP 地址授予某種權(quán)限。
比如我們可以讓一個(gè) IP 地址為ip:192.168.0.11的機(jī)器對(duì)服務(wù)器上的某個(gè)數(shù)據(jù)節(jié)點(diǎn)具有寫入的權(quán)限。
或者也可以通過ip:192.168.0.11/22給一段 IP 地址的機(jī)器賦權(quán)。
口令驗(yàn)證
可以理解為用戶名密碼的方式,這是我們最熟悉也是日常生活中經(jīng)常使用的模式,比如我們打開自己的電腦或者去銀行取錢都需要提供相應(yīng)的密碼。
在 ZooKeeper 中這種驗(yàn)證方式是 Digest 認(rèn)證,我們知道通過網(wǎng)絡(luò)傳輸相對(duì)來說并不安全,所以絕不通過明文在網(wǎng)絡(luò)發(fā)送密碼也是程序設(shè)計(jì)中很重要的原則之一,而 Digest 這種認(rèn)證方式首先在客戶端傳送username:password這種形式的權(quán)限表示符后,ZooKeeper 服務(wù)端會(huì)對(duì)密碼部分使用 SHA-1 和 BASE64 算法進(jìn)行加密,以保證安全性。
Super 權(quán)限模式
權(quán)限模式 Super 可以認(rèn)為是一種特殊的 Digest 認(rèn)證。
具有 Super 權(quán)限的客戶端可以對(duì) ZooKeeper 上的任意數(shù)據(jù)節(jié)點(diǎn)進(jìn)行任意操作。
下面這段代碼給出了 Digest 模式下客戶端的調(diào)用方式。
//創(chuàng)建節(jié)點(diǎn)
create /digest_node1
//設(shè)置digest權(quán)限驗(yàn)證
setAcl /digest_node1 digest:用戶名:base64格式密碼:rwadc
//查詢節(jié)點(diǎn)Acl權(quán)限
getAcl /digest_node1
//授權(quán)操作
addauth digest user:passwd
如果一個(gè)客戶端對(duì)服務(wù)器上的一個(gè)節(jié)點(diǎn)設(shè)置了只有它自己才能操作的權(quán)限,那么等這個(gè)客戶端下線或被刪除后。
對(duì)其創(chuàng)建的節(jié)點(diǎn)要想進(jìn)行修改應(yīng)該怎么做呢?
我們可以通過「super 模式」即超級(jí)管理員的方式刪除該節(jié)點(diǎn)或變更該節(jié)點(diǎn)的權(quán)限驗(yàn)證方式。
正因?yàn)椤竤uper 模式」有如此大的權(quán)限,我們?cè)谄綍r(shí)使用時(shí)也應(yīng)該更加謹(jǐn)慎。
world 模式
這種授權(quán)模式對(duì)應(yīng)于系統(tǒng)中的所有用戶,本質(zhì)上起不到任何作用。
設(shè)置了 world 權(quán)限模式系統(tǒng)中的所有用戶操作都可以不進(jìn)行權(quán)限驗(yàn)證。
「授權(quán)對(duì)象(ID)」
所謂的授權(quán)對(duì)象就是說我們要把權(quán)限賦予誰,而對(duì)應(yīng)于 4 種不同的權(quán)限模式來說,如果我們選擇采用 IP 方式,使用的授權(quán)對(duì)象可以是一個(gè) IP 地址或 IP 地址段;而如果使用 Digest 或 Super 方式,則對(duì)應(yīng)于一個(gè)用戶名。
如果是 World 模式,是授權(quán)系統(tǒng)中所有的用戶。
「權(quán)限信息(Permission)」
權(quán)限就是指我們可以在數(shù)據(jù)節(jié)點(diǎn)上執(zhí)行的操作種類,在 ZooKeeper 中已經(jīng)定義好的權(quán)限有 5 種:
- 數(shù)據(jù)節(jié)點(diǎn)(create)創(chuàng)建權(quán)限,授予權(quán)限的對(duì)象可以在數(shù)據(jù)節(jié)點(diǎn)下創(chuàng)建子節(jié)點(diǎn);
- 數(shù)據(jù)節(jié)點(diǎn)(wirte)更新權(quán)限,授予權(quán)限的對(duì)象可以更新該數(shù)據(jù)節(jié)點(diǎn);
- 數(shù)據(jù)節(jié)點(diǎn)(read)讀取權(quán)限,授予權(quán)限的對(duì)象可以讀取該節(jié)點(diǎn)的內(nèi)容以及子節(jié)點(diǎn)的信息;
- 數(shù)據(jù)節(jié)點(diǎn)(delete)刪除權(quán)限,授予權(quán)限的對(duì)象可以刪除該數(shù)據(jù)節(jié)點(diǎn)的子節(jié)點(diǎn);
- 數(shù)據(jù)節(jié)點(diǎn)(admin)管理者權(quán)限,授予權(quán)限的對(duì)象可以對(duì)該數(shù)據(jù)節(jié)點(diǎn)體進(jìn)行 ACL 權(quán)限設(shè)置。
需要注意的一點(diǎn)是,每個(gè)節(jié)點(diǎn)都有維護(hù)自身的 ACL 權(quán)限數(shù)據(jù),即使是該節(jié)點(diǎn)的子節(jié)點(diǎn)也是有自己的 ACL 權(quán)限而不是直接繼承其父節(jié)點(diǎn)的權(quán)限。
「實(shí)現(xiàn)自己的權(quán)限口控制」
雖然 ZooKeeper 自身的權(quán)限控制機(jī)制已經(jīng)做得很細(xì),但是它還是提供了一種權(quán)限擴(kuò)展機(jī)制來讓用戶實(shí)現(xiàn)自己的權(quán)限控制方式。
官方文檔中對(duì)這種機(jī)制的定義是 Pluggable ZooKeeper Authenication,意思是可插拔的授權(quán)機(jī)制,從名稱上我們可以看出它的靈活性。那么這種機(jī)制是如何實(shí)現(xiàn)的呢?
要想實(shí)現(xiàn)自定義的權(quán)限控制機(jī)制,最核心的一點(diǎn)是實(shí)現(xiàn) ZooKeeper 提供的權(quán)限控制器接口 AuthenticationProvider。
實(shí)現(xiàn)了自定義權(quán)限后,如何才能讓 ZooKeeper 服務(wù)端使用自定義的權(quán)限驗(yàn)證方式呢?
接下來就需要將自定義的權(quán)限控制注冊(cè)到 ZooKeeper 服務(wù)器中,而注冊(cè)的方式通常有兩種。
第一種是通過設(shè)置系統(tǒng)屬性來注冊(cè)自定義的權(quán)限控制器:
-Dzookeeper.authProvider.x=CustomAuthenticationProvider
另一種是在配置文件zoo.cfg中進(jìn)行配置:
authProvider.x=CustomAuthenticationProvider
「實(shí)現(xiàn)原理」
首先是封裝該請(qǐng)求的類型,之后將權(quán)限信息封裝到 request 中并發(fā)送給服務(wù)端。而服務(wù)器的實(shí)現(xiàn)比較復(fù)雜,首先分析請(qǐng)求類型是否是權(quán)限相關(guān)操作,之后根據(jù)不同的權(quán)限模式(scheme)調(diào)用不同的實(shí)現(xiàn)類驗(yàn)證權(quán)限最后存儲(chǔ)權(quán)限信息。
在授權(quán)接口中,值得注意的是會(huì)話的授權(quán)信息存儲(chǔ)在 ZooKeeper 服務(wù)端的內(nèi)存中,如果客戶端會(huì)話關(guān)閉,授權(quán)信息會(huì)被刪除。
下次連接服務(wù)器后,需要重新調(diào)用授權(quán)接口進(jìn)行授權(quán)。
序列化方式
在 ZooKeeper 中并沒有采用和 Java 一樣的序列化方式,而是采用了一個(gè) Jute 的序列解決方案作為 ZooKeeper 框架自身的序列化方式。
ZooKeeper 從最開始就采用 Jute 作為其序列化解決方案,直到其最新的版本依然沒有更改。
雖然 ZooKeeper 一直將 Jute 框架作為序列化解決方案,但這并不意味著 Jute 相對(duì)其他框架性能更好,反倒是 Apache Avro、Thrift 等框架在性能上優(yōu)于前者。
之所以 ZooKeeper 一直采用 Jute 作為序列化解決方案,主要是新老版本的兼容等問題。
「如何 使用 Jute 實(shí)現(xiàn)序列化」
如果我們要想將某個(gè)定義的類進(jìn)行序列化,首先需要該類實(shí)現(xiàn) Record 接口的 serilize 和 deserialize 方法,這兩個(gè)方法分別是序列化和反序列化方法。
下邊這段代碼給出了我們一般在 ZooKeeper 中進(jìn)行序列化的具體實(shí)現(xiàn):
首先,我們定義了一個(gè)test_jute類,為了能夠?qū)λM(jìn)行序列化,需要該test_jute類實(shí)現(xiàn) Record 接口,并在對(duì)應(yīng)的 serialize 序列化方法和 deserialize 反序列化方法中編輯具體的實(shí)現(xiàn)邏輯。
class test_jute implements Record{
private long ids;
private String name;
...
public void serialize(OutpurArchive a_,String tag){
...
}
public void deserialize(INputArchive a_,String tag){
...
}
}
在序列化方法 serialize 中,我們要實(shí)現(xiàn)的邏輯是,首先通過字符類型參數(shù) tag 傳遞標(biāo)記序列化標(biāo)識(shí)符,之后使用 writeLong 和 writeString 等方法分別將對(duì)象屬性字段進(jìn)行序列化。
public void serialize(OutpurArchive a_,String tag) throws ...{
a_.startRecord(this.tag);
a_.writeLong(ids,"ids");
a_.writeString(type,"name");
a_.endRecord(this,tag);
}
調(diào)用 derseralize 在實(shí)現(xiàn)反序列化的過程則與我們上邊說的序列化過程正好相反。
public void deserialize(INputArchive a_,String tag) throws {
a_.startRecord(tag);
ids = a_.readLong("ids");
name = a_.readString("name");
a_.endRecord(tag);
}
序列化和反序列化的實(shí)現(xiàn)邏輯編碼方式相對(duì)固定,首先通過 startRecord 開啟一段序列化操作,之后通過 writeLong、writeString 或 readLong、 readString 等方法執(zhí)行序列化或反序列化。
本例中只是實(shí)現(xiàn)了長整型和字符型的序列化和反序列化操作,除此之外 ZooKeeper 中的 Jute 框架還支持整數(shù)類型(Int)、布爾類型(Bool)、雙精度類型(Double)以及 Byte/Buffer 類型。
集群
「ZooKeeper集群模式的特點(diǎn)」
在 ZooKeeper 集群中將服務(wù)器分成 「Leader 、Follow 、Observer 三」種角色服務(wù)器,在集群運(yùn)行期間這三種服務(wù)器所負(fù)責(zé)的工作各不相同:
Leader 角色服務(wù)器負(fù)責(zé)管理集群中其他的服務(wù)器,是集群中工作的分配和調(diào)度者,既可以為客戶端提供寫服務(wù)又能提供讀服務(wù)。
Follow 服務(wù)器的主要工作是選舉出 Leader 服務(wù)器,在發(fā)生 Leader 服務(wù)器選舉的時(shí)候,系統(tǒng)會(huì)從 Follow 服務(wù)器之間根據(jù)多數(shù)投票原則,選舉出一個(gè) Follow 服務(wù)器作為新的 Leader 服務(wù)器,只能提供讀服務(wù)。
Observer 服務(wù)器則主要負(fù)責(zé)處理來自客戶端的獲取數(shù)據(jù)等請(qǐng)求,并不參與 Leader 服務(wù)器的選舉操作,也不會(huì)作為候選者被選舉為 Leader 服務(wù)器,只能提供讀服務(wù)。
在 ZooKeeper 集群接收到來自客戶端的會(huì)話請(qǐng)求操作后,首先會(huì)判斷該條請(qǐng)求是否是事務(wù)性的會(huì)話請(qǐng)求。
對(duì)于事務(wù)性的會(huì)話請(qǐng)求,ZooKeeper 集群服務(wù)端會(huì)將該請(qǐng)求統(tǒng)一轉(zhuǎn)發(fā)給 Leader 服務(wù)器進(jìn)行操作。
所謂事務(wù)性請(qǐng)求,是指 ZooKeeper 服務(wù)器執(zhí)行完該條會(huì)話請(qǐng)求后,是否會(huì)導(dǎo)致執(zhí)行該條會(huì)話請(qǐng)求的服務(wù)器的數(shù)據(jù)或狀態(tài)發(fā)生改變,進(jìn)而導(dǎo)致與其他集群中的服務(wù)器出現(xiàn)數(shù)據(jù)不一致的情況。
Leader 服務(wù)器內(nèi)部執(zhí)行該條事務(wù)性的會(huì)話請(qǐng)求后,再將數(shù)據(jù)同步給其他角色服務(wù)器,從而保證事務(wù)性會(huì)話請(qǐng)求的執(zhí)行順序,進(jìn)而保證整個(gè) ZooKeeper 集群的數(shù)據(jù)一致性。
在 ZooKeeper 集群的內(nèi)部實(shí)現(xiàn)中,是通過什么方法保證所有 ZooKeeper 集群接收到的事務(wù)性會(huì)話請(qǐng)求都能交給 Leader 服務(wù)器進(jìn)行處理的呢?
在 ZooKeeper 集群內(nèi)部,集群中除 Leader 服務(wù)器外的其他角色服務(wù)器接收到來自客戶端的事務(wù)性會(huì)話請(qǐng)求后,必須將該條會(huì)話請(qǐng)求轉(zhuǎn)發(fā)給 Leader 服務(wù)器進(jìn)行處理。
ZooKeeper 集群中的 Follow 和 Observer 服務(wù)器,都會(huì)檢查當(dāng)前接收到的會(huì)話請(qǐng)求是否是事務(wù)性的請(qǐng)求,如果是事務(wù)性的請(qǐng)求,那么就將該請(qǐng)求以 REQUEST 消息類型轉(zhuǎn)發(fā)給 Leader 服務(wù)器。
在 ZooKeeper集群中的服務(wù)器接收到該條消息后,會(huì)對(duì)該條消息進(jìn)行解析。
- 分析出該條消息所包含的原始客戶端會(huì)話請(qǐng)求。
- 之后將該條消息提交到自己的 Leader 服務(wù)器請(qǐng)求處理鏈中,開始進(jìn)行事務(wù)性的會(huì)話請(qǐng)求操作。
- 如果不是事務(wù)性請(qǐng)求,ZooKeeper 集群則交由 Follow 和 Observer 角色服務(wù)器處理該條會(huì)話請(qǐng)求,如查詢數(shù)據(jù)節(jié)點(diǎn)信息。
當(dāng)一個(gè)業(yè)務(wù)場景在查詢操作多而創(chuàng)建刪除等事務(wù)性操作少的情況下,ZooKeeper 集群的性能表現(xiàn)的就會(huì)很好。
如果是在極端情況下,ZooKeeper 集群只有事務(wù)性的會(huì)話請(qǐng)求而沒有查詢操作,那么 Follow 和 Observer 服務(wù)器就只能充當(dāng)一個(gè)請(qǐng)求轉(zhuǎn)發(fā)服務(wù)器的角色, 所有的會(huì)話的處理壓力都在 Leader 服務(wù)器。
在處理性能上整個(gè)集群服務(wù)器的瓶頸取決于 Leader 服務(wù)器的性能。
ZooKeeper 集群的作用只能保證在 Leader 節(jié)點(diǎn)崩潰的時(shí)候,重新選舉出 Leader 服務(wù)器保證系統(tǒng)的穩(wěn)定性。
這也是 ZooKeeper 設(shè)計(jì)的一個(gè)缺點(diǎn)。
「Leader選舉」
Leader 服務(wù)器的選舉操作主要發(fā)生在兩種情況下。
第一種就是 ZooKeeper 集群服務(wù)啟動(dòng)的時(shí)候,第二種就是在 ZooKeeper 集群中舊的 Leader 服務(wù)器失效時(shí),這時(shí) ZooKeeper 集群需要選舉出新的 Leader 服務(wù)器。
ZooKeeper 集群重新選舉 Leader 的過程只有 Follow 服務(wù)器參與工作。
服務(wù)器狀態(tài)
服務(wù)器具有四種狀態(tài),分別是LOOKING、FOLLOWING、LEADING、OBSERVING。
- 「LOOKING」:尋找Leader狀態(tài)。當(dāng)服務(wù)器處于該狀態(tài)時(shí),它會(huì)認(rèn)為當(dāng)前集群中沒有Leader,因此需要進(jìn)入Leader選舉狀態(tài)。
- 「FOLLOWING」:跟隨者狀態(tài)。表明當(dāng)前服務(wù)器角色是Follower。
- 「LEADING」:領(lǐng)導(dǎo)者狀態(tài)。表明當(dāng)前服務(wù)器角色是Leader。
- 「OBSERVING」:觀察者狀態(tài)。表明當(dāng)前服務(wù)器角色是Observer。
「事務(wù)ID(zxid)」
Zookeeper的狀態(tài)變化,都會(huì)由一個(gè)Zookeeper事務(wù)ID(ZXID)標(biāo)識(shí)。
寫入Zookeeper,會(huì)導(dǎo)致狀態(tài)變化,每次寫入都會(huì)導(dǎo)致ZXID發(fā)生變化。
ZXID由Leader統(tǒng)一分配,全局唯一,長度64位,遞增。
ZXID展示了所有的Zookeeper轉(zhuǎn)臺(tái)變更順序,每次變更都有一個(gè)唯一ZXID,如果zxid1小于zxid2,則說明zxid1的事務(wù)在zxid2的事務(wù)之前發(fā)生。
「選舉過程」
在 ZooKeeper 集群重新選舉 Leader 節(jié)點(diǎn)的過程中,主要可以分為 Leader 失效發(fā)現(xiàn)、重新選舉 Leader 、Follow 服務(wù)器角色變更、集群同步這幾個(gè)步驟。
Leader 失效發(fā)現(xiàn)
在 ZooKeeper 集群中,當(dāng) Leader 服務(wù)器失效時(shí),ZooKeeper 集群會(huì)重新選舉出新的 Leader 服務(wù)器。
在 ZooKeeper 集群中,探測 Leader 服務(wù)器是否存活的方式與保持客戶端活躍性的方法非常相似。
首先,F(xiàn)ollow 服務(wù)器會(huì)定期向 Leader 服務(wù)器發(fā)送 網(wǎng)絡(luò)請(qǐng)求,在接收到請(qǐng)求后,Leader 服務(wù)器會(huì)返回響應(yīng)數(shù)據(jù)包給 Follow 服務(wù)器,而在 Follow 服務(wù)器接收到 Leader 服務(wù)器的響應(yīng)后,如果判斷 Leader 服務(wù)器運(yùn)行正常,則繼續(xù)進(jìn)行數(shù)據(jù)同步和服務(wù)轉(zhuǎn)發(fā)等工作,反之,則進(jìn)行 Leader 服務(wù)器的重新選舉操作。
Leader重新選舉
當(dāng) Follow 服務(wù)器向 Leader 服務(wù)器發(fā)送狀態(tài)請(qǐng)求包后,如果沒有得到 Leader 服務(wù)器的返回信息,這時(shí),如果是集群中個(gè)別的 Follow 服務(wù)器發(fā)現(xiàn)返回錯(cuò)誤,并不會(huì)導(dǎo)致 ZooKeeper 集群立刻重新選舉 Leader 服務(wù)器,而是將該 Follow 服務(wù)器的狀態(tài)變更為 LOOKING 狀態(tài),并向網(wǎng)絡(luò)中發(fā)起投票,當(dāng) ZooKeeper 集群中有更多的機(jī)器發(fā)起投票,最后當(dāng)投票結(jié)果滿足多數(shù)原則的情況下。
ZooKeeper 會(huì)重新選舉出 Leader 服務(wù)器。
Follow 角色變更
在 ZooKeeper 集群中,F(xiàn)ollow 服務(wù)器作為 Leader 服務(wù)器的候選者,當(dāng)被選舉為 Leader 服務(wù)器之后,其在 ZooKeeper 集群中的 Follow 角色,也隨之發(fā)生改變。也就是要轉(zhuǎn)變?yōu)?Leader 服務(wù)器,并作為 ZooKeeper 集群中的 Leader 角色服務(wù)器對(duì)外提供服務(wù)。
集群同步數(shù)據(jù)
在 ZooKeeper 集群成功選舉 Leader 服務(wù)器,并且候選 Follow 服務(wù)器的角色變更后。
為避免在這期間導(dǎo)致的數(shù)據(jù)不一致問題,ZooKeeper 集群在對(duì)外提供服務(wù)之前,會(huì)通過 Leader 角色服務(wù)器管理同步其他角色服務(wù)器。
「底層實(shí)現(xiàn)」
首先,ZooKeeper 集群會(huì)先判斷 Leader 服務(wù)器是否失效,而判斷的方式就是 Follow 服務(wù)器向 Leader 服務(wù)器發(fā)送請(qǐng)求包,之后 Follow 服務(wù)器接收到響應(yīng)數(shù)據(jù)后,進(jìn)行解析,F(xiàn)ollow 服務(wù)器會(huì)根據(jù)返回的數(shù)據(jù),判斷 Leader 服務(wù)器的運(yùn)行狀態(tài),如果返回的是 LOOKING 關(guān)鍵字,表明與集群中 Leader 服務(wù)器無法正常通信。
- 之后,在 ZooKeeper 集群選舉 Leader 服務(wù)器時(shí),是通過 「FastLeaderElection」類實(shí)現(xiàn)的。
該類實(shí)現(xiàn)了 TCP 方式的通信連接,用于在 ZooKeeper 集群中與其他 Follow 服務(wù)器進(jìn)行協(xié)調(diào)溝通。
FastLeaderElection 類繼承了 Election 接口,定義其是用來進(jìn)行選舉的實(shí)現(xiàn)類。
- 而在其內(nèi)部,又定義了選舉通信相關(guān)的一些配置參數(shù),比如 finalizeWait 最終等待時(shí)間、最大通知間隔時(shí)間 maxNotificationInterval 等。
在選舉的過程中,首先調(diào)用 ToSend 函數(shù)向 ZooKeeper 集群中的其他角色服務(wù)器發(fā)送本機(jī)的投票信息,其他服務(wù)器在接收投票信息后,會(huì)對(duì)投票信息進(jìn)行有效性驗(yàn)證等操作,之后 ZooKeeper 集群統(tǒng)計(jì)投票信息,如果過半數(shù)的機(jī)器投票信息一致,則集群就重新選出新的 Leader 服務(wù)器。
這里我們要注意一個(gè)問題,那就是在重新選舉 Leader 服務(wù)器的過程中,ZooKeeper 集群理論上是無法進(jìn)行事務(wù)性的請(qǐng)求處理的。
因此,發(fā)送到 ZooKeeper 集群中的事務(wù)性會(huì)話會(huì)被掛起,暫時(shí)不執(zhí)行,等到選舉出新的 Leader 服務(wù)器后再進(jìn)行操作。
「Observer」
在 ZooKeeper 集群服務(wù)運(yùn)行的過程中,Observer 服務(wù)器與 Follow 服務(wù)器具有一個(gè)相同的功能,那就是負(fù)責(zé)處理來自客戶端的諸如查詢數(shù)據(jù)節(jié)點(diǎn)等非事務(wù)性的會(huì)話請(qǐng)求操作。
- 但與 Follow 服務(wù)器不同的是,Observer 不參與 Leader 服務(wù)器的選舉工作,也不會(huì)被選舉為 Leader 服務(wù)器。
在早期的 ZooKeeper 集群服務(wù)運(yùn)行過程中,只有 Leader 服務(wù)器和 Follow 服務(wù)器。
不過隨著 ZooKeeper 在分布式環(huán)境下的廣泛應(yīng)用,早期模式的設(shè)計(jì)缺點(diǎn)也隨之產(chǎn)生,主要帶來的問題有如下幾點(diǎn):
隨著集群規(guī)模的變大,集群處理寫入的性能反而下降。
- ZooKeeper 集群無法做到跨域部署。
其中最主要的問題在于,當(dāng) ZooKeeper 集群的規(guī)模變大,集群中 Follow 服務(wù)器數(shù)量逐漸增多的時(shí)候,ZooKeeper 處理創(chuàng)建數(shù)據(jù)節(jié)點(diǎn)等事務(wù)性請(qǐng)求操作的性能就會(huì)逐漸下降。
這是因?yàn)?ZooKeeper 集群在處理事務(wù)性請(qǐng)求操作時(shí),要在 ZooKeeper 集群中對(duì)該事務(wù)性的請(qǐng)求發(fā)起投票,只有超過半數(shù)的 Follow 服務(wù)器投票一致,才會(huì)執(zhí)行該條寫入操作。
正因如此,隨著集群中 Follow 服務(wù)器的數(shù)量越來越多,一次寫入等相關(guān)操作的投票也就變得越來越復(fù)雜,并且 Follow 服務(wù)器之間彼此的網(wǎng)絡(luò)通信也變得越來越耗時(shí),導(dǎo)致隨著 Follow 服務(wù)器數(shù)量的逐步增加,事務(wù)性的處理性能反而變得越來越低。
- 為了解決這一問題,在 ZooKeeper 3.6 版本后,ZooKeeper 集群中創(chuàng)建了一種新的服務(wù)器角色,即 Observer——觀察者角色服務(wù)器。
Observer 可以處理 ZooKeeper 集群中的非事務(wù)性請(qǐng)求,并且不參與 Leader 節(jié)點(diǎn)等投票相關(guān)的操作。
這樣既保證了 ZooKeeper 集群性能的擴(kuò)展性,又避免了因?yàn)檫^多的服務(wù)器參與投票相關(guān)的操作而影響 ZooKeeper 集群處理事務(wù)性會(huì)話請(qǐng)求的能力。
- 在實(shí)際部署的時(shí)候,因?yàn)?Observer 不參與 Leader 節(jié)點(diǎn)等操作,并不會(huì)像 Follow 服務(wù)器那樣頻繁的與 Leader 服務(wù)器進(jìn)行通信。
因此,可以將 Observer 服務(wù)器部署在不同的網(wǎng)絡(luò)區(qū)間中,這樣也不會(huì)影響整個(gè) ZooKeeper 集群的性能,也就是所謂的跨域部署。
「在我們?nèi)粘J褂?ZooKeeper 集群服務(wù)器的時(shí)候,集群中的機(jī)器個(gè)數(shù)應(yīng)該選擇奇數(shù)個(gè)?」
兩個(gè)原因:
在容錯(cuò)能力相同的情況下,奇數(shù)臺(tái)更節(jié)省資源
Zookeeper中 Leader 選舉算法采用了Zab協(xié)議。
Zab核心思想是當(dāng)多數(shù) Server 寫成功,則寫成功。
舉兩個(gè)例子:
- 假如zookeeper集群1 ,有3個(gè)節(jié)點(diǎn),3/2=1.5 , 即zookeeper想要正常對(duì)外提供服務(wù)(即leader選舉成功),至少需要2個(gè)節(jié)點(diǎn)是正常的。換句話說,3個(gè)節(jié)點(diǎn)的zookeeper集群,允許有一個(gè)節(jié)點(diǎn)宕機(jī)。
- 假如zookeeper集群2,有4個(gè)節(jié)點(diǎn),4/2=2 , 即zookeeper想要正常對(duì)外提供服務(wù)(即leader選舉成功),至少需要3個(gè)節(jié)點(diǎn)是正常的。換句話說,4個(gè)節(jié)點(diǎn)的zookeeper集群,也允許有一個(gè)節(jié)點(diǎn)宕機(jī)。
集群1與集群2都有 允許1個(gè)節(jié)點(diǎn)宕機(jī) 的容錯(cuò)能力,但是集群2比集群1多了1個(gè)節(jié)點(diǎn)。在相同容錯(cuò)能力的情況下,本著節(jié)約資源的原則,zookeeper集群的節(jié)點(diǎn)數(shù)維持奇數(shù)個(gè)更好一些。
防止由腦裂造成的集群不可用。
集群的腦裂通常是發(fā)生在節(jié)點(diǎn)之間通信不可達(dá)的情況下,集群會(huì)分裂成不同的小集群,小集群各自選出自己的master節(jié)點(diǎn),導(dǎo)致原有的集群出現(xiàn)多個(gè)master節(jié)點(diǎn)的情況,這就是腦裂。
下面舉例說一下為什么采用奇數(shù)臺(tái)節(jié)點(diǎn),就可以防止由于腦裂造成的服務(wù)不可用:
假如zookeeper集群有 5 個(gè)節(jié)點(diǎn),發(fā)生了腦裂,腦裂成了A、B兩個(gè)小集群:
- A :1個(gè)節(jié)點(diǎn) ,B :4個(gè)節(jié)點(diǎn)
- A :2個(gè)節(jié)點(diǎn), B :3個(gè)節(jié)點(diǎn)
可以看出,上面這兩種情況下,A、B中總會(huì)有一個(gè)小集群滿足 可用節(jié)點(diǎn)數(shù)量 > 總節(jié)點(diǎn)數(shù)量/2 。
所以zookeeper集群仍然能夠選舉出leader , 仍然能對(duì)外提供服務(wù),只不過是有一部分節(jié)點(diǎn)失效了而已。
假如zookeeper集群有4個(gè)節(jié)點(diǎn),同樣發(fā)生腦裂,腦裂成了A、B兩個(gè)小集群:
- A:1個(gè)節(jié)點(diǎn) , B:3個(gè)節(jié)點(diǎn)
- A:2個(gè)節(jié)點(diǎn) , B:2個(gè)節(jié)點(diǎn)
因?yàn)锳和B都是2個(gè)節(jié)點(diǎn),都不滿足 可用節(jié)點(diǎn)數(shù)量 > 總節(jié)點(diǎn)數(shù)量/2 的選舉條件, 所以此時(shí)zookeeper就徹底不能提供服務(wù)了。
ZAB協(xié)議
「ZAB 協(xié)議算法」
ZooKeeper 最核心的作用就是保證分布式系統(tǒng)的數(shù)據(jù)一致性,而無論是處理來自客戶端的會(huì)話請(qǐng)求時(shí),還是集群 Leader 節(jié)點(diǎn)發(fā)生重新選舉時(shí),都會(huì)產(chǎn)生數(shù)據(jù)不一致的情況。
為了解決這個(gè)問題,ZooKeeper 采用了 ZAB 協(xié)議算法。
ZAB 協(xié)議算法(Zookeeper Atomic Broadcast ,Zookeeper 原子廣播協(xié)議)是 ZooKeeper 專門設(shè)計(jì)用來解決集群最終一致性問題的算法,它的兩個(gè)核心功能點(diǎn)是崩潰恢復(fù)和原子廣播協(xié)議。
在整個(gè) ZAB 協(xié)議的底層實(shí)現(xiàn)中,ZooKeeper 集群主要采用主從模式的系統(tǒng)架構(gòu)方式來保證 ZooKeeper 集群系統(tǒng)的一致性。
當(dāng)接收到來自客戶端的事務(wù)性會(huì)話請(qǐng)求后,系統(tǒng)集群采用主服務(wù)器來處理該條會(huì)話請(qǐng)求,經(jīng)過主服務(wù)器處理的結(jié)果會(huì)通過網(wǎng)絡(luò)發(fā)送給集群中其他從節(jié)點(diǎn)服務(wù)器進(jìn)行數(shù)據(jù)同步操作。
以 ZooKeeper 集群為例,這個(gè)操作過程可以概括為:
當(dāng) ZooKeeper 集群接收到來自客戶端的事務(wù)性的會(huì)話請(qǐng)求后,集群中的其他 Follow 角色服務(wù)器會(huì)將該請(qǐng)求轉(zhuǎn)發(fā)給 Leader 角色服務(wù)器進(jìn)行處理。
當(dāng) Leader 節(jié)點(diǎn)服務(wù)器在處理完該條會(huì)話請(qǐng)求后,會(huì)將結(jié)果通過操作日志的方式同步給集群中的 Follow 角色服務(wù)器。
然后 Follow 角色服務(wù)器根據(jù)接收到的操作日志,在本地執(zhí)行相關(guān)的數(shù)據(jù)處理操作,最終完成整個(gè) ZooKeeper 集群對(duì)客戶端會(huì)話的處理工作。
「崩潰恢復(fù)」
當(dāng)集群中的 Leader 發(fā)生故障的時(shí)候,整個(gè)集群就會(huì)因?yàn)槿鄙?Leader 服務(wù)器而無法處理來自客戶端的事務(wù)性的會(huì)話請(qǐng)求。
因此,為了解決這個(gè)問題。在 ZAB 協(xié)議中也設(shè)置了處理該問題的崩潰恢復(fù)機(jī)制。
崩潰恢復(fù)機(jī)制是保證 ZooKeeper 集群服務(wù)高可用的關(guān)鍵。觸發(fā) ZooKeeper 集群執(zhí)行崩潰恢復(fù)的事件是集群中的 Leader 節(jié)點(diǎn)服務(wù)器發(fā)生了異常而無法工作,于是 Follow 服務(wù)器會(huì)通過投票來決定是否選出新的 Leader 節(jié)點(diǎn)服務(wù)器。
投票過程如下:
當(dāng)崩潰恢復(fù)機(jī)制開始的時(shí)候,整個(gè) ZooKeeper 集群的每臺(tái) Follow 服務(wù)器會(huì)發(fā)起投票,并同步給集群中的其他 Follow 服務(wù)器。
在接收到來自集群中的其他 Follow 服務(wù)器的投票信息后,集群中的每個(gè) Follow 服務(wù)器都會(huì)與自身的投票信息進(jìn)行對(duì)比,如果判斷新的投票信息更合適,則采用新的投票信息作為自己的投票信息。在集群中的投票信息還沒有達(dá)到超過半數(shù)原則的情況下,再進(jìn)行新一輪的投票,最終當(dāng)整個(gè) ZooKeeper 集群中的 Follow 服務(wù)器超過半數(shù)投出的結(jié)果相同的時(shí)候,就會(huì)產(chǎn)生新的 Leader 服務(wù)器。
選票結(jié)構(gòu):
以 Fast Leader Election 選舉的實(shí)現(xiàn)方式來講,如下圖所示,一個(gè)選票的整體結(jié)果可以分為一下六個(gè)部分:
- logicClock:用來記錄服務(wù)器的投票輪次。logicClock 會(huì)從 1 開始計(jì)數(shù),每當(dāng)該臺(tái)服務(wù)經(jīng)過一輪投票后,logicClock 的數(shù)值就會(huì)加 1 。
- state:用來標(biāo)記當(dāng)前服務(wù)器的狀態(tài)。在 ZooKeeper 集群中一臺(tái)服務(wù)器具有 LOOKING、FOLLOWING、LEADERING、OBSERVING 這四種狀態(tài)。
- self_id:用來表示當(dāng)前服務(wù)器的 ID 信息,該字段在 ZooKeeper 集群中主要用來作為服務(wù)器的身份標(biāo)識(shí)符。
- self_zxid:當(dāng)前服務(wù)器上所保存的數(shù)據(jù)的最大事務(wù) ID ,從 0 開始計(jì)數(shù)。
- vote_id:投票要被推舉的服務(wù)器的唯一 ID 。
- vote_zxid:被推舉的服務(wù)器上所保存的數(shù)據(jù)的最大事務(wù) ID ,從 0 開始計(jì)數(shù)。
當(dāng) ZooKeeper 集群需要重新選舉出新的 Leader 服務(wù)器的時(shí)候,就會(huì)根據(jù)上面介紹的投票信息內(nèi)容進(jìn)行對(duì)比,以找出最適合的服務(wù)器。
選票篩選
當(dāng)一臺(tái) Follow 服務(wù)器接收到網(wǎng)絡(luò)中的其他 Follow 服務(wù)器的投票信息后,是如何進(jìn)行對(duì)比來更新自己的投票信息的。
Follow 服務(wù)器進(jìn)行選票對(duì)比的過程,如下圖所示。
首先,會(huì)對(duì)比 logicClock 服務(wù)器的投票輪次,當(dāng) logicClock 相同時(shí),表明兩張選票處于相同的投票階段,并進(jìn)入下一階段,否則跳過。
接下來再對(duì)比vote_zxid被選舉的服務(wù)器 ID 信息,若接收到的外部投票信息中的 vote_zxid字段較大,則將自己的票中的vote_zxid與vote_myid更新為收到的票中的vote_zxid與vote_myid ,并廣播出去。
要是對(duì)比的結(jié)果相同,則繼續(xù)對(duì)比vote_myid被選舉服務(wù)器上所保存的最大事務(wù) ID ,若外部投票的vote_myid 比較大,則將自己的票中的 vote_myid更新為收到的票中的vote_myid 。
經(jīng)過這些對(duì)比和替換后,最終該臺(tái) Follow 服務(wù)器會(huì)產(chǎn)生新的投票信息,并在下一輪的投票中發(fā)送到 ZooKeeper 集群中。
「消息廣播」
在 Leader 節(jié)點(diǎn)服務(wù)器處理請(qǐng)求后,需要通知集群中的其他角色服務(wù)器進(jìn)行數(shù)據(jù)同步。ZooKeeper 集群采用消息廣播的方式發(fā)送通知。
ZooKeeper 集群使用原子廣播協(xié)議進(jìn)行消息發(fā)送,該協(xié)議的底層實(shí)現(xiàn)過程與二階段提交過程非常相似,如下圖所示。
當(dāng)要在集群中的其他角色服務(wù)器進(jìn)行數(shù)據(jù)同步的時(shí)候,Leader 服務(wù)器將該操作過程封裝成一個(gè) Proposal 提交事務(wù),并將其發(fā)送給集群中其他需要進(jìn)行數(shù)據(jù)同步的服務(wù)器。
當(dāng)這些服務(wù)器接收到 Leader 服務(wù)器的數(shù)據(jù)同步事務(wù)后,會(huì)將該條事務(wù)能否在本地正常執(zhí)行的結(jié)果反饋給 Leader 服務(wù)器,Leader 服務(wù)器在接收到其他 Follow 服務(wù)器的反饋信息后進(jìn)行統(tǒng)計(jì),判斷是否在集群中執(zhí)行本次事務(wù)操作。
這里請(qǐng)注意 ,與二階段提交過程不同(即需要集群中所有服務(wù)器都反饋可以執(zhí)行事務(wù)操作后,主服務(wù)器再次發(fā)送 commit 提交請(qǐng)求執(zhí)行數(shù)據(jù)變更) ,ZAB 協(xié)議算法省去了中斷的邏輯,當(dāng) ZooKeeper 集群中有超過一半的 Follow 服務(wù)器能夠正常執(zhí)行事務(wù)操作后,整個(gè) ZooKeeper 集群就可以提交 Proposal 事務(wù)了。
日志清理
「日志類型」
在 ZooKeeper 服務(wù)運(yùn)行的時(shí)候,一般會(huì)產(chǎn)生數(shù)據(jù)快照和日志文件,數(shù)據(jù)快照用于集群服務(wù)中的數(shù)據(jù)同步,而數(shù)據(jù)日志則記錄了 ZooKeeper 服務(wù)運(yùn)行的相關(guān)狀態(tài)信息。
其中,數(shù)據(jù)日志是我們?cè)谏a(chǎn)環(huán)境中需要定期維護(hù)和管理的文件。
「清理方案」
如上面所介紹的,面對(duì)生產(chǎn)系統(tǒng)中產(chǎn)生的日志,一般的維護(hù)操作是備份和清理。
備份是為了之后對(duì)系統(tǒng)的運(yùn)行情況進(jìn)行排查和優(yōu)化,而清理主要因?yàn)殡S著系統(tǒng)日志的增加,日志會(huì)逐漸占用系統(tǒng)的存儲(chǔ)空間,如果一直不進(jìn)行清理,可能耗盡系統(tǒng)的磁盤存儲(chǔ)空間,并最終影響服務(wù)的運(yùn)行。
「清理工具」
Corntab
首先,我們介紹的是 Linux corntab ,它是 Linux 系統(tǒng)下的軟件,可以自動(dòng)地按照我們?cè)O(shè)定的時(shí)間,周期性地執(zhí)行我們編寫的相關(guān)腳本。
crontab 定時(shí)腳本的方式相對(duì)靈活,可以按照我們的業(yè)務(wù)需求來設(shè)置處理日志的維護(hù)方式,比如這里我們希望定期清除 ZooKeeper 服務(wù)運(yùn)行的日志,而不想清除數(shù)據(jù)快照的文件,則可以通過腳本設(shè)置,達(dá)到只對(duì)數(shù)據(jù)日志文件進(jìn)行清理的目的。
PurgeTxnLog
ZooKeeper 自身還提供了 PurgeTxnLog 工具類,用來清理 snapshot 數(shù)據(jù)快照文件和系統(tǒng)日志。
PurgeTxnLog 清理方式和我們上面介紹的方式十分相似,也是通過定時(shí)腳本執(zhí)行任務(wù),唯一的不同是,上面提到在編寫日志清除 logsCleanWeek 的時(shí)候 ,我們使用的是原生 shell 腳本自己手動(dòng)編寫的數(shù)據(jù)日志清理邏輯,而使用 PurgeTxnLog 則可以在編寫清除腳本的時(shí)候調(diào)用 ZooKeeper 為我們提供的工具類完成日志清理工作。
如下面的代碼所示,首先,我們?cè)?usr/bin目錄下創(chuàng)建一個(gè) PurgeLogsClean 腳本。注意這里的腳本也是一個(gè) shell 文件。
在腳本中我們只需要編寫 PurgeTxnLog 類的調(diào)用程序,系統(tǒng)就會(huì)自動(dòng)通過 PurgeTxnLog 工具類為我們完成對(duì)應(yīng)日志文件的清理工作。
#!/bin/sh
java -cp "$CLASSPATH" org.apache.zookeeper.server.PurgeTxnLog
echo "清理完成"
PurgeTxnLog 方式與 crontab 相比,使用起來更加容易而且也更加穩(wěn)定安全,不過 crontab 方式更加靈活,我們可以根據(jù)不同的業(yè)務(wù)需求編寫自己的清理邏輯。
實(shí)現(xiàn)分布式鎖
分布式鎖的目的是保證在分布式部署的應(yīng)用集群中,多個(gè)服務(wù)在請(qǐng)求同一個(gè)方法或者同一個(gè)業(yè)務(wù)操作的情況下,對(duì)應(yīng)業(yè)務(wù)邏輯只能被一臺(tái)機(jī)器上的一個(gè)線程執(zhí)行,避免出現(xiàn)并發(fā)問題。
實(shí)現(xiàn)分布式鎖目前有三種流行方案,即基于數(shù)據(jù)庫、Redis、ZooKeeper 的方案
「方案一:」
使用節(jié)點(diǎn)中的存儲(chǔ)數(shù)據(jù)區(qū)域,ZK中節(jié)點(diǎn)存儲(chǔ)數(shù)據(jù)的大小不能超過1M,但是只是存放一個(gè)標(biāo)識(shí)是足夠的,線程獲得鎖時(shí),先檢查該標(biāo)識(shí)是否是無鎖標(biāo)識(shí),若是可修改為占用標(biāo)識(shí),使用完再恢復(fù)為無鎖標(biāo)識(shí)
「方案二:」
使用子節(jié)點(diǎn),每當(dāng)有線程來請(qǐng)求鎖的時(shí)候,便在鎖的節(jié)點(diǎn)下創(chuàng)建一個(gè)子節(jié)點(diǎn),子節(jié)點(diǎn)類型必須維護(hù)一個(gè)順序,對(duì)子節(jié)點(diǎn)的自增序號(hào)進(jìn)行排序,默認(rèn)總是最小的子節(jié)點(diǎn)對(duì)應(yīng)的線程獲得鎖,釋放鎖時(shí)刪除對(duì)應(yīng)子節(jié)點(diǎn)便可
「死鎖風(fēng)險(xiǎn):」
兩種方案其實(shí)都是可行的,但是使用鎖的時(shí)候一定要去規(guī)避死鎖
方案一看上去是沒問題的,用的時(shí)候設(shè)置標(biāo)識(shí),用完清除標(biāo)識(shí),但是要是持有鎖的線程發(fā)生了意外,釋放鎖的代碼無法執(zhí)行,鎖就無法釋放,其他線程就會(huì)一直等待鎖,相關(guān)同步代碼便無法執(zhí)行
方案二也存在這個(gè)問題,但方案二可以利用ZK的臨時(shí)順序節(jié)點(diǎn)來解決這個(gè)問題,只要線程發(fā)生了異常導(dǎo)致程序中斷,就會(huì)丟失與ZK的連接,ZK檢測到該鏈接斷開,就會(huì)自動(dòng)刪除該鏈接創(chuàng)建的臨時(shí)節(jié)點(diǎn),這樣就可以達(dá)到即使占用鎖的線程程序發(fā)生意外,也能保證鎖正常釋放的目的
「避免羊群效應(yīng)」
把鎖請(qǐng)求者按照后綴數(shù)字進(jìn)行排隊(duì),后綴數(shù)字小的鎖請(qǐng)求者先獲取鎖。
如果所有的鎖請(qǐng)求者都 watch 鎖持有者,當(dāng)代表鎖請(qǐng)求者的 znode 被刪除以后,所有的鎖請(qǐng)求者都會(huì)通知到,但是只有一個(gè)鎖請(qǐng)求者能拿到鎖。這就是羊群效應(yīng)。
為了避免羊群效應(yīng),每個(gè)鎖請(qǐng)求者 watch 它前面的鎖請(qǐng)求者。
每次鎖被釋放,只會(huì)有一個(gè)鎖請(qǐng)求者 會(huì)被通知到。
這樣做還讓鎖的分配具有公平性,鎖定的分配遵循先到先得的原則。
「用 ZooKeeper 實(shí)現(xiàn)分布式鎖的算法流程,根節(jié)點(diǎn)為 /lock:」
- 客戶端連接 ZooKeeper,并在/lock下創(chuàng)建臨時(shí)有序子節(jié)點(diǎn),第一個(gè)客戶端對(duì)應(yīng)的子節(jié)點(diǎn)為/lock/lock01/00000001,第二個(gè)為 /lock/lock01/00000002;
- 其他客戶端獲取/lock01下的子節(jié)點(diǎn)列表,判斷自己創(chuàng)建的子節(jié)點(diǎn)是否為當(dāng)前列表中序號(hào)最小的子節(jié)點(diǎn);
- 如果是則認(rèn)為獲得鎖,執(zhí)行業(yè)務(wù)代碼,否則通過 watch 事件監(jiān)聽/lock01的子節(jié)點(diǎn)變更消息,獲得變更通知后重復(fù)此步驟直至獲得鎖;
- 完成業(yè)務(wù)流程后,刪除對(duì)應(yīng)的子節(jié)點(diǎn),釋放分布式鎖;
在實(shí)際開發(fā)中,可以應(yīng)用 Apache Curator 來快速實(shí)現(xiàn)分布式鎖,Curator 是 Netflix 公司開源的一個(gè) ZooKeeper 客戶端,對(duì) ZooKeeper 原生 API 做了抽象和封裝。
實(shí)現(xiàn)分布式ID
我們可以通過 ZooKeeper 自身的客戶端和服務(wù)器運(yùn)行模式,來實(shí)現(xiàn)一個(gè)分布式網(wǎng)絡(luò)環(huán)境下的 ID 請(qǐng)求和分發(fā)過程。
每個(gè)需要 ID 編碼的業(yè)務(wù)服務(wù)器可以看作是 ZooKeeper 的客戶端。ID 編碼生成器可以作為 ZooKeeper 的服務(wù)端。
客戶端通過發(fā)送請(qǐng)求到 ZooKeeper 服務(wù)器,來獲取編碼信息,服務(wù)端接收到請(qǐng)求后,發(fā)送 ID 編碼給客戶端。
「實(shí)現(xiàn)原理:」
可以利用 ZooKeeper 數(shù)據(jù)模型中的順序節(jié)點(diǎn)作為 ID 編碼。
- 客戶端通過調(diào)用 create 函數(shù)創(chuàng)建順序節(jié)點(diǎn)。服務(wù)器成功創(chuàng)建節(jié)點(diǎn)后,會(huì)響應(yīng)客戶端請(qǐng)求,把創(chuàng)建好的節(jié)點(diǎn)信息發(fā)送給客戶端。
- 客戶端用數(shù)據(jù)節(jié)點(diǎn)名稱作為 ID 編碼,進(jìn)行之后的本地業(yè)務(wù)操作。
- 利用 ZooKeeper 中的順序節(jié)點(diǎn)特性,很容易使我們創(chuàng)建的 ID 編碼具有有序的特性。并且我們也可以通過客戶端傳遞節(jié)點(diǎn)的名稱,根據(jù)不同的業(yè)務(wù)編碼區(qū)分不同的業(yè)務(wù)系統(tǒng),從而使編碼的擴(kuò)展能力更強(qiáng)。
雖然使用 ZooKeeper 的實(shí)現(xiàn)方式有這么多優(yōu)點(diǎn),但也會(huì)有一些潛在的問題。
其中最主要的是,在定義編碼的規(guī)則上還是強(qiáng)烈依賴于程序員自身的能力和對(duì)業(yè)務(wù)的深入理解。
很容易出現(xiàn)因?yàn)榭紤]不周,造成設(shè)置的規(guī)則在運(yùn)行一段時(shí)間后,無法滿足業(yè)務(wù)要求或者安全性不夠等問題。
實(shí)現(xiàn)負(fù)載均衡
「常見負(fù)載均衡算法」
輪詢法
輪詢法是最為簡單的負(fù)載均衡算法,當(dāng)接收到來自網(wǎng)絡(luò)中的客戶端請(qǐng)求后,負(fù)載均衡服務(wù)器會(huì)按順序逐個(gè)分配給后端服務(wù)。
比如集群中有 3 臺(tái)服務(wù)器,分別是 server1、server2、server3,輪詢法會(huì)按照 sever1、server2、server3 這個(gè)順序依次分發(fā)會(huì)話請(qǐng)求給每個(gè)服務(wù)器。當(dāng)?shù)谝淮屋喸兘Y(jié)束后,會(huì)重新開始下一輪的循環(huán)。
隨機(jī)法
隨機(jī)算法是指負(fù)載均衡服務(wù)器在接收到來自客戶端的請(qǐng)求后,會(huì)根據(jù)一定的隨機(jī)算法選中后臺(tái)集群中的一臺(tái)服務(wù)器來處理這次會(huì)話請(qǐng)求。
不過,當(dāng)集群中備選機(jī)器變的越來越多時(shí),通過統(tǒng)計(jì)學(xué)我們可以知道每臺(tái)機(jī)器被抽中的概率基本相等,因此隨機(jī)算法的實(shí)際效果越來越趨近輪詢算法。
原地址哈希法
原地址哈希算法的核心思想是根據(jù)客戶端的 IP 地址進(jìn)行哈希計(jì)算,用計(jì)算結(jié)果進(jìn)行取模后,根據(jù)最終結(jié)果選擇服務(wù)器地址列表中的一臺(tái)機(jī)器,處理該條會(huì)話請(qǐng)求。
采用這種算法后,當(dāng)同一 IP 的客戶端再次訪問服務(wù)端后,負(fù)載均衡服務(wù)器最終選舉的還是上次處理該臺(tái)機(jī)器會(huì)話請(qǐng)求的服務(wù)器,也就是每次都會(huì)分配同一臺(tái)服務(wù)器給客戶端。
加權(quán)輪詢法
加權(quán)輪詢的方式與輪詢算法的方式很相似,唯一的不同在于選擇機(jī)器的時(shí)候,不只是單純按照順序的方式選擇,還根據(jù)機(jī)器的配置和性能高低有所側(cè)重,配置性
本文名稱:一篇學(xué)會(huì)ZooKeeper核心
文章轉(zhuǎn)載:http://www.dlmjj.cn/article/ccscspe.html


咨詢
建站咨詢
