|
你好,我是悟空呀~ Zookeeper 基本介紹 Apache ZooKeeper 是由Apache Hadoop的子項目發(fā)展而來,為分布式應用提供高效且可靠的分布式協(xié)調(diào)服務(wù)。
ZK可以提供諸如數(shù)據(jù)發(fā)布/訂閱、負載均衡、命名服務(wù)、分布式協(xié)調(diào)/通知,集群管理,Master選舉,分布式鎖,分布式隊列等功能。 「它具有以下特性:」
數(shù)據(jù)模型ZooKeeper 中的數(shù)據(jù)模型是一種樹形結(jié)構(gòu),非常像電腦中的文件系統(tǒng),有一個根文件夾,下面還有很多子文件夾。
![]() 「為什么 ZooKeeper 不能采用相對路徑查找節(jié)點呢?」 ? 像這種查找與給定值相等的記錄問題最適合用散列來解決。 因此 ZooKeeper 在底層實現(xiàn)的時候,使用了一個 hashtable,即 這樣就大大提高了 ZooKeeper 的性能。 「節(jié)點類型」 ZooKeeper 中的數(shù)據(jù)節(jié)點也分為持久節(jié)點、臨時節(jié)點和有序節(jié)點三種類型: ? 一旦將節(jié)點創(chuàng)建為持久節(jié)點,該數(shù)據(jù)節(jié)點會一直存儲在 ZooKeeper 服務(wù)器上,即使創(chuàng)建該節(jié)點的客戶端與服務(wù)端的會話關(guān)閉了,該節(jié)點依然不會被刪除。如果我們想刪除持久節(jié)點,就要顯式調(diào)用 delete 函數(shù)進行刪除操作。 ? 如果將節(jié)點創(chuàng)建為臨時節(jié)點,那么該節(jié)點數(shù)據(jù)不會一直存儲在 ZooKeeper 服務(wù)器上。 當創(chuàng)建該臨時節(jié)點的客戶端會話因超時或發(fā)生異常而關(guān)閉時,該節(jié)點也相應在 ZooKeeper 服務(wù)器上被刪除,同樣,我們可以像刪除持久節(jié)點一樣主動刪除臨時節(jié)點。 在平時的開發(fā)中,我們可以利用臨時節(jié)點的這一特性來做服務(wù)器集群內(nèi)機器運行情況的統(tǒng)計,將集群設(shè)置為 ? 節(jié)點有序是說在我們創(chuàng)建有序節(jié)點的時候,ZooKeeper 服務(wù)器會自動使用一個單調(diào)遞增的數(shù)字作為后綴,追加到我們創(chuàng)建節(jié)點的后邊。 例如一個客戶端創(chuàng)建了一個路徑為
ZooKeeper 中的每個節(jié)點都維護有這些內(nèi)容:一個二進制數(shù)組 「節(jié)點的狀態(tài)結(jié)構(gòu)」 執(zhí)行 每一個節(jié)點都有一個自己的狀態(tài)屬性,記錄了節(jié)點本身的一些信息:
「數(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ù)。
? 在單臺 ZooKeeper 服務(wù)器運行過程中因為異常而關(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)信息等。
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ù)器就會進行數(shù)據(jù)同步。 ? 在接收到事務(wù)日志后,并在本地服務(wù)器上執(zhí)行。這種數(shù)據(jù)同步的方式,避免了直接使用實際的業(yè)務(wù)數(shù)據(jù),減少了網(wǎng)絡(luò)傳輸?shù)拈_銷,提升了整個 ZooKeeper 集群的執(zhí)行性能。 Watch機制ZooKeeper 的客戶端可以通過 Watch 機制來訂閱當服務(wù)器上某一節(jié)點的數(shù)據(jù)或狀態(tài)發(fā)生變化時收到相應的通知; 「如何實現(xiàn):」 我們可以通過向 ZooKeeper 客戶端的構(gòu)造方法中傳遞 Watcher 參數(shù)的方式實現(xiàn): 上面代碼的意思是定義了一個了 ZooKeeper 客戶端對象實例,并傳入三個參數(shù):
這個 Watcher 將作為整個 ZooKeeper 會話期間的上下文 ,一直被保存在客戶端 ZKWatchManager 的 defaultWatcher 中。 除此之外,ZooKeeper 客戶端也可以通過 getData、exists 和 getChildren 三個接口來向 ZooKeeper 服務(wù)器注冊 Watcher,從而方便地在不同的情況下添加 Watch 事件: 觸發(fā)通知的條件: ![]() 上圖中列出了客戶端在不同會話狀態(tài)下,相應的在服務(wù)器節(jié)點所能支持的事件類型。
「當服務(wù)端某一節(jié)點發(fā)生數(shù)據(jù)變更操作時,所有曾經(jīng)設(shè)置了該節(jié)點監(jiān)控事件的客戶端都會收到服務(wù)器的通知嗎?」 答案是否定的,Watch 事件的觸發(fā)機制取決于會話的連接狀態(tài)和客戶端注冊事件的類型,所以當客戶端會話狀態(tài)或數(shù)據(jù)節(jié)點發(fā)生改變時,都會觸發(fā)對應的 Watch 事件。 「訂閱發(fā)布場景實現(xiàn)」 ? 發(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ù)器配置信息的維護,利用ZooKeeper 的發(fā)布訂閱功能就能解決這個問題。 我們可以把諸如數(shù)據(jù)庫配置項這樣的信息存儲在 ZooKeeper 數(shù)據(jù)節(jié)點中。 如
我們使用 Watch 機制實現(xiàn)了一個分布式環(huán)境下的配置管理功能,通過對 ZooKeeper 服務(wù)器節(jié)點添加數(shù)據(jù)變更事件,實現(xiàn)當數(shù)據(jù)庫配置項信息變更后,集群中的各個客戶端能接收到該變更事件的通知,并獲取最新的配置信息。 ? 會話機制ZooKeeper 的工作方式一般是通過客戶端向服務(wù)端發(fā)送請求而實現(xiàn)的。 而在一個請求的發(fā)送過程中,首先,客戶端要與服務(wù)端進行連接,而一個連接就是一個會話。 ? 這個數(shù)據(jù)結(jié)構(gòu)由三個部分組成:分別是會話 ID(sessionID)、會話超時時間(TimeOut)、會話關(guān)閉狀態(tài)(isClosing)
「會話狀態(tài)」 在 ZooKeeper 服務(wù)的運行過程中,會話會經(jīng)歷不同的狀態(tài)變化。 這些狀態(tài)包括: ? 當客戶端開始創(chuàng)建一個與服務(wù)端的會話操作時,它的會話狀態(tài)就會變成 CONNECTING,之后客戶端會根據(jù)服務(wù)器地址列表中的服務(wù)器 IP 地址分別嘗試進行連接。如果遇到一個 IP 地址可以連接到服務(wù)器,那么客戶端會話狀態(tài)將變?yōu)?CONNECTIED。 如果因為網(wǎng)絡(luò)原因造成已經(jīng)連接的客戶端會話斷開時,客戶端會重新嘗試連接服務(wù)端。而對應的客戶端會話狀態(tài)又變成 CONNECTING ,直到該會話連接到服務(wù)端最終又變成 CONNECTIED。 ? 最后,當出現(xiàn)超時或者客戶端主動退出程序等情況時,客戶端會話狀態(tài)則會變?yōu)?CLOSE 狀態(tài)。 「會話異?!?/strong> 在 ZooKeeper 中,會話的超時異常包括客戶端 readtimeout 異常和服務(wù)器端 sessionTimeout 異常。
而對于那些對 ZooKeeper 接觸不深的開發(fā)人員來說,他們常常踩坑的地方在于,雖然設(shè)置了超時間,但是在實際服務(wù)運行的時候 ZooKeeper 并沒有按照設(shè)置的超時時間來管理會話。
ZooKeeper 客戶端在和服務(wù)端建立連接的時候,會提交一個客戶端設(shè)置的會話超時時間,而該超時時間會和服務(wù)端設(shè)置的最大超時時間和最小超時時間進行比對,如果正好在其允許的范圍內(nèi),則采用客戶端的超時時間管理會話。 如果大于或者小于服務(wù)端設(shè)置的超時時間,則采用服務(wù)端設(shè)置的值管理會話。 「分桶策略」 我們知道在 ZooKeeper 中為了保證一個會話的存活狀態(tài),客戶端需要向服務(wù)器周期性地發(fā)送心跳信息。
ZooKeeper 服務(wù)端接收請求后,會更新會話的過期時間,來保證會話的存活狀態(tài)。
? 在 ZooKeeper 中,會話將按照不同的時間間隔進行劃分,超時時間相近的會話將被放在同一個間隔區(qū)間中,這種方式避免了 ZooKeeper 對每一個會話進行檢查,而是采用分批次的方式管理會話。 這就降低了會話管理的難度,因為每次小批量的處理會話過期也提高了會話處理的效率。 「ZooKeeper 這種會話管理的好處?」 ZooKeeper 這種分段的會話管理策略大大提高了計算會話過期的效率,如果是在一個實際生產(chǎn)環(huán)境中,一個大型的分布式系統(tǒng)往往具有很高的訪問量。 而 ZooKeeper 作為其中的組件,對外提供服務(wù)往往要承擔數(shù)千個客戶端的訪問,這其中就要對這幾千個會話進行管理。 在這種場景下,要想通過對每一個會話進行管理和檢查并不合適,所以采用將同一個時間段的會話進行統(tǒng)一管理,這樣就大大提高了服務(wù)的運行效率。 「底層實現(xiàn)」 ZooKeeper 底層實現(xiàn)的原理,核心的一點就是過期隊列這個數(shù)據(jù)結(jié)構(gòu)。所有會話過期的相關(guān)操作都是圍繞這個隊列進行的。
「一個會話過期隊列是由若干個 bucket 組成的。」
將會話按照不同的過期時間段分別維護到過期隊列之后,在 ZooKeeper 服務(wù)運行的過程中,具體的執(zhí)行過程如下圖所示。 ![]() 首先,ZooKeeper 服務(wù)會開啟一個線程專門用來檢索過期隊列,找出要過期的 bucket,而 ZooKeeper 每次只會讓一個 bucket 的會話過期,每當要進行會話過期操作時,ZooKeeper 會喚醒一個處于休眠狀態(tài)的線程進行會話過期操作,之后會按照上面介紹的操作檢索過期隊列,取出過期的會話后會執(zhí)行過期操作。 ACL權(quán)限ZooKeeper的ACL可針對znodes設(shè)置相應的權(quán)限信息。 一個 ACL 權(quán)限設(shè)置通常可以分為 3 部分,分別是:權(quán)限模式(Scheme)、授權(quán)對象(ID)、權(quán)限信息(Permission)。
「權(quán)限模式:Scheme」 ZooKeeper 的權(quán)限驗證方式大體分為兩種類型,一種是范圍驗證,另外一種是口令驗證。 ? 所謂的范圍驗證就是說 ZooKeeper 可以針對一個 IP 或者一段 IP 地址授予某種權(quán)限。 比如我們可以讓一個 IP 地址為 或者也可以通過 ? 可以理解為用戶名密碼的方式,這是我們最熟悉也是日常生活中經(jīng)常使用的模式,比如我們打開自己的電腦或者去銀行取錢都需要提供相應的密碼。 在 ZooKeeper 中這種驗證方式是 Digest 認證,我們知道通過網(wǎng)絡(luò)傳輸相對來說并不安全,所以絕不通過明文在網(wǎng)絡(luò)發(fā)送密碼也是程序設(shè)計中很重要的原則之一,而 Digest 這種認證方式首先在客戶端傳送 ? 權(quán)限模式 Super 可以認為是一種特殊的 Digest 認證。 具有 Super 權(quán)限的客戶端可以對 ZooKeeper 上的任意數(shù)據(jù)節(jié)點進行任意操作。 下面這段代碼給出了 Digest 模式下客戶端的調(diào)用方式。 ? 對其創(chuàng)建的節(jié)點要想進行修改應該怎么做呢? 我們可以通過「super 模式」即超級管理員的方式刪除該節(jié)點或變更該節(jié)點的權(quán)限驗證方式。 正因為「super 模式」有如此大的權(quán)限,我們在平時使用時也應該更加謹慎。 ? 這種授權(quán)模式對應于系統(tǒng)中的所有用戶,本質(zhì)上起不到任何作用。 設(shè)置了 world 權(quán)限模式系統(tǒng)中的所有用戶操作都可以不進行權(quán)限驗證。 「授權(quán)對象(ID)」 所謂的授權(quán)對象就是說我們要把權(quán)限賦予誰,而對應于 4 種不同的權(quán)限模式來說,如果我們選擇采用 IP 方式,使用的授權(quán)對象可以是一個 IP 地址或 IP 地址段;而如果使用 Digest 或 Super 方式,則對應于一個用戶名。 如果是 World 模式,是授權(quán)系統(tǒng)中所有的用戶。 「權(quán)限信息(Permission)」 權(quán)限就是指我們可以在數(shù)據(jù)節(jié)點上執(zhí)行的操作種類,在 ZooKeeper 中已經(jīng)定義好的權(quán)限有 5 種:
? 「實現(xiàn)自己的權(quán)限口控制」 雖然 ZooKeeper 自身的權(quán)限控制機制已經(jīng)做得很細,但是它還是提供了一種權(quán)限擴展機制來讓用戶實現(xiàn)自己的權(quán)限控制方式。 官方文檔中對這種機制的定義是 ? 實現(xiàn)了自定義權(quán)限后,如何才能讓 ZooKeeper 服務(wù)端使用自定義的權(quán)限驗證方式呢? 接下來就需要將自定義的權(quán)限控制注冊到 ZooKeeper 服務(wù)器中,而注冊的方式通常有兩種。
「實現(xiàn)原理」 首先是封裝該請求的類型,之后將權(quán)限信息封裝到 request 中并發(fā)送給服務(wù)端。而服務(wù)器的實現(xiàn)比較復雜,首先分析請求類型是否是權(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)接口進行授權(quán)。 序列化方式在 ZooKeeper 中并沒有采用和 Java 一樣的序列化方式,而是采用了一個 Jute 的序列解決方案作為 ZooKeeper 框架自身的序列化方式。 ? 雖然 ZooKeeper 一直將 Jute 框架作為序列化解決方案,但這并不意味著 Jute 相對其他框架性能更好,反倒是 Apache Avro、Thrift 等框架在性能上優(yōu)于前者。 之所以 ZooKeeper 一直采用 Jute 作為序列化解決方案,主要是新老版本的兼容等問題。 「如何 使用 Jute 實現(xiàn)序列化」 如果我們要想將某個定義的類進行序列化,首先需要該類實現(xiàn) Record 接口的 serilize 和 deserialize 方法,這兩個方法分別是序列化和反序列化方法。 ? 首先,我們定義了一個 在序列化方法 serialize 中,我們要實現(xiàn)的邏輯是,首先通過字符類型參數(shù) tag 傳遞標記序列化標識符,之后使用 writeLong 和 writeString 等方法分別將對象屬性字段進行序列化。 調(diào)用 derseralize 在實現(xiàn)反序列化的過程則與我們上邊說的序列化過程正好相反。 序列化和反序列化的實現(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ù)器,在集群運行期間這三種服務(wù)器所負責的工作各不相同:
在 ZooKeeper 集群接收到來自客戶端的會話請求操作后,首先會判斷該條請求是否是事務(wù)性的會話請求。 ? Leader 服務(wù)器內(nèi)部執(zhí)行該條事務(wù)性的會話請求后,再將數(shù)據(jù)同步給其他角色服務(wù)器,從而保證事務(wù)性會話請求的執(zhí)行順序,進而保證整個 ZooKeeper 集群的數(shù)據(jù)一致性。 ? 在 ZooKeeper 集群內(nèi)部,集群中除 Leader 服務(wù)器外的其他角色服務(wù)器接收到來自客戶端的事務(wù)性會話請求后,必須將該條會話請求轉(zhuǎn)發(fā)給 Leader 服務(wù)器進行處理。 ZooKeeper 集群中的 Follow 和 Observer 服務(wù)器,都會檢查當前接收到的會話請求是否是事務(wù)性的請求,如果是事務(wù)性的請求,那么就將該請求以 REQUEST 消息類型轉(zhuǎn)發(fā)給 Leader 服務(wù)器。 在 ZooKeeper集群中的服務(wù)器接收到該條消息后,會對該條消息進行解析。
當一個業(yè)務(wù)場景在查詢操作多而創(chuàng)建刪除等事務(wù)性操作少的情況下,ZooKeeper 集群的性能表現(xiàn)的就會很好。 ? 在處理性能上整個集群服務(wù)器的瓶頸取決于 Leader 服務(wù)器的性能。 ? 這也是 ZooKeeper 設(shè)計的一個缺點。 「Leader選舉」 Leader 服務(wù)器的選舉操作主要發(fā)生在兩種情況下。 第一種就是 ZooKeeper 集群服務(wù)啟動的時候,第二種就是在 ZooKeeper 集群中舊的 Leader 服務(wù)器失效時,這時 ZooKeeper 集群需要選舉出新的 Leader 服務(wù)器。 ? ? 服務(wù)器具有四種狀態(tài),分別是LOOKING、FOLLOWING、LEADING、OBSERVING。
「事務(wù)ID(zxid)」 Zookeeper的狀態(tài)變化,都會由一個Zookeeper事務(wù)ID(ZXID)標識。 ? 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ù)器角色變更、集群同步這幾個步驟。 ? 在 ZooKeeper 集群中,當 Leader 服務(wù)器失效時,ZooKeeper 集群會重新選舉出新的 Leader 服務(wù)器。
首先,F(xiàn)ollow 服務(wù)器會定期向 Leader 服務(wù)器發(fā)送 網(wǎng)絡(luò)請求,在接收到請求后,Leader 服務(wù)器會返回響應數(shù)據(jù)包給 Follow 服務(wù)器,而在 Follow 服務(wù)器接收到 Leader 服務(wù)器的響應后,如果判斷 Leader 服務(wù)器運行正常,則繼續(xù)進行數(shù)據(jù)同步和服務(wù)轉(zhuǎn)發(fā)等工作,反之,則進行 Leader 服務(wù)器的重新選舉操作。 ? 當 Follow 服務(wù)器向 Leader 服務(wù)器發(fā)送狀態(tài)請求包后,如果沒有得到 Leader 服務(wù)器的返回信息,這時,如果是集群中個別的 Follow 服務(wù)器發(fā)現(xiàn)返回錯誤,并不會導致 ZooKeeper 集群立刻重新選舉 Leader 服務(wù)器,而是將該 Follow 服務(wù)器的狀態(tài)變更為 LOOKING 狀態(tài),并向網(wǎng)絡(luò)中發(fā)起投票,當 ZooKeeper 集群中有更多的機器發(fā)起投票,最后當投票結(jié)果滿足多數(shù)原則的情況下。 ZooKeeper 會重新選舉出 Leader 服務(wù)器。 ? 在 ZooKeeper 集群中,F(xiàn)ollow 服務(wù)器作為 Leader 服務(wù)器的候選者,當被選舉為 Leader 服務(wù)器之后,其在 ZooKeeper 集群中的 Follow 角色,也隨之發(fā)生改變。也就是要轉(zhuǎn)變?yōu)?Leader 服務(wù)器,并作為 ZooKeeper 集群中的 Leader 角色服務(wù)器對外提供服務(wù)。 ? 在 ZooKeeper 集群成功選舉 Leader 服務(wù)器,并且候選 Follow 服務(wù)器的角色變更后。 為避免在這期間導致的數(shù)據(jù)不一致問題,ZooKeeper 集群在對外提供服務(wù)之前,會通過 Leader 角色服務(wù)器管理同步其他角色服務(wù)器。 「底層實現(xiàn)」 首先,ZooKeeper 集群會先判斷 Leader 服務(wù)器是否失效,而判斷的方式就是 Follow 服務(wù)器向 Leader 服務(wù)器發(fā)送請求包,之后 Follow 服務(wù)器接收到響應數(shù)據(jù)后,進行解析,F(xiàn)ollow 服務(wù)器會根據(jù)返回的數(shù)據(jù),判斷 Leader 服務(wù)器的運行狀態(tài),如果返回的是 LOOKING 關(guān)鍵字,表明與集群中 Leader 服務(wù)器無法正常通信。
該類實現(xiàn)了 TCP 方式的通信連接,用于在 ZooKeeper 集群中與其他 Follow 服務(wù)器進行協(xié)調(diào)溝通。 FastLeaderElection 類繼承了 Election 接口,定義其是用來進行選舉的實現(xiàn)類。
在選舉的過程中,首先調(diào)用 ToSend 函數(shù)向 ZooKeeper 集群中的其他角色服務(wù)器發(fā)送本機的投票信息,其他服務(wù)器在接收投票信息后,會對投票信息進行有效性驗證等操作,之后 ZooKeeper 集群統(tǒng)計投票信息,如果過半數(shù)的機器投票信息一致,則集群就重新選出新的 Leader 服務(wù)器。 ? 因此,發(fā)送到 ZooKeeper 集群中的事務(wù)性會話會被掛起,暫時不執(zhí)行,等到選舉出新的 Leader 服務(wù)器后再進行操作。 「Observer」 在 ZooKeeper 集群服務(wù)運行的過程中,Observer 服務(wù)器與 Follow 服務(wù)器具有一個相同的功能,那就是負責處理來自客戶端的諸如查詢數(shù)據(jù)節(jié)點等非事務(wù)性的會話請求操作。
在早期的 ZooKeeper 集群服務(wù)運行過程中,只有 Leader 服務(wù)器和 Follow 服務(wù)器。 不過隨著 ZooKeeper 在分布式環(huán)境下的廣泛應用,早期模式的設(shè)計缺點也隨之產(chǎn)生,主要帶來的問題有如下幾點:
其中最主要的問題在于,當 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)操作的投票也就變得越來越復雜,并且 Follow 服務(wù)器之間彼此的網(wǎng)絡(luò)通信也變得越來越耗時,導致隨著 Follow 服務(wù)器數(shù)量的逐步增加,事務(wù)性的處理性能反而變得越來越低。
Observer 可以處理 ZooKeeper 集群中的非事務(wù)性請求,并且不參與 Leader 節(jié)點等投票相關(guān)的操作。 這樣既保證了 ZooKeeper 集群性能的擴展性,又避免了因為過多的服務(wù)器參與投票相關(guān)的操作而影響 ZooKeeper 集群處理事務(wù)性會話請求的能力。
因此,可以將 Observer 服務(wù)器部署在不同的網(wǎng)絡(luò)區(qū)間中,這樣也不會影響整個 ZooKeeper 集群的性能,也就是所謂的跨域部署。 「在我們?nèi)粘J褂?ZooKeeper 集群服務(wù)器的時候,集群中的機器個數(shù)應該選擇奇數(shù)個?」 兩個原因: ? Zookeeper中 Leader 選舉算法采用了Zab協(xié)議。 Zab核心思想是當多數(shù) Server 寫成功,則寫成功。 舉兩個例子:
集群1與集群2都有 允許1個節(jié)點宕機 的容錯能力,但是集群2比集群1多了1個節(jié)點。在相同容錯能力的情況下,本著節(jié)約資源的原則,zookeeper集群的節(jié)點數(shù)維持奇數(shù)個更好一些。 ? 集群的腦裂通常是發(fā)生在節(jié)點之間通信不可達的情況下,集群會分裂成不同的小集群,小集群各自選出自己的master節(jié)點,導致原有的集群出現(xiàn)多個master節(jié)點的情況,這就是腦裂。 下面舉例說一下為什么采用奇數(shù)臺節(jié)點,就可以防止由于腦裂造成的服務(wù)不可用: 假如zookeeper集群有 5 個節(jié)點,發(fā)生了腦裂,腦裂成了A、B兩個小集群:
可以看出,上面這兩種情況下,A、B中總會有一個小集群滿足 可用節(jié)點數(shù)量 > 總節(jié)點數(shù)量/2 。 所以zookeeper集群仍然能夠選舉出leader , 仍然能對外提供服務(wù),只不過是有一部分節(jié)點失效了而已。 假如zookeeper集群有4個節(jié)點,同樣發(fā)生腦裂,腦裂成了A、B兩個小集群:
因為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ù)不一致的情況。 ? ZAB 協(xié)議算法(Zookeeper Atomic Broadcast ,Zookeeper 原子廣播協(xié)議)是 ZooKeeper 專門設(shè)計用來解決集群最終一致性問題的算法,它的兩個核心功能點是崩潰恢復和原子廣播協(xié)議。
當接收到來自客戶端的事務(wù)性會話請求后,系統(tǒng)集群采用主服務(wù)器來處理該條會話請求,經(jīng)過主服務(wù)器處理的結(jié)果會通過網(wǎng)絡(luò)發(fā)送給集群中其他從節(jié)點服務(wù)器進行數(shù)據(jù)同步操作。 ? 當 ZooKeeper 集群接收到來自客戶端的事務(wù)性的會話請求后,集群中的其他 Follow 角色服務(wù)器會將該請求轉(zhuǎn)發(fā)給 Leader 角色服務(wù)器進行處理。 當 Leader 節(jié)點服務(wù)器在處理完該條會話請求后,會將結(jié)果通過操作日志的方式同步給集群中的 Follow 角色服務(wù)器。 然后 Follow 角色服務(wù)器根據(jù)接收到的操作日志,在本地執(zhí)行相關(guān)的數(shù)據(jù)處理操作,最終完成整個 ZooKeeper 集群對客戶端會話的處理工作。 「崩潰恢復」 當集群中的 Leader 發(fā)生故障的時候,整個集群就會因為缺少 Leader 服務(wù)器而無法處理來自客戶端的事務(wù)性的會話請求。 ? 崩潰恢復機制是保證 ZooKeeper 集群服務(wù)高可用的關(guān)鍵。觸發(fā) ZooKeeper 集群執(zhí)行崩潰恢復的事件是集群中的 Leader 節(jié)點服務(wù)器發(fā)生了異常而無法工作,于是 Follow 服務(wù)器會通過投票來決定是否選出新的 Leader 節(jié)點服務(wù)器。 ? 當崩潰恢復機制開始的時候,整個 ZooKeeper 集群的每臺 Follow 服務(wù)器會發(fā)起投票,并同步給集群中的其他 Follow 服務(wù)器。 在接收到來自集群中的其他 Follow 服務(wù)器的投票信息后,集群中的每個 Follow 服務(wù)器都會與自身的投票信息進行對比,如果判斷新的投票信息更合適,則采用新的投票信息作為自己的投票信息。在集群中的投票信息還沒有達到超過半數(shù)原則的情況下,再進行新一輪的投票,最終當整個 ZooKeeper 集群中的 Follow 服務(wù)器超過半數(shù)投出的結(jié)果相同的時候,就會產(chǎn)生新的 Leader 服務(wù)器。 ? 以 Fast Leader Election 選舉的實現(xiàn)方式來講,如下圖所示,一個選票的整體結(jié)果可以分為一下六個部分: ![]()
當 ZooKeeper 集群需要重新選舉出新的 Leader 服務(wù)器的時候,就會根據(jù)上面介紹的投票信息內(nèi)容進行對比,以找出最適合的服務(wù)器。 ? 當一臺 Follow 服務(wù)器接收到網(wǎng)絡(luò)中的其他 Follow 服務(wù)器的投票信息后,是如何進行對比來更新自己的投票信息的。 Follow 服務(wù)器進行選票對比的過程,如下圖所示。 ![]() 首先,會對比 logicClock 服務(wù)器的投票輪次,當 logicClock 相同時,表明兩張選票處于相同的投票階段,并進入下一階段,否則跳過。 接下來再對比 要是對比的結(jié)果相同,則繼續(xù)對比 經(jīng)過這些對比和替換后,最終該臺 Follow 服務(wù)器會產(chǎn)生新的投票信息,并在下一輪的投票中發(fā)送到 ZooKeeper 集群中。 「消息廣播」 在 Leader 節(jié)點服務(wù)器處理請求后,需要通知集群中的其他角色服務(wù)器進行數(shù)據(jù)同步。ZooKeeper 集群采用消息廣播的方式發(fā)送通知。 ZooKeeper 集群使用原子廣播協(xié)議進行消息發(fā)送,該協(xié)議的底層實現(xiàn)過程與二階段提交過程非常相似,如下圖所示。 ![]() 當要在集群中的其他角色服務(wù)器進行數(shù)據(jù)同步的時候,Leader 服務(wù)器將該操作過程封裝成一個 Proposal 提交事務(wù),并將其發(fā)送給集群中其他需要進行數(shù)據(jù)同步的服務(wù)器。 當這些服務(wù)器接收到 Leader 服務(wù)器的數(shù)據(jù)同步事務(wù)后,會將該條事務(wù)能否在本地正常執(zhí)行的結(jié)果反饋給 Leader 服務(wù)器,Leader 服務(wù)器在接收到其他 Follow 服務(wù)器的反饋信息后進行統(tǒng)計,判斷是否在集群中執(zhí)行本次事務(wù)操作。 這里請注意 ,與二階段提交過程不同(即需要集群中所有服務(wù)器都反饋可以執(zhí)行事務(wù)操作后,主服務(wù)器再次發(fā)送 commit 提交請求執(zhí)行數(shù)據(jù)變更) ,ZAB 協(xié)議算法省去了中斷的邏輯,當 ZooKeeper 集群中有超過一半的 Follow 服務(wù)器能夠正常執(zhí)行事務(wù)操作后,整個 ZooKeeper 集群就可以提交 Proposal 事務(wù)了。 日志清理「日志類型」 在 ZooKeeper 服務(wù)運行的時候,一般會產(chǎn)生數(shù)據(jù)快照和日志文件,數(shù)據(jù)快照用于集群服務(wù)中的數(shù)據(jù)同步,而數(shù)據(jù)日志則記錄了 ZooKeeper 服務(wù)運行的相關(guān)狀態(tài)信息。 ? 「清理方案」 如上面所介紹的,面對生產(chǎn)系統(tǒng)中產(chǎn)生的日志,一般的維護操作是備份和清理。 備份是為了之后對系統(tǒng)的運行情況進行排查和優(yōu)化,而清理主要因為隨著系統(tǒng)日志的增加,日志會逐漸占用系統(tǒng)的存儲空間,如果一直不進行清理,可能耗盡系統(tǒng)的磁盤存儲空間,并最終影響服務(wù)的運行。 「清理工具」 ? 首先,我們介紹的是 Linux corntab ,它是 Linux 系統(tǒng)下的軟件,可以自動地按照我們設(shè)定的時間,周期性地執(zhí)行我們編寫的相關(guān)腳本。 crontab 定時腳本的方式相對靈活,可以按照我們的業(yè)務(wù)需求來設(shè)置處理日志的維護方式,比如這里我們希望定期清除 ZooKeeper 服務(wù)運行的日志,而不想清除數(shù)據(jù)快照的文件,則可以通過腳本設(shè)置,達到只對數(shù)據(jù)日志文件進行清理的目的。 ? ZooKeeper 自身還提供了 PurgeTxnLog 工具類,用來清理 snapshot 數(shù)據(jù)快照文件和系統(tǒng)日志。 PurgeTxnLog 清理方式和我們上面介紹的方式十分相似,也是通過定時腳本執(zhí)行任務(wù),唯一的不同是,上面提到在編寫日志清除 logsCleanWeek 的時候 ,我們使用的是原生 shell 腳本自己手動編寫的數(shù)據(jù)日志清理邏輯,而使用 PurgeTxnLog 則可以在編寫清除腳本的時候調(diào)用 ZooKeeper 為我們提供的工具類完成日志清理工作。 如下面的代碼所示,首先,我們在 在腳本中我們只需要編寫 PurgeTxnLog 類的調(diào)用程序,系統(tǒng)就會自動通過 PurgeTxnLog 工具類為我們完成對應日志文件的清理工作。 PurgeTxnLog 方式與 crontab 相比,使用起來更加容易而且也更加穩(wěn)定安全,不過 crontab 方式更加靈活,我們可以根據(jù)不同的業(yè)務(wù)需求編寫自己的清理邏輯。 實現(xiàn)分布式鎖分布式鎖的目的是保證在分布式部署的應用集群中,多個服務(wù)在請求同一個方法或者同一個業(yè)務(wù)操作的情況下,對應業(yè)務(wù)邏輯只能被一臺機器上的一個線程執(zhí)行,避免出現(xiàn)并發(fā)問題。 ? 「方案一:」 使用節(jié)點中的存儲數(shù)據(jù)區(qū)域,ZK中節(jié)點存儲數(shù)據(jù)的大小不能超過1M,但是只是存放一個標識是足夠的,線程獲得鎖時,先檢查該標識是否是無鎖標識,若是可修改為占用標識,使用完再恢復為無鎖標識 「方案二:」 使用子節(jié)點,每當有線程來請求鎖的時候,便在鎖的節(jié)點下創(chuàng)建一個子節(jié)點,子節(jié)點類型必須維護一個順序,對子節(jié)點的自增序號進行排序,默認總是最小的子節(jié)點對應的線程獲得鎖,釋放鎖時刪除對應子節(jié)點便可 ![]() 「死鎖風險:」 兩種方案其實都是可行的,但是使用鎖的時候一定要去規(guī)避死鎖
「避免羊群效應」 把鎖請求者按照后綴數(shù)字進行排隊,后綴數(shù)字小的鎖請求者先獲取鎖。 如果所有的鎖請求者都 watch 鎖持有者,當代表鎖請求者的 znode 被刪除以后,所有的鎖請求者都會通知到,但是只有一個鎖請求者能拿到鎖。這就是羊群效應。 ? 每次鎖被釋放,只會有一個鎖請求者 會被通知到。 這樣做還讓鎖的分配具有公平性,鎖定的分配遵循先到先得的原則。 ![]() 「用 ZooKeeper 實現(xiàn)分布式鎖的算法流程,根節(jié)點為 /lock:」
在實際開發(fā)中,可以應用 Apache Curator 來快速實現(xiàn)分布式鎖,Curator 是 Netflix 公司開源的一個 ZooKeeper 客戶端,對 ZooKeeper 原生 API 做了抽象和封裝。 實現(xiàn)分布式ID我們可以通過 ZooKeeper 自身的客戶端和服務(wù)器運行模式,來實現(xiàn)一個分布式網(wǎng)絡(luò)環(huán)境下的 ID 請求和分發(fā)過程。 ? 客戶端通過發(fā)送請求到 ZooKeeper 服務(wù)器,來獲取編碼信息,服務(wù)端接收到請求后,發(fā)送 ID 編碼給客戶端。 「實現(xiàn)原理:」 可以利用 ZooKeeper 數(shù)據(jù)模型中的順序節(jié)點作為 ID 編碼。
利用 ZooKeeper 中的順序節(jié)點特性,很容易使我們創(chuàng)建的 ID 編碼具有有序的特性。并且我們也可以通過客戶端傳遞節(jié)點的名稱,根據(jù)不同的業(yè)務(wù)編碼區(qū)分不同的業(yè)務(wù)系統(tǒng),從而使編碼的擴展能力更強。 ? 其中最主要的是,在定義編碼的規(guī)則上還是強烈依賴于程序員自身的能力和對業(yè)務(wù)的深入理解。 很容易出現(xiàn)因為考慮不周,造成設(shè)置的規(guī)則在運行一段時間后,無法滿足業(yè)務(wù)要求或者安全性不夠等問題。 實現(xiàn)負載均衡「常見負載均衡算法」 ? 輪詢法是最為簡單的負載均衡算法,當接收到來自網(wǎng)絡(luò)中的客戶端請求后,負載均衡服務(wù)器會按順序逐個分配給后端服務(wù)。 比如集群中有 3 臺服務(wù)器,分別是 server1、server2、server3,輪詢法會按照 sever1、server2、server3 這個順序依次分發(fā)會話請求給每個服務(wù)器。當?shù)谝淮屋喸兘Y(jié)束后,會重新開始下一輪的循環(huán)。 ? 隨機算法是指負載均衡服務(wù)器在接收到來自客戶端的請求后,會根據(jù)一定的隨機算法選中后臺集群中的一臺服務(wù)器來處理這次會話請求。 不過,當集群中備選機器變的越來越多時,通過統(tǒng)計學我們可以知道每臺機器被抽中的概率基本相等,因此隨機算法的實際效果越來越趨近輪詢算法。 ? 原地址哈希算法的核心思想是根據(jù)客戶端的 IP 地址進行哈希計算,用計算結(jié)果進行取模后,根據(jù)最終結(jié)果選擇服務(wù)器地址列表中的一臺機器,處理該條會話請求。 采用這種算法后,當同一 IP 的客戶端再次訪問服務(wù)端后,負載均衡服務(wù)器最終選舉的還是上次處理該臺機器會話請求的服務(wù)器,也就是每次都會分配同一臺服務(wù)器給客戶端。 ? 加權(quán)輪詢的方式與輪詢算法的方式很相似,唯一的不同在于選擇機器的時候,不只是單純按照順序的方式選擇,還根據(jù)機器的配置和性能高低有所側(cè)重,配置性能好的機器往往首先分配。 ? 加權(quán)隨機法和我們上面提到的隨機算法一樣,在采用隨機算法選舉服務(wù)器的時候,會考慮系統(tǒng)性能作為權(quán)值條件。 ? 最小連接數(shù)算法是指,根據(jù)后臺處理客戶端的連接會話條數(shù),計算應該把新會話分配給哪一臺服務(wù)器。 一般認為,連接數(shù)越少的機器,在網(wǎng)絡(luò)帶寬和計算性能上都有很大優(yōu)勢,會作為最優(yōu)先分配的對象。 「利用 ZooKeeper 實現(xiàn) 負載均衡 算法」 ? 如下圖所示,建立的 ZooKeeper 數(shù)據(jù)模型中 Severs 節(jié)點可以作為存儲服務(wù)器列表的父節(jié)點。 在它下面創(chuàng)建 servers_host1、servers_host2、servers_host3等臨時節(jié)點來存儲集群中的服務(wù)器運行狀態(tài)信息。 ![]() 整個實現(xiàn)的過程如下圖所示。 ![]()
「這里注意:」 我們?nèi)粘S玫降呢撦d均衡器主要是選擇后臺處理的服務(wù)器,并給其分發(fā)請求。 ? 在請求分發(fā)的過程中,還是通過負載算法計算出要訪問的服務(wù)器,之后客戶端自己連接該服務(wù)器,完成請求操作。 開源框架使用案例「Dubbo與ZooKeeper」 Dubbo 是阿里巴巴開發(fā)的一套開源的技術(shù)框架,是一款高性能、輕量級的開源 Java RPC 框架。 「用ZooKeeper做注冊中心」 在整個 Dubbo 框架的實現(xiàn)過程中,注冊中心是其中最為關(guān)鍵的一點,它保證了整個 PRC 過程中服務(wù)對外的透明性。 而 Dubbo 的注冊中心也是通過 ZooKeeper 來實現(xiàn)的。 如下圖所示,在整個 Dubbo 服務(wù)的啟動過程中,服務(wù)提供者會在啟動時向 服務(wù)消費者在啟動時訂閱 該操作是通過 ZooKeeper 服務(wù)器在 /consumers 節(jié)點路徑下創(chuàng)建一個子數(shù)據(jù)節(jié)點,然后再在請求會話中發(fā)起對 /providers 節(jié)點的 watch 監(jiān)控 ![]() 「Kafka與ZooKeeper」 「Zookeeper的作用」 由于 Broker 服務(wù)器采用分布式集群的方式工作,那么在服務(wù)的運行過程中,難免出現(xiàn)某臺機器因異常而關(guān)閉的狀況。 為了保證整個 Kafka 集群的可用性,需要在系統(tǒng)中監(jiān)控整個機器的運行情況。而 Kafka 可以通過 ZooKeeper 中的數(shù)據(jù)節(jié)點,將網(wǎng)絡(luò)中機器的運行統(tǒng)計存儲在數(shù)據(jù)模型中的 brokers 節(jié)點下。 在 Kafka 的 Topic 信息注冊中也需要使用到 ZooKeeper ,在 Kafka 中同一個Topic 消息容器可以分成多個不同片,而這些分區(qū)既可以存在于一臺 Broker 服務(wù)器中,也可以存在于不同的 Broker 服務(wù)器中。 而在 Kafka 集群中,每臺 Broker 服務(wù)器又相對獨立。 為了能夠讀取這些以分布式方式存儲的分區(qū)信息,Kafka 會將這些分區(qū)信息在 Broker 服務(wù)器中的對應關(guān)系存儲在 ZooKeeper 數(shù)據(jù)模型的 topic 節(jié)點上,每一個 topic 在 ZooKeeper 數(shù)據(jù)節(jié)點上都會以 ![]() 參考資料《從Paxos到Zookeeper 分布式一致性原理與實踐》 |
|
|
來自: 昵稱10087950 > 《中間件》