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

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


咨詢(xún)
建站咨詢(xún)
