av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

一篇學(xué)會ZooKeeper核心

「文章較長,可以點贊,收藏再看!」

文章內(nèi)容會同步到個人網(wǎng)站上,方便閱讀:https://xiaoflyfish.cn/,(「可以訪問了!」)

基本介紹

Apache ZooKeeper 是由Apache Hadoop的子項目發(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選舉,分布式鎖,分布式隊列等功能。

「它具有以下特性:」

  • 「順序一致性」:從一個客戶端發(fā)起的事務(wù)請求,最終都會嚴(yán)格按照其發(fā)起順序被應(yīng)用到 Zookeeper 中;
  • 「原子性」:要么所有應(yīng)用,要么不應(yīng)用;不存在部分機(jī)器應(yīng)用了該事務(wù),而「另一部分沒有應(yīng)用」的情況;
  • 「單一視圖」:所有客戶端看到的服務(wù)端數(shù)據(jù)模型都是一致的,無論客戶連接的是哪個ZK服務(wù)器;
  • 「可靠性」:一旦服務(wù)端成功應(yīng)用了一個事務(wù),則其引起的改變會一直保留,直到被另外一個事務(wù)所更改;
  • 「實時性」:一旦一個事務(wù)被成功應(yīng)用后,Zookeeper 可以保證客戶端立即可以讀取到這個事務(wù)變更后的最新狀態(tài)的數(shù)據(jù)(「一段時間」)。

數(shù)據(jù)模型

ZooKeeper 中的數(shù)據(jù)模型是一種樹形結(jié)構(gòu),非常像電腦中的文件系統(tǒng),有一個根文件夾,下面還有很多子文件夾。

  • ZooKeeper的數(shù)據(jù)模型也具有一個固定的根節(jié)點(/),我們可以在根節(jié)點下創(chuàng)建子節(jié)點,并在子節(jié)點下繼續(xù)創(chuàng)建下一級節(jié)點。
  • ZooKeeper 樹中的每一層級用斜杠(/)分隔開,且只能用絕對路徑(如get /work/task)的方式查詢 ZooKeeper 節(jié)點,而不能使用相對路徑。

「為什么 ZooKeeper 不能采用相對路徑查找節(jié)點呢?」

這是因為 ZooKeeper 大多是應(yīng)用場景是定位數(shù)據(jù)模型上的節(jié)點,并在相關(guān)節(jié)點上進(jìn)行操作。

像這種查找與給定值相等的記錄問題最適合用散列來解決。

因此 ZooKeeper 在底層實現(xiàn)的時候,使用了一個 hashtable,即 hashtableConcurrentHashMap nodes,用節(jié)點的完整路徑來作為 key 存儲節(jié)點數(shù)據(jù)。

這樣就大大提高了 ZooKeeper 的性能。

「節(jié)點類型」

ZooKeeper 中的數(shù)據(jù)節(jié)點也分為持久節(jié)點、臨時節(jié)點和有序節(jié)點三種類型:

1、持久節(jié)點

一旦將節(jié)點創(chuàng)建為持久節(jié)點,該數(shù)據(jù)節(jié)點會一直存儲在 ZooKeeper 服務(wù)器上,即使創(chuàng)建該節(jié)點的客戶端與服務(wù)端的會話關(guān)閉了,該節(jié)點依然不會被刪除。如果我們想刪除持久節(jié)點,就要顯式調(diào)用 delete 函數(shù)進(jìn)行刪除操作。

2、臨時節(jié)點

如果將節(jié)點創(chuàng)建為臨時節(jié)點,那么該節(jié)點數(shù)據(jù)不會一直存儲在 ZooKeeper 服務(wù)器上。

當(dāng)創(chuàng)建該臨時節(jié)點的客戶端會話因超時或發(fā)生異常而關(guān)閉時,該節(jié)點也相應(yīng)在 ZooKeeper 服務(wù)器上被刪除,同樣,我們可以像刪除持久節(jié)點一樣主動刪除臨時節(jié)點。

在平時的開發(fā)中,我們可以利用臨時節(jié)點的這一特性來做服務(wù)器集群內(nèi)機(jī)器運(yùn)行情況的統(tǒng)計,將集群設(shè)置為/servers節(jié)點,并為集群下的每臺服務(wù)器創(chuàng)建一個臨時節(jié)點/servers/host,當(dāng)服務(wù)器下線時該節(jié)點自動被刪除,最后統(tǒng)計臨時節(jié)點個數(shù)就可以知道集群中的運(yùn)行情況。

3、有序節(jié)點

節(jié)點有序是說在我們創(chuàng)建有序節(jié)點的時候,ZooKeeper 服務(wù)器會自動使用一個單調(diào)遞增的數(shù)字作為后綴,追加到我們創(chuàng)建節(jié)點的后邊。

例如一個客戶端創(chuàng)建了一個路徑為 works/task-的有序節(jié)點,那么 ZooKeeper 將會生成一個序號并追加到該節(jié)點的路徑后,最后該節(jié)點的路徑為works/task-1。

通過這種方式我們可以直觀的查看到節(jié)點的創(chuàng)建順序。

ZooKeeper 中的每個節(jié)點都維護(hù)有這些內(nèi)容:一個二進(jìn)制數(shù)組(byte data[]),用來存儲節(jié)點的數(shù)據(jù)、ACL 訪問控制信息、子節(jié)點數(shù)據(jù)(因為臨時節(jié)點不允許有子節(jié)點,所以其子節(jié)點字段為 null),除此之外每個數(shù)據(jù)節(jié)點還有一個記錄自身狀態(tài)信息的字段 stat。

「節(jié)點的狀態(tài)結(jié)構(gòu)」

執(zhí)行stat /zk_test,可以看到控制臺輸出了一些信息,這些就是節(jié)點狀態(tài)信息。

每一個節(jié)點都有一個自己的狀態(tài)屬性,記錄了節(jié)點本身的一些信息:

「狀態(tài)屬性」

「說明」

czxid

數(shù)據(jù)節(jié)點創(chuàng)建時的事務(wù) ID

ctime

數(shù)據(jù)節(jié)點創(chuàng)建時的時間

mzxid

數(shù)據(jù)節(jié)點最后一次更新時的事務(wù) ID

mtime

數(shù)據(jù)節(jié)點最后一次更新時的時間

pzxid

數(shù)據(jù)節(jié)點的子節(jié)點最后一次被修改時的事務(wù) ID

「cversion」

「子節(jié)點的版本」

「version」

「當(dāng)前節(jié)點數(shù)據(jù)的版本」

「aversion」

「節(jié)點的 ACL 的版本」

ephemeralOwner

如果節(jié)點是臨時節(jié)點,則表示創(chuàng)建該節(jié)點的會話的 SessionID;如果節(jié)點是持久節(jié)點,則該屬性值為 0

dataLength

數(shù)據(jù)內(nèi)容的長度

numChildren

數(shù)據(jù)節(jié)點當(dāng)前的子節(jié)點個數(shù)

「數(shù)據(jù)節(jié)點的版本」

在 ZooKeeper 中為數(shù)據(jù)節(jié)點引入了版本的概念,每個數(shù)據(jù)節(jié)點有 3 種類型的版本信息,對數(shù)據(jù)節(jié)點的任何更新操作都會引起版本號的變化。

ZooKeeper 的版本信息表示的是對節(jié)點數(shù)據(jù)內(nèi)容、子節(jié)點信息或者是 ACL 信息的修改次數(shù)。

數(shù)據(jù)存儲

從存儲位置上來說,事務(wù)日志和數(shù)據(jù)快照一樣,都存儲在本地磁盤上;而從業(yè)務(wù)角度來講,內(nèi)存數(shù)據(jù)就是我們創(chuàng)建數(shù)據(jù)節(jié)點、添加監(jiān)控等請求時直接操作的數(shù)據(jù)。

事務(wù)日志數(shù)據(jù)主要用于記錄本地事務(wù)性會話操作,用于 ZooKeeper 集群服務(wù)器之間的數(shù)據(jù)同步。

事務(wù)快照則是將內(nèi)存數(shù)據(jù)持久化到本地磁盤。

這里要注意的一點是,數(shù)據(jù)快照是每間隔一段時間才把內(nèi)存數(shù)據(jù)存儲到本地磁盤,因此數(shù)據(jù)并不會一直與內(nèi)存數(shù)據(jù)保持一致。

在單臺 ZooKeeper 服務(wù)器運(yùn)行過程中因為異常而關(guān)閉時,可能會出現(xiàn)數(shù)據(jù)丟失等情況。

「內(nèi)存數(shù)據(jù)」

ZooKeeper 的數(shù)據(jù)模型可以看作一棵樹形結(jié)構(gòu),而數(shù)據(jù)節(jié)點就是這棵樹上的葉子節(jié)點。

從數(shù)據(jù)存儲的角度看,ZooKeeper 的數(shù)據(jù)模型是存儲在內(nèi)存中的。

我們可以把 ZooKeeper 的數(shù)據(jù)模型看作是存儲在內(nèi)存中的數(shù)據(jù)庫,而這個數(shù)據(jù)庫不但存儲數(shù)據(jù)的節(jié)點信息,還存儲每個數(shù)據(jù)節(jié)點的 ACL 權(quán)限信息以及 stat 狀態(tài)信息等。

而在底層實現(xiàn)中,ZooKeeper 數(shù)據(jù)模型是通過 DataTree 類來定義的。

DataTree 類定義了一個 ZooKeeper 數(shù)據(jù)的內(nèi)存結(jié)構(gòu)。

DataTree 的內(nèi)部定義類 nodes 節(jié)點類型、root 根節(jié)點信息、子節(jié)點的 WatchManager 監(jiān)控信息等數(shù)據(jù)模型中的相關(guān)信息。

可以說,一個 DataTree 類定義了 ZooKeeper 內(nèi)存數(shù)據(jù)的邏輯結(jié)構(gòu)。

「事務(wù)日志」

為了整個 ZooKeeper 集群中數(shù)據(jù)的一致性,Leader 服務(wù)器會向 ZooKeeper 集群中的其他角色服務(wù)發(fā)送數(shù)據(jù)同步信息,在接收到數(shù)據(jù)同步信息后, ZooKeeper 集群中的 Follow 和 Observer 服務(wù)器就會進(jìn)行數(shù)據(jù)同步。

而這兩種角色服務(wù)器所接收到的信息就是 Leader 服務(wù)器的事務(wù)日志。

在接收到事務(wù)日志后,并在本地服務(wù)器上執(zhí)行。這種數(shù)據(jù)同步的方式,避免了直接使用實際的業(yè)務(wù)數(shù)據(jù),減少了網(wǎng)絡(luò)傳輸?shù)拈_銷,提升了整個 ZooKeeper 集群的執(zhí)行性能。

Watch機(jī)制

ZooKeeper 的客戶端可以通過 Watch 機(jī)制來訂閱當(dāng)服務(wù)器上某一節(jié)點的數(shù)據(jù)或狀態(tài)發(fā)生變化時收到相應(yīng)的通知;

「如何實現(xiàn):」

我們可以通過向 ZooKeeper 客戶端的構(gòu)造方法中傳遞 Watcher 參數(shù)的方式實現(xiàn):

new ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)

上面代碼的意思是定義了一個了 ZooKeeper 客戶端對象實例,并傳入三個參數(shù):

  • connectString 服務(wù)端地址
  • sessionTimeout:超時時間
  • Watcher:監(jiān)控事件

這個 Watcher 將作為整個 ZooKeeper 會話期間的上下文 ,一直被保存在客戶端 ZKWatchManager 的 defaultWatcher 中。

除此之外,ZooKeeper 客戶端也可以通過 getData、exists 和 getChildren 三個接口來向 ZooKeeper 服務(wù)器注冊 Watcher,從而方便地在不同的情況下添加 Watch 事件:

getData(String path, Watcher watcher, Stat stat)

觸發(fā)通知的條件:

上圖中列出了客戶端在不同會話狀態(tài)下,相應(yīng)的在服務(wù)器節(jié)點所能支持的事件類型。

例如在客戶端連接服務(wù)端的時候,可以對數(shù)據(jù)節(jié)點的創(chuàng)建、刪除、數(shù)據(jù)變更、子節(jié)點的更新等操作進(jìn)行監(jiān)控。

「當(dāng)服務(wù)端某一節(jié)點發(fā)生數(shù)據(jù)變更操作時,所有曾經(jīng)設(shè)置了該節(jié)點監(jiān)控事件的客戶端都會收到服務(wù)器的通知嗎?」

答案是否定的,Watch 事件的觸發(fā)機(jī)制取決于會話的連接狀態(tài)和客戶端注冊事件的類型,所以當(dāng)客戶端會話狀態(tài)或數(shù)據(jù)節(jié)點發(fā)生改變時,都會觸發(fā)對應(yīng)的 Watch 事件。

「訂閱發(fā)布場景實現(xiàn)」

提到 ZooKeeper 的應(yīng)用場景,你可能第一時間會想到最為典型的發(fā)布訂閱功能。

發(fā)布訂閱功能可以看作是一個一對多的關(guān)系,即一個服務(wù)或數(shù)據(jù)的發(fā)布者可以被多個不同的消費者調(diào)用。

一般一個發(fā)布訂閱模式的數(shù)據(jù)交互可以分為消費者主動請求生產(chǎn)者信息的拉取模式,和生產(chǎn)者數(shù)據(jù)變更時主動推送給消費者的推送模式。

ZooKeeper 采用了兩種模式結(jié)合的方式實現(xiàn)訂閱發(fā)布功能。

下面我們來分析一個具體案例:

在系統(tǒng)開發(fā)的過程中會用到各種各樣的配置信息,如數(shù)據(jù)庫配置項、第三方接口、服務(wù)地址等,這些配置操作在我們開發(fā)過程中很容易完成,但是放到一個大規(guī)模的集群中配置起來就比較麻煩了。

通常這種集群中,我們可以用配置管理功能自動完成服務(wù)器配置信息的維護(hù),利用ZooKeeper 的發(fā)布訂閱功能就能解決這個問題。

我們可以把諸如數(shù)據(jù)庫配置項這樣的信息存儲在 ZooKeeper 數(shù)據(jù)節(jié)點中。

如/confs/data_item1。

  • 服務(wù)器集群客戶端對該節(jié)點添加 Watch 事件監(jiān)控,當(dāng)集群中的服務(wù)啟動時,會讀取該節(jié)點數(shù)據(jù)獲取數(shù)據(jù)配置信息。
  • 而當(dāng)該節(jié)點數(shù)據(jù)發(fā)生變化時,ZooKeeper 服務(wù)器會發(fā)送 Watch 事件給各個客戶端,集群中的客戶端在接收到該通知后,重新讀取節(jié)點的數(shù)據(jù)庫配置信息。

我們使用 Watch 機(jī)制實現(xiàn)了一個分布式環(huán)境下的配置管理功能,通過對 ZooKeeper 服務(wù)器節(jié)點添加數(shù)據(jù)變更事件,實現(xiàn)當(dāng)數(shù)據(jù)庫配置項信息變更后,集群中的各個客戶端能接收到該變更事件的通知,并獲取最新的配置信息。

要注意一點是,我們提到 Watch 具有一次性,所以當(dāng)我們獲得服務(wù)器通知后要再次添加 Watch 事件。

會話機(jī)制

ZooKeeper 的工作方式一般是通過客戶端向服務(wù)端發(fā)送請求而實現(xiàn)的。

而在一個請求的發(fā)送過程中,首先,客戶端要與服務(wù)端進(jìn)行連接,而一個連接就是一個會話。

在 ZooKeeper 中,一個會話可以看作是一個用于表示客戶端與服務(wù)器端連接的數(shù)據(jù)結(jié)構(gòu) Session。

這個數(shù)據(jù)結(jié)構(gòu)由三個部分組成:分別是會話 ID(sessionID)、會話超時時間(TimeOut)、會話關(guān)閉狀態(tài)(isClosing)

  • 會話 ID:會話 ID 作為一個會話的標(biāo)識符,當(dāng)我們創(chuàng)建一次會話的時候,ZooKeeper 會自動為其分配一個唯一的 ID 編碼。
  • 會話超時時間:一般來說,一個會話的超時時間就是指一次會話從發(fā)起后到被服務(wù)器關(guān)閉的時長。而設(shè)置會話超時時間后,服務(wù)器會參考設(shè)置的超時時間,最終計算一個服務(wù)端自己的超時時間。而這個超時時間則是最終真正用于 ZooKeeper 中服務(wù)端用戶會話管理的超時時間。
  • 會話關(guān)閉狀態(tài):會話關(guān)閉 isClosing 狀態(tài)屬性字段表示一個會話是否已經(jīng)關(guān)閉。如果服務(wù)器檢查到一個會話已經(jīng)因為超時等原因失效時, ZooKeeper 會在該會話的 isClosing 屬性值標(biāo)記為關(guān)閉,再之后就不對該會話進(jìn)行操作了。

「會話狀態(tài)」

在 ZooKeeper 服務(wù)的運(yùn)行過程中,會話會經(jīng)歷不同的狀態(tài)變化。

這些狀態(tài)包括:

正在連接(CONNECTING)、已經(jīng)連接(CONNECTIED)、正在重新連接(RECONNECTING)、已經(jīng)重新連接(RECONNECTED)、會話關(guān)閉(CLOSE)等。

當(dāng)客戶端開始創(chuàng)建一個與服務(wù)端的會話操作時,它的會話狀態(tài)就會變成 CONNECTING,之后客戶端會根據(jù)服務(wù)器地址列表中的服務(wù)器 IP 地址分別嘗試進(jìn)行連接。如果遇到一個 IP 地址可以連接到服務(wù)器,那么客戶端會話狀態(tài)將變?yōu)?CONNECTIED。

如果因為網(wǎng)絡(luò)原因造成已經(jīng)連接的客戶端會話斷開時,客戶端會重新嘗試連接服務(wù)端。而對應(yīng)的客戶端會話狀態(tài)又變成 CONNECTING ,直到該會話連接到服務(wù)端最終又變成 CONNECTIED。

在 ZooKeeper 服務(wù)的整個運(yùn)行過程中,會話狀態(tài)經(jīng)常會在 CONNECTING 與 CONNECTIED 之間進(jìn)行切換。

最后,當(dāng)出現(xiàn)超時或者客戶端主動退出程序等情況時,客戶端會話狀態(tài)則會變?yōu)?CLOSE 狀態(tài)。

「會話異?!?/h3>

在 ZooKeeper 中,會話的超時異常包括客戶端 readtimeout 異常和服務(wù)器端 sessionTimeout 異常。

在我們平時的開發(fā)中,要明確這兩個異常的不同之處在于一個是發(fā)生在客戶端,而另一個是發(fā)生在服務(wù)端。

而對于那些對 ZooKeeper 接觸不深的開發(fā)人員來說,他們常常踩坑的地方在于,雖然設(shè)置了超時間,但是在實際服務(wù)運(yùn)行的時候 ZooKeeper 并沒有按照設(shè)置的超時時間來管理會話。

這是因為 ZooKeeper 實際起作用的超時時間是通過客戶端和服務(wù)端協(xié)商決定。

ZooKeeper 客戶端在和服務(wù)端建立連接的時候,會提交一個客戶端設(shè)置的會話超時時間,而該超時時間會和服務(wù)端設(shè)置的最大超時時間和最小超時時間進(jìn)行比對,如果正好在其允許的范圍內(nèi),則采用客戶端的超時時間管理會話。

如果大于或者小于服務(wù)端設(shè)置的超時時間,則采用服務(wù)端設(shè)置的值管理會話。

「分桶策略」

我們知道在 ZooKeeper 中為了保證一個會話的存活狀態(tài),客戶端需要向服務(wù)器周期性地發(fā)送心跳信息。

而客戶端所發(fā)送的心跳信息可以是一個 ping 請求,也可以是一個普通的業(yè)務(wù)請求。

ZooKeeper 服務(wù)端接收請求后,會更新會話的過期時間,來保證會話的存活狀態(tài)。

所以在 ZooKeeper 的會話管理中,最主要的工作就是管理會話的過期時間。

ZooKeeper 中采用了獨特的會話管理方式來管理會話的過期時間。

在 ZooKeeper 中,會話將按照不同的時間間隔進(jìn)行劃分,超時時間相近的會話將被放在同一個間隔區(qū)間中,這種方式避免了 ZooKeeper 對每一個會話進(jìn)行檢查,而是采用分批次的方式管理會話。

這就降低了會話管理的難度,因為每次小批量的處理會話過期也提高了會話處理的效率。

「ZooKeeper 這種會話管理的好處?」

ZooKeeper 這種分段的會話管理策略大大提高了計算會話過期的效率,如果是在一個實際生產(chǎn)環(huán)境中,一個大型的分布式系統(tǒng)往往具有很高的訪問量。

而 ZooKeeper 作為其中的組件,對外提供服務(wù)往往要承擔(dān)數(shù)千個客戶端的訪問,這其中就要對這幾千個會話進(jìn)行管理。

在這種場景下,要想通過對每一個會話進(jìn)行管理和檢查并不合適,所以采用將同一個時間段的會話進(jìn)行統(tǒng)一管理,這樣就大大提高了服務(wù)的運(yùn)行效率。

「底層實現(xiàn)」

ZooKeeper 底層實現(xiàn)的原理,核心的一點就是過期隊列這個數(shù)據(jù)結(jié)構(gòu)。所有會話過期的相關(guān)操作都是圍繞這個隊列進(jìn)行的。

可以說 ZooKeeper 底層就是采用這個隊列結(jié)構(gòu)來管理會話過期的。

「一個會話過期隊列是由若干個 bucket 組成的。」

  • bucket 是一個按照時間劃分的區(qū)間。
  • 在 ZooKeeper 中,通常以 expirationInterval 為單位進(jìn)行時間區(qū)間的劃分,它是 ZooKeeper 分桶策略中用于劃分時間區(qū)間的最小單位。
  • 在 ZooKeeper 中,一個過期隊列由不同的 bucket 組成。
  • 每個 bucket 中存放了在某一時間內(nèi)過期的會話。

將會話按照不同的過期時間段分別維護(hù)到過期隊列之后,在 ZooKeeper 服務(wù)運(yùn)行的過程中,具體的執(zhí)行過程如下圖所示。

首先,ZooKeeper 服務(wù)會開啟一個線程專門用來檢索過期隊列,找出要過期的 bucket,而 ZooKeeper 每次只會讓一個 bucket 的會話過期,每當(dāng)要進(jìn)行會話過期操作時,ZooKeeper 會喚醒一個處于休眠狀態(tài)的線程進(jìn)行會話過期操作,之后會按照上面介紹的操作檢索過期隊列,取出過期的會話后會執(zhí)行過期操作。

ACL權(quán)限

ZooKeeper的ACL可針對znodes設(shè)置相應(yīng)的權(quán)限信息。

一個 ACL 權(quán)限設(shè)置通??梢苑譃?3 部分,分別是:權(quán)限模式(Scheme)、授權(quán)對象(ID)、權(quán)限信息(Permission)。

最終組成一條例如scheme:id:permission格式的 ACL 請求信息。

「權(quán)限模式:Scheme」

ZooKeeper 的權(quán)限驗證方式大體分為兩種類型,一種是范圍驗證,另外一種是口令驗證。

范圍驗證

所謂的范圍驗證就是說 ZooKeeper 可以針對一個 IP 或者一段 IP 地址授予某種權(quán)限。

比如我們可以讓一個 IP 地址為ip:192.168.0.11的機(jī)器對服務(wù)器上的某個數(shù)據(jù)節(jié)點具有寫入的權(quán)限。

或者也可以通過ip:192.168.0.11/22給一段 IP 地址的機(jī)器賦權(quán)。

口令驗證

可以理解為用戶名密碼的方式,這是我們最熟悉也是日常生活中經(jīng)常使用的模式,比如我們打開自己的電腦或者去銀行取錢都需要提供相應(yīng)的密碼。

在 ZooKeeper 中這種驗證方式是 Digest 認(rèn)證,我們知道通過網(wǎng)絡(luò)傳輸相對來說并不安全,所以絕不通過明文在網(wǎng)絡(luò)發(fā)送密碼也是程序設(shè)計中很重要的原則之一,而 Digest 這種認(rèn)證方式首先在客戶端傳送username:password這種形式的權(quán)限表示符后,ZooKeeper 服務(wù)端會對密碼部分使用 SHA-1 和 BASE64 算法進(jìn)行加密,以保證安全性。

Super 權(quán)限模式

權(quán)限模式 Super 可以認(rèn)為是一種特殊的 Digest 認(rèn)證。

具有 Super 權(quán)限的客戶端可以對 ZooKeeper 上的任意數(shù)據(jù)節(jié)點進(jìn)行任意操作。

下面這段代碼給出了 Digest 模式下客戶端的調(diào)用方式。

//創(chuàng)建節(jié)點
create /digest_node1
//設(shè)置digest權(quán)限驗證
setAcl /digest_node1 digest:用戶名:base64格式密碼:rwadc
//查詢節(jié)點Acl權(quán)限
getAcl /digest_node1
//授權(quán)操作
addauth digest user:passwd

如果一個客戶端對服務(wù)器上的一個節(jié)點設(shè)置了只有它自己才能操作的權(quán)限,那么等這個客戶端下線或被刪除后。

對其創(chuàng)建的節(jié)點要想進(jìn)行修改應(yīng)該怎么做呢?

我們可以通過「super 模式」即超級管理員的方式刪除該節(jié)點或變更該節(jié)點的權(quán)限驗證方式。

正因為「super 模式」有如此大的權(quán)限,我們在平時使用時也應(yīng)該更加謹(jǐn)慎。

world 模式

這種授權(quán)模式對應(yīng)于系統(tǒng)中的所有用戶,本質(zhì)上起不到任何作用。

設(shè)置了 world 權(quán)限模式系統(tǒng)中的所有用戶操作都可以不進(jìn)行權(quán)限驗證。

「授權(quán)對象(ID)」

所謂的授權(quán)對象就是說我們要把權(quán)限賦予誰,而對應(yīng)于 4 種不同的權(quán)限模式來說,如果我們選擇采用 IP 方式,使用的授權(quán)對象可以是一個 IP 地址或 IP 地址段;而如果使用 Digest 或 Super 方式,則對應(yīng)于一個用戶名。

如果是 World 模式,是授權(quán)系統(tǒng)中所有的用戶。

「權(quán)限信息(Permission)」

權(quán)限就是指我們可以在數(shù)據(jù)節(jié)點上執(zhí)行的操作種類,在 ZooKeeper 中已經(jīng)定義好的權(quán)限有 5 種:

  • 數(shù)據(jù)節(jié)點(create)創(chuàng)建權(quán)限,授予權(quán)限的對象可以在數(shù)據(jù)節(jié)點下創(chuàng)建子節(jié)點;
  • 數(shù)據(jù)節(jié)點(wirte)更新權(quán)限,授予權(quán)限的對象可以更新該數(shù)據(jù)節(jié)點;
  • 數(shù)據(jù)節(jié)點(read)讀取權(quán)限,授予權(quán)限的對象可以讀取該節(jié)點的內(nèi)容以及子節(jié)點的信息;
  • 數(shù)據(jù)節(jié)點(delete)刪除權(quán)限,授予權(quán)限的對象可以刪除該數(shù)據(jù)節(jié)點的子節(jié)點;
  • 數(shù)據(jù)節(jié)點(admin)管理者權(quán)限,授予權(quán)限的對象可以對該數(shù)據(jù)節(jié)點體進(jìn)行 ACL 權(quán)限設(shè)置。

需要注意的一點是,每個節(jié)點都有維護(hù)自身的 ACL 權(quán)限數(shù)據(jù),即使是該節(jié)點的子節(jié)點也是有自己的 ACL 權(quán)限而不是直接繼承其父節(jié)點的權(quán)限。

「實現(xiàn)自己的權(quán)限口控制」

雖然 ZooKeeper 自身的權(quán)限控制機(jī)制已經(jīng)做得很細(xì),但是它還是提供了一種權(quán)限擴(kuò)展機(jī)制來讓用戶實現(xiàn)自己的權(quán)限控制方式。

官方文檔中對這種機(jī)制的定義是 Pluggable ZooKeeper Authenication,意思是可插拔的授權(quán)機(jī)制,從名稱上我們可以看出它的靈活性。那么這種機(jī)制是如何實現(xiàn)的呢?

要想實現(xiàn)自定義的權(quán)限控制機(jī)制,最核心的一點是實現(xiàn) ZooKeeper 提供的權(quán)限控制器接口 AuthenticationProvider。

實現(xiàn)了自定義權(quán)限后,如何才能讓 ZooKeeper 服務(wù)端使用自定義的權(quán)限驗證方式呢?

接下來就需要將自定義的權(quán)限控制注冊到 ZooKeeper 服務(wù)器中,而注冊的方式通常有兩種。

第一種是通過設(shè)置系統(tǒng)屬性來注冊自定義的權(quán)限控制器:

-Dzookeeper.authProvider.x=CustomAuthenticationProvider

另一種是在配置文件zoo.cfg中進(jìn)行配置:

authProvider.x=CustomAuthenticationProvider

「實現(xiàn)原理」

首先是封裝該請求的類型,之后將權(quán)限信息封裝到 request 中并發(fā)送給服務(wù)端。而服務(wù)器的實現(xiàn)比較復(fù)雜,首先分析請求類型是否是權(quán)限相關(guān)操作,之后根據(jù)不同的權(quán)限模式(scheme)調(diào)用不同的實現(xiàn)類驗證權(quán)限最后存儲權(quán)限信息。

在授權(quán)接口中,值得注意的是會話的授權(quán)信息存儲在 ZooKeeper 服務(wù)端的內(nèi)存中,如果客戶端會話關(guān)閉,授權(quán)信息會被刪除。

下次連接服務(wù)器后,需要重新調(diào)用授權(quán)接口進(jìn)行授權(quán)。

序列化方式

在 ZooKeeper 中并沒有采用和 Java 一樣的序列化方式,而是采用了一個 Jute 的序列解決方案作為 ZooKeeper 框架自身的序列化方式。

ZooKeeper 從最開始就采用 Jute 作為其序列化解決方案,直到其最新的版本依然沒有更改。

雖然 ZooKeeper 一直將 Jute 框架作為序列化解決方案,但這并不意味著 Jute 相對其他框架性能更好,反倒是 Apache Avro、Thrift 等框架在性能上優(yōu)于前者。

之所以 ZooKeeper 一直采用 Jute 作為序列化解決方案,主要是新老版本的兼容等問題。

「如何 使用 Jute 實現(xiàn)序列化」

如果我們要想將某個定義的類進(jìn)行序列化,首先需要該類實現(xiàn) Record 接口的 serilize 和 deserialize 方法,這兩個方法分別是序列化和反序列化方法。

下邊這段代碼給出了我們一般在 ZooKeeper 中進(jìn)行序列化的具體實現(xiàn):

首先,我們定義了一個test_jute類,為了能夠?qū)λM(jìn)行序列化,需要該test_jute類實現(xiàn) Record 接口,并在對應(yīng)的 serialize 序列化方法和 deserialize 反序列化方法中編輯具體的實現(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 中,我們要實現(xiàn)的邏輯是,首先通過字符類型參數(shù) tag 傳遞標(biāo)記序列化標(biāo)識符,之后使用 writeLong 和 writeString 等方法分別將對象屬性字段進(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 在實現(xiàn)反序列化的過程則與我們上邊說的序列化過程正好相反。

public void deserialize(INputArchive a_,String tag) throws {
a_.startRecord(tag);
ids = a_.readLong("ids");
name = a_.readString("name");
a_.endRecord(tag);
}

序列化和反序列化的實現(xiàn)邏輯編碼方式相對固定,首先通過 startRecord 開啟一段序列化操作,之后通過 writeLong、writeString 或 readLong、 readString 等方法執(zhí)行序列化或反序列化。

本例中只是實現(xiàn)了長整型和字符型的序列化和反序列化操作,除此之外 ZooKeeper 中的 Jute 框架還支持整數(shù)類型(Int)、布爾類型(Bool)、雙精度類型(Double)以及 Byte/Buffer 類型。

集群

「ZooKeeper集群模式的特點」

在 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ù)器選舉的時候,系統(tǒng)會從 Follow 服務(wù)器之間根據(jù)多數(shù)投票原則,選舉出一個 Follow 服務(wù)器作為新的 Leader 服務(wù)器,只能提供讀服務(wù)。

Observer 服務(wù)器則主要負(fù)責(zé)處理來自客戶端的獲取數(shù)據(jù)等請求,并不參與 Leader 服務(wù)器的選舉操作,也不會作為候選者被選舉為 Leader 服務(wù)器,只能提供讀服務(wù)。

在 ZooKeeper 集群接收到來自客戶端的會話請求操作后,首先會判斷該條請求是否是事務(wù)性的會話請求。

對于事務(wù)性的會話請求,ZooKeeper 集群服務(wù)端會將該請求統(tǒng)一轉(zhuǎn)發(fā)給 Leader 服務(wù)器進(jìn)行操作。

所謂事務(wù)性請求,是指 ZooKeeper 服務(wù)器執(zhí)行完該條會話請求后,是否會導(dǎo)致執(zhí)行該條會話請求的服務(wù)器的數(shù)據(jù)或狀態(tài)發(fā)生改變,進(jìn)而導(dǎo)致與其他集群中的服務(wù)器出現(xiàn)數(shù)據(jù)不一致的情況。

Leader 服務(wù)器內(nèi)部執(zhí)行該條事務(wù)性的會話請求后,再將數(shù)據(jù)同步給其他角色服務(wù)器,從而保證事務(wù)性會話請求的執(zhí)行順序,進(jìn)而保證整個 ZooKeeper 集群的數(shù)據(jù)一致性。

在 ZooKeeper 集群的內(nèi)部實現(xiàn)中,是通過什么方法保證所有 ZooKeeper 集群接收到的事務(wù)性會話請求都能交給 Leader 服務(wù)器進(jìn)行處理的呢?

在 ZooKeeper 集群內(nèi)部,集群中除 Leader 服務(wù)器外的其他角色服務(wù)器接收到來自客戶端的事務(wù)性會話請求后,必須將該條會話請求轉(zhuǎn)發(fā)給 Leader 服務(wù)器進(jìn)行處理。

ZooKeeper 集群中的 Follow 和 Observer 服務(wù)器,都會檢查當(dāng)前接收到的會話請求是否是事務(wù)性的請求,如果是事務(wù)性的請求,那么就將該請求以 REQUEST 消息類型轉(zhuǎn)發(fā)給 Leader 服務(wù)器。

在 ZooKeeper集群中的服務(wù)器接收到該條消息后,會對該條消息進(jìn)行解析。

  • 分析出該條消息所包含的原始客戶端會話請求。
  • 之后將該條消息提交到自己的 Leader 服務(wù)器請求處理鏈中,開始進(jìn)行事務(wù)性的會話請求操作。
  • 如果不是事務(wù)性請求,ZooKeeper 集群則交由 Follow 和 Observer 角色服務(wù)器處理該條會話請求,如查詢數(shù)據(jù)節(jié)點信息。

當(dāng)一個業(yè)務(wù)場景在查詢操作多而創(chuàng)建刪除等事務(wù)性操作少的情況下,ZooKeeper 集群的性能表現(xiàn)的就會很好。

如果是在極端情況下,ZooKeeper 集群只有事務(wù)性的會話請求而沒有查詢操作,那么 Follow 和 Observer 服務(wù)器就只能充當(dāng)一個請求轉(zhuǎn)發(fā)服務(wù)器的角色, 所有的會話的處理壓力都在 Leader 服務(wù)器。

在處理性能上整個集群服務(wù)器的瓶頸取決于 Leader 服務(wù)器的性能。

ZooKeeper 集群的作用只能保證在 Leader 節(jié)點崩潰的時候,重新選舉出 Leader 服務(wù)器保證系統(tǒng)的穩(wěn)定性。

這也是 ZooKeeper 設(shè)計的一個缺點。

「Leader選舉」

Leader 服務(wù)器的選舉操作主要發(fā)生在兩種情況下。

第一種就是 ZooKeeper 集群服務(wù)啟動的時候,第二種就是在 ZooKeeper 集群中舊的 Leader 服務(wù)器失效時,這時 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)時,它會認(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)變化,都會由一個Zookeeper事務(wù)ID(ZXID)標(biāo)識。

寫入Zookeeper,會導(dǎo)致狀態(tài)變化,每次寫入都會導(dǎo)致ZXID發(fā)生變化。

ZXID由Leader統(tǒng)一分配,全局唯一,長度64位,遞增。

ZXID展示了所有的Zookeeper轉(zhuǎn)臺變更順序,每次變更都有一個唯一ZXID,如果zxid1小于zxid2,則說明zxid1的事務(wù)在zxid2的事務(wù)之前發(fā)生。

「選舉過程」

在 ZooKeeper 集群重新選舉 Leader 節(jié)點的過程中,主要可以分為 Leader 失效發(fā)現(xiàn)、重新選舉 Leader 、Follow 服務(wù)器角色變更、集群同步這幾個步驟。

Leader 失效發(fā)現(xiàn)

在 ZooKeeper 集群中,當(dāng) Leader 服務(wù)器失效時,ZooKeeper 集群會重新選舉出新的 Leader 服務(wù)器。

在 ZooKeeper 集群中,探測 Leader 服務(wù)器是否存活的方式與保持客戶端活躍性的方法非常相似。

首先,F(xiàn)ollow 服務(wù)器會定期向 Leader 服務(wù)器發(fā)送 網(wǎng)絡(luò)請求,在接收到請求后,Leader 服務(wù)器會返回響應(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)請求包后,如果沒有得到 Leader 服務(wù)器的返回信息,這時,如果是集群中個別的 Follow 服務(wù)器發(fā)現(xiàn)返回錯誤,并不會導(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 會重新選舉出 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ù)器對外提供服務(wù)。

集群同步數(shù)據(jù)

在 ZooKeeper 集群成功選舉 Leader 服務(wù)器,并且候選 Follow 服務(wù)器的角色變更后。

為避免在這期間導(dǎo)致的數(shù)據(jù)不一致問題,ZooKeeper 集群在對外提供服務(wù)之前,會通過 Leader 角色服務(wù)器管理同步其他角色服務(wù)器。

「底層實現(xiàn)」

首先,ZooKeeper 集群會先判斷 Leader 服務(wù)器是否失效,而判斷的方式就是 Follow 服務(wù)器向 Leader 服務(wù)器發(fā)送請求包,之后 Follow 服務(wù)器接收到響應(yīng)數(shù)據(jù)后,進(jìn)行解析,F(xiàn)ollow 服務(wù)器會根據(jù)返回的數(shù)據(jù),判斷 Leader 服務(wù)器的運(yùn)行狀態(tài),如果返回的是 LOOKING 關(guān)鍵字,表明與集群中 Leader 服務(wù)器無法正常通信。

  • 之后,在 ZooKeeper 集群選舉 Leader 服務(wù)器時,是通過 「FastLeaderElection」類實現(xiàn)的。

該類實現(xiàn)了 TCP 方式的通信連接,用于在 ZooKeeper 集群中與其他 Follow 服務(wù)器進(jìn)行協(xié)調(diào)溝通。

FastLeaderElection 類繼承了 Election 接口,定義其是用來進(jìn)行選舉的實現(xiàn)類。

  • 而在其內(nèi)部,又定義了選舉通信相關(guān)的一些配置參數(shù),比如 finalizeWait 最終等待時間、最大通知間隔時間 maxNotificationInterval 等。

在選舉的過程中,首先調(diào)用 ToSend 函數(shù)向 ZooKeeper 集群中的其他角色服務(wù)器發(fā)送本機(jī)的投票信息,其他服務(wù)器在接收投票信息后,會對投票信息進(jìn)行有效性驗證等操作,之后 ZooKeeper 集群統(tǒng)計投票信息,如果過半數(shù)的機(jī)器投票信息一致,則集群就重新選出新的 Leader 服務(wù)器。

這里我們要注意一個問題,那就是在重新選舉 Leader 服務(wù)器的過程中,ZooKeeper 集群理論上是無法進(jìn)行事務(wù)性的請求處理的。

因此,發(fā)送到 ZooKeeper 集群中的事務(wù)性會話會被掛起,暫時不執(zhí)行,等到選舉出新的 Leader 服務(wù)器后再進(jìn)行操作。

「Observer」

在 ZooKeeper 集群服務(wù)運(yùn)行的過程中,Observer 服務(wù)器與 Follow 服務(wù)器具有一個相同的功能,那就是負(fù)責(zé)處理來自客戶端的諸如查詢數(shù)據(jù)節(jié)點等非事務(wù)性的會話請求操作。

  • 但與 Follow 服務(wù)器不同的是,Observer 不參與 Leader 服務(wù)器的選舉工作,也不會被選舉為 Leader 服務(wù)器。

在早期的 ZooKeeper 集群服務(wù)運(yùn)行過程中,只有 Leader 服務(wù)器和 Follow 服務(wù)器。

不過隨著 ZooKeeper 在分布式環(huán)境下的廣泛應(yīng)用,早期模式的設(shè)計缺點也隨之產(chǎn)生,主要帶來的問題有如下幾點:

隨著集群規(guī)模的變大,集群處理寫入的性能反而下降。

  • ZooKeeper 集群無法做到跨域部署。

其中最主要的問題在于,當(dāng) ZooKeeper 集群的規(guī)模變大,集群中 Follow 服務(wù)器數(shù)量逐漸增多的時候,ZooKeeper 處理創(chuàng)建數(shù)據(jù)節(jié)點等事務(wù)性請求操作的性能就會逐漸下降。

這是因為 ZooKeeper 集群在處理事務(wù)性請求操作時,要在 ZooKeeper 集群中對該事務(wù)性的請求發(fā)起投票,只有超過半數(shù)的 Follow 服務(wù)器投票一致,才會執(zhí)行該條寫入操作。

正因如此,隨著集群中 Follow 服務(wù)器的數(shù)量越來越多,一次寫入等相關(guān)操作的投票也就變得越來越復(fù)雜,并且 Follow 服務(wù)器之間彼此的網(wǎng)絡(luò)通信也變得越來越耗時,導(dǎo)致隨著 Follow 服務(wù)器數(shù)量的逐步增加,事務(wù)性的處理性能反而變得越來越低。

  • 為了解決這一問題,在 ZooKeeper 3.6 版本后,ZooKeeper 集群中創(chuàng)建了一種新的服務(wù)器角色,即 Observer——觀察者角色服務(wù)器。

Observer 可以處理 ZooKeeper 集群中的非事務(wù)性請求,并且不參與 Leader 節(jié)點等投票相關(guān)的操作。

這樣既保證了 ZooKeeper 集群性能的擴(kuò)展性,又避免了因為過多的服務(wù)器參與投票相關(guān)的操作而影響 ZooKeeper 集群處理事務(wù)性會話請求的能力。

  • 在實際部署的時候,因為 Observer 不參與 Leader 節(jié)點等操作,并不會像 Follow 服務(wù)器那樣頻繁的與 Leader 服務(wù)器進(jìn)行通信。

因此,可以將 Observer 服務(wù)器部署在不同的網(wǎng)絡(luò)區(qū)間中,這樣也不會影響整個 ZooKeeper 集群的性能,也就是所謂的跨域部署。

「在我們?nèi)粘J褂?ZooKeeper 集群服務(wù)器的時候,集群中的機(jī)器個數(shù)應(yīng)該選擇奇數(shù)個?」

兩個原因:

在容錯能力相同的情況下,奇數(shù)臺更節(jié)省資源

Zookeeper中 Leader 選舉算法采用了Zab協(xié)議。

Zab核心思想是當(dāng)多數(shù) Server 寫成功,則寫成功。

舉兩個例子:

  • 假如zookeeper集群1 ,有3個節(jié)點,3/2=1.5 , 即zookeeper想要正常對外提供服務(wù)(即leader選舉成功),至少需要2個節(jié)點是正常的。換句話說,3個節(jié)點的zookeeper集群,允許有一個節(jié)點宕機(jī)。
  • 假如zookeeper集群2,有4個節(jié)點,4/2=2 , 即zookeeper想要正常對外提供服務(wù)(即leader選舉成功),至少需要3個節(jié)點是正常的。換句話說,4個節(jié)點的zookeeper集群,也允許有一個節(jié)點宕機(jī)。

集群1與集群2都有 允許1個節(jié)點宕機(jī) 的容錯能力,但是集群2比集群1多了1個節(jié)點。在相同容錯能力的情況下,本著節(jié)約資源的原則,zookeeper集群的節(jié)點數(shù)維持奇數(shù)個更好一些。

防止由腦裂造成的集群不可用。

集群的腦裂通常是發(fā)生在節(jié)點之間通信不可達(dá)的情況下,集群會分裂成不同的小集群,小集群各自選出自己的master節(jié)點,導(dǎo)致原有的集群出現(xiàn)多個master節(jié)點的情況,這就是腦裂。

下面舉例說一下為什么采用奇數(shù)臺節(jié)點,就可以防止由于腦裂造成的服務(wù)不可用:

假如zookeeper集群有 5 個節(jié)點,發(fā)生了腦裂,腦裂成了A、B兩個小集群:

  • A :1個節(jié)點 ,B :4個節(jié)點
  • A :2個節(jié)點, B :3個節(jié)點

可以看出,上面這兩種情況下,A、B中總會有一個小集群滿足 可用節(jié)點數(shù)量 > 總節(jié)點數(shù)量/2 。

所以zookeeper集群仍然能夠選舉出leader , 仍然能對外提供服務(wù),只不過是有一部分節(jié)點失效了而已。

假如zookeeper集群有4個節(jié)點,同樣發(fā)生腦裂,腦裂成了A、B兩個小集群:

  • A:1個節(jié)點 , B:3個節(jié)點
  • A:2個節(jié)點 , B:2個節(jié)點

因為A和B都是2個節(jié)點,都不滿足 可用節(jié)點數(shù)量 > 總節(jié)點數(shù)量/2 的選舉條件, 所以此時zookeeper就徹底不能提供服務(wù)了。

ZAB協(xié)議

「ZAB 協(xié)議算法」

ZooKeeper 最核心的作用就是保證分布式系統(tǒng)的數(shù)據(jù)一致性,而無論是處理來自客戶端的會話請求時,還是集群 Leader 節(jié)點發(fā)生重新選舉時,都會產(chǎn)生數(shù)據(jù)不一致的情況。

為了解決這個問題,ZooKeeper 采用了 ZAB 協(xié)議算法。

ZAB 協(xié)議算法(Zookeeper Atomic Broadcast ,Zookeeper 原子廣播協(xié)議)是 ZooKeeper 專門設(shè)計用來解決集群最終一致性問題的算法,它的兩個核心功能點是崩潰恢復(fù)和原子廣播協(xié)議。

在整個 ZAB 協(xié)議的底層實現(xiàn)中,ZooKeeper 集群主要采用主從模式的系統(tǒng)架構(gòu)方式來保證 ZooKeeper 集群系統(tǒng)的一致性。

當(dāng)接收到來自客戶端的事務(wù)性會話請求后,系統(tǒng)集群采用主服務(wù)器來處理該條會話請求,經(jīng)過主服務(wù)器處理的結(jié)果會通過網(wǎng)絡(luò)發(fā)送給集群中其他從節(jié)點服務(wù)器進(jìn)行數(shù)據(jù)同步操作。

以 ZooKeeper 集群為例,這個操作過程可以概括為:

當(dāng) ZooKeeper 集群接收到來自客戶端的事務(wù)性的會話請求后,集群中的其他 Follow 角色服務(wù)器會將該請求轉(zhuǎn)發(fā)給 Leader 角色服務(wù)器進(jìn)行處理。

當(dāng) Leader 節(jié)點服務(wù)器在處理完該條會話請求后,會將結(jié)果通過操作日志的方式同步給集群中的 Follow 角色服務(wù)器。

然后 Follow 角色服務(wù)器根據(jù)接收到的操作日志,在本地執(zhí)行相關(guān)的數(shù)據(jù)處理操作,最終完成整個 ZooKeeper 集群對客戶端會話的處理工作。

「崩潰恢復(fù)」

當(dāng)集群中的 Leader 發(fā)生故障的時候,整個集群就會因為缺少 Leader 服務(wù)器而無法處理來自客戶端的事務(wù)性的會話請求。

因此,為了解決這個問題。在 ZAB 協(xié)議中也設(shè)置了處理該問題的崩潰恢復(fù)機(jī)制。

崩潰恢復(fù)機(jī)制是保證 ZooKeeper 集群服務(wù)高可用的關(guān)鍵。觸發(fā) ZooKeeper 集群執(zhí)行崩潰恢復(fù)的事件是集群中的 Leader 節(jié)點服務(wù)器發(fā)生了異常而無法工作,于是 Follow 服務(wù)器會通過投票來決定是否選出新的 Leader 節(jié)點服務(wù)器。

投票過程如下:

當(dāng)崩潰恢復(fù)機(jī)制開始的時候,整個 ZooKeeper 集群的每臺 Follow 服務(wù)器會發(fā)起投票,并同步給集群中的其他 Follow 服務(wù)器。

在接收到來自集群中的其他 Follow 服務(wù)器的投票信息后,集群中的每個 Follow 服務(wù)器都會與自身的投票信息進(jìn)行對比,如果判斷新的投票信息更合適,則采用新的投票信息作為自己的投票信息。在集群中的投票信息還沒有達(dá)到超過半數(shù)原則的情況下,再進(jìn)行新一輪的投票,最終當(dāng)整個 ZooKeeper 集群中的 Follow 服務(wù)器超過半數(shù)投出的結(jié)果相同的時候,就會產(chǎn)生新的 Leader 服務(wù)器。

選票結(jié)構(gòu):

以 Fast Leader Election 選舉的實現(xiàn)方式來講,如下圖所示,一個選票的整體結(jié)果可以分為一下六個部分:

  • logicClock:用來記錄服務(wù)器的投票輪次。logicClock 會從 1 開始計數(shù),每當(dāng)該臺服務(wù)經(jīng)過一輪投票后,logicClock 的數(shù)值就會加 1 。
  • state:用來標(biāo)記當(dāng)前服務(wù)器的狀態(tài)。在 ZooKeeper 集群中一臺服務(wù)器具有 LOOKING、FOLLOWING、LEADERING、OBSERVING 這四種狀態(tài)。
  • self_id:用來表示當(dāng)前服務(wù)器的 ID 信息,該字段在 ZooKeeper 集群中主要用來作為服務(wù)器的身份標(biāo)識符。
  • self_zxid:當(dāng)前服務(wù)器上所保存的數(shù)據(jù)的最大事務(wù) ID ,從 0 開始計數(shù)。
  • vote_id:投票要被推舉的服務(wù)器的唯一 ID 。
  • vote_zxid:被推舉的服務(wù)器上所保存的數(shù)據(jù)的最大事務(wù) ID ,從 0 開始計數(shù)。

當(dāng) ZooKeeper 集群需要重新選舉出新的 Leader 服務(wù)器的時候,就會根據(jù)上面介紹的投票信息內(nèi)容進(jìn)行對比,以找出最適合的服務(wù)器。

選票篩選

當(dāng)一臺 Follow 服務(wù)器接收到網(wǎng)絡(luò)中的其他 Follow 服務(wù)器的投票信息后,是如何進(jìn)行對比來更新自己的投票信息的。

Follow 服務(wù)器進(jìn)行選票對比的過程,如下圖所示。

首先,會對比 logicClock 服務(wù)器的投票輪次,當(dāng) logicClock 相同時,表明兩張選票處于相同的投票階段,并進(jìn)入下一階段,否則跳過。

接下來再對比vote_zxid被選舉的服務(wù)器 ID 信息,若接收到的外部投票信息中的 vote_zxid字段較大,則將自己的票中的vote_zxid與vote_myid更新為收到的票中的vote_zxid與vote_myid ,并廣播出去。

要是對比的結(jié)果相同,則繼續(xù)對比vote_myid被選舉服務(wù)器上所保存的最大事務(wù) ID ,若外部投票的vote_myid 比較大,則將自己的票中的 vote_myid更新為收到的票中的vote_myid 。

經(jīng)過這些對比和替換后,最終該臺 Follow 服務(wù)器會產(chǎn)生新的投票信息,并在下一輪的投票中發(fā)送到 ZooKeeper 集群中。

「消息廣播」

在 Leader 節(jié)點服務(wù)器處理請求后,需要通知集群中的其他角色服務(wù)器進(jìn)行數(shù)據(jù)同步。ZooKeeper 集群采用消息廣播的方式發(fā)送通知。

ZooKeeper 集群使用原子廣播協(xié)議進(jìn)行消息發(fā)送,該協(xié)議的底層實現(xiàn)過程與二階段提交過程非常相似,如下圖所示。

當(dāng)要在集群中的其他角色服務(wù)器進(jìn)行數(shù)據(jù)同步的時候,Leader 服務(wù)器將該操作過程封裝成一個 Proposal 提交事務(wù),并將其發(fā)送給集群中其他需要進(jìn)行數(shù)據(jù)同步的服務(wù)器。

當(dāng)這些服務(wù)器接收到 Leader 服務(wù)器的數(shù)據(jù)同步事務(wù)后,會將該條事務(wù)能否在本地正常執(zhí)行的結(jié)果反饋給 Leader 服務(wù)器,Leader 服務(wù)器在接收到其他 Follow 服務(wù)器的反饋信息后進(jìn)行統(tǒng)計,判斷是否在集群中執(zhí)行本次事務(wù)操作。

這里請注意 ,與二階段提交過程不同(即需要集群中所有服務(wù)器都反饋可以執(zhí)行事務(wù)操作后,主服務(wù)器再次發(fā)送 commit 提交請求執(zhí)行數(shù)據(jù)變更) ,ZAB 協(xié)議算法省去了中斷的邏輯,當(dāng) ZooKeeper 集群中有超過一半的 Follow 服務(wù)器能夠正常執(zhí)行事務(wù)操作后,整個 ZooKeeper 集群就可以提交 Proposal 事務(wù)了。

日志清理

「日志類型」

在 ZooKeeper 服務(wù)運(yùn)行的時候,一般會產(chǎn)生數(shù)據(jù)快照和日志文件,數(shù)據(jù)快照用于集群服務(wù)中的數(shù)據(jù)同步,而數(shù)據(jù)日志則記錄了 ZooKeeper 服務(wù)運(yùn)行的相關(guān)狀態(tài)信息。

其中,數(shù)據(jù)日志是我們在生產(chǎn)環(huán)境中需要定期維護(hù)和管理的文件。

「清理方案」

如上面所介紹的,面對生產(chǎn)系統(tǒng)中產(chǎn)生的日志,一般的維護(hù)操作是備份和清理。

備份是為了之后對系統(tǒng)的運(yùn)行情況進(jìn)行排查和優(yōu)化,而清理主要因為隨著系統(tǒng)日志的增加,日志會逐漸占用系統(tǒng)的存儲空間,如果一直不進(jìn)行清理,可能耗盡系統(tǒng)的磁盤存儲空間,并最終影響服務(wù)的運(yùn)行。

「清理工具」

Corntab

首先,我們介紹的是 Linux corntab ,它是 Linux 系統(tǒng)下的軟件,可以自動地按照我們設(shè)定的時間,周期性地執(zhí)行我們編寫的相關(guān)腳本。

crontab 定時腳本的方式相對靈活,可以按照我們的業(yè)務(wù)需求來設(shè)置處理日志的維護(hù)方式,比如這里我們希望定期清除 ZooKeeper 服務(wù)運(yùn)行的日志,而不想清除數(shù)據(jù)快照的文件,則可以通過腳本設(shè)置,達(dá)到只對數(shù)據(jù)日志文件進(jìn)行清理的目的。

PurgeTxnLog

ZooKeeper 自身還提供了 PurgeTxnLog 工具類,用來清理 snapshot 數(shù)據(jù)快照文件和系統(tǒng)日志。

PurgeTxnLog 清理方式和我們上面介紹的方式十分相似,也是通過定時腳本執(zhí)行任務(wù),唯一的不同是,上面提到在編寫日志清除 logsCleanWeek 的時候 ,我們使用的是原生 shell 腳本自己手動編寫的數(shù)據(jù)日志清理邏輯,而使用 PurgeTxnLog 則可以在編寫清除腳本的時候調(diào)用 ZooKeeper 為我們提供的工具類完成日志清理工作。

如下面的代碼所示,首先,我們在/usr/bin目錄下創(chuàng)建一個 PurgeLogsClean 腳本。注意這里的腳本也是一個 shell 文件。

在腳本中我們只需要編寫 PurgeTxnLog 類的調(diào)用程序,系統(tǒng)就會自動通過 PurgeTxnLog 工具類為我們完成對應(yīng)日志文件的清理工作。

#!/bin/sh  
java -cp "$CLASSPATH" org.apache.zookeeper.server.PurgeTxnLog
echo "清理完成"

PurgeTxnLog 方式與 crontab 相比,使用起來更加容易而且也更加穩(wěn)定安全,不過 crontab 方式更加靈活,我們可以根據(jù)不同的業(yè)務(wù)需求編寫自己的清理邏輯。

實現(xiàn)分布式鎖

分布式鎖的目的是保證在分布式部署的應(yīng)用集群中,多個服務(wù)在請求同一個方法或者同一個業(yè)務(wù)操作的情況下,對應(yīng)業(yè)務(wù)邏輯只能被一臺機(jī)器上的一個線程執(zhí)行,避免出現(xiàn)并發(fā)問題。

實現(xiàn)分布式鎖目前有三種流行方案,即基于數(shù)據(jù)庫、Redis、ZooKeeper 的方案

「方案一:」

使用節(jié)點中的存儲數(shù)據(jù)區(qū)域,ZK中節(jié)點存儲數(shù)據(jù)的大小不能超過1M,但是只是存放一個標(biāo)識是足夠的,線程獲得鎖時,先檢查該標(biāo)識是否是無鎖標(biāo)識,若是可修改為占用標(biāo)識,使用完再恢復(fù)為無鎖標(biāo)識

「方案二:」

使用子節(jié)點,每當(dāng)有線程來請求鎖的時候,便在鎖的節(jié)點下創(chuàng)建一個子節(jié)點,子節(jié)點類型必須維護(hù)一個順序,對子節(jié)點的自增序號進(jìn)行排序,默認(rèn)總是最小的子節(jié)點對應(yīng)的線程獲得鎖,釋放鎖時刪除對應(yīng)子節(jié)點便可

「死鎖風(fēng)險:」

兩種方案其實都是可行的,但是使用鎖的時候一定要去規(guī)避死鎖

方案一看上去是沒問題的,用的時候設(shè)置標(biāo)識,用完清除標(biāo)識,但是要是持有鎖的線程發(fā)生了意外,釋放鎖的代碼無法執(zhí)行,鎖就無法釋放,其他線程就會一直等待鎖,相關(guān)同步代碼便無法執(zhí)行

方案二也存在這個問題,但方案二可以利用ZK的臨時順序節(jié)點來解決這個問題,只要線程發(fā)生了異常導(dǎo)致程序中斷,就會丟失與ZK的連接,ZK檢測到該鏈接斷開,就會自動刪除該鏈接創(chuàng)建的臨時節(jié)點,這樣就可以達(dá)到即使占用鎖的線程程序發(fā)生意外,也能保證鎖正常釋放的目的

「避免羊群效應(yīng)」

把鎖請求者按照后綴數(shù)字進(jìn)行排隊,后綴數(shù)字小的鎖請求者先獲取鎖。

如果所有的鎖請求者都 watch 鎖持有者,當(dāng)代表鎖請求者的 znode 被刪除以后,所有的鎖請求者都會通知到,但是只有一個鎖請求者能拿到鎖。這就是羊群效應(yīng)。

為了避免羊群效應(yīng),每個鎖請求者 watch 它前面的鎖請求者。

每次鎖被釋放,只會有一個鎖請求者 會被通知到。

這樣做還讓鎖的分配具有公平性,鎖定的分配遵循先到先得的原則。

「用 ZooKeeper 實現(xiàn)分布式鎖的算法流程,根節(jié)點為 /lock:」

  • 客戶端連接 ZooKeeper,并在/lock下創(chuàng)建臨時有序子節(jié)點,第一個客戶端對應(yīng)的子節(jié)點為/lock/lock01/00000001,第二個為 /lock/lock01/00000002;
  • 其他客戶端獲取/lock01下的子節(jié)點列表,判斷自己創(chuàng)建的子節(jié)點是否為當(dāng)前列表中序號最小的子節(jié)點;
  • 如果是則認(rèn)為獲得鎖,執(zhí)行業(yè)務(wù)代碼,否則通過 watch 事件監(jiān)聽/lock01的子節(jié)點變更消息,獲得變更通知后重復(fù)此步驟直至獲得鎖;
  • 完成業(yè)務(wù)流程后,刪除對應(yīng)的子節(jié)點,釋放分布式鎖;

在實際開發(fā)中,可以應(yīng)用 Apache Curator 來快速實現(xiàn)分布式鎖,Curator 是 Netflix 公司開源的一個 ZooKeeper 客戶端,對 ZooKeeper 原生 API 做了抽象和封裝。

實現(xiàn)分布式ID

我們可以通過 ZooKeeper 自身的客戶端和服務(wù)器運(yùn)行模式,來實現(xiàn)一個分布式網(wǎng)絡(luò)環(huán)境下的 ID 請求和分發(fā)過程。

每個需要 ID 編碼的業(yè)務(wù)服務(wù)器可以看作是 ZooKeeper 的客戶端。ID 編碼生成器可以作為 ZooKeeper 的服務(wù)端。

客戶端通過發(fā)送請求到 ZooKeeper 服務(wù)器,來獲取編碼信息,服務(wù)端接收到請求后,發(fā)送 ID 編碼給客戶端。

「實現(xiàn)原理:」

可以利用 ZooKeeper 數(shù)據(jù)模型中的順序節(jié)點作為 ID 編碼。

  • 客戶端通過調(diào)用 create 函數(shù)創(chuàng)建順序節(jié)點。服務(wù)器成功創(chuàng)建節(jié)點后,會響應(yīng)客戶端請求,把創(chuàng)建好的節(jié)點信息發(fā)送給客戶端。
  • 客戶端用數(shù)據(jù)節(jié)點名稱作為 ID 編碼,進(jìn)行之后的本地業(yè)務(wù)操作。
  • 利用 ZooKeeper 中的順序節(jié)點特性,很容易使我們創(chuàng)建的 ID 編碼具有有序的特性。并且我們也可以通過客戶端傳遞節(jié)點的名稱,根據(jù)不同的業(yè)務(wù)編碼區(qū)分不同的業(yè)務(wù)系統(tǒng),從而使編碼的擴(kuò)展能力更強(qiáng)。

雖然使用 ZooKeeper 的實現(xiàn)方式有這么多優(yōu)點,但也會有一些潛在的問題。

其中最主要的是,在定義編碼的規(guī)則上還是強(qiáng)烈依賴于程序員自身的能力和對業(yè)務(wù)的深入理解。

很容易出現(xiàn)因為考慮不周,造成設(shè)置的規(guī)則在運(yùn)行一段時間后,無法滿足業(yè)務(wù)要求或者安全性不夠等問題。

實現(xiàn)負(fù)載均衡

「常見負(fù)載均衡算法」

輪詢法

輪詢法是最為簡單的負(fù)載均衡算法,當(dāng)接收到來自網(wǎng)絡(luò)中的客戶端請求后,負(fù)載均衡服務(wù)器會按順序逐個分配給后端服務(wù)。

比如集群中有 3 臺服務(wù)器,分別是 server1、server2、server3,輪詢法會按照 sever1、server2、server3 這個順序依次分發(fā)會話請求給每個服務(wù)器。當(dāng)?shù)谝淮屋喸兘Y(jié)束后,會重新開始下一輪的循環(huán)。

隨機(jī)法

隨機(jī)算法是指負(fù)載均衡服務(wù)器在接收到來自客戶端的請求后,會根據(jù)一定的隨機(jī)算法選中后臺集群中的一臺服務(wù)器來處理這次會話請求。

不過,當(dāng)集群中備選機(jī)器變的越來越多時,通過統(tǒng)計學(xué)我們可以知道每臺機(jī)器被抽中的概率基本相等,因此隨機(jī)算法的實際效果越來越趨近輪詢算法。

原地址哈希法

原地址哈希算法的核心思想是根據(jù)客戶端的 IP 地址進(jìn)行哈希計算,用計算結(jié)果進(jìn)行取模后,根據(jù)最終結(jié)果選擇服務(wù)器地址列表中的一臺機(jī)器,處理該條會話請求。

采用這種算法后,當(dāng)同一 IP 的客戶端再次訪問服務(wù)端后,負(fù)載均衡服務(wù)器最終選舉的還是上次處理該臺機(jī)器會話請求的服務(wù)器,也就是每次都會分配同一臺服務(wù)器給客戶端。

加權(quán)輪詢法

加權(quán)輪詢的方式與輪詢算法的方式很相似,唯一的不同在于選擇機(jī)器的時候,不只是單純按照順序的方式選擇,還根據(jù)機(jī)器的配置和性能高低有所側(cè)重,配置性
當(dāng)前名稱:一篇學(xué)會ZooKeeper核心
URL網(wǎng)址:http://uogjgqi.cn/article/ccscspe.html

掃二維碼與項目經(jīng)理溝通

我們在微信上24小時期待你的聲音

解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流