<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ZooKeeper 常見(jiàn)知識(shí)點(diǎn)&面試題總結(jié)!

          共 24233字,需瀏覽 49分鐘

           ·

          2022-03-10 19:11

          ?《Java 面試指北》來(lái)啦!歡迎準(zhǔn)備面試的小伙伴加入我的知識(shí)星球閱讀!近期文章精選?:

          你好,我是 Guide。今天這篇介紹一下ZooKeeper!

          「文章較長(zhǎng),可以點(diǎ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ì)話(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ì)話(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ì)話(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ì)話(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)楫惓6P(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ì)話(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ì)話(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ì)話(huà)的連接狀態(tài)和客戶(hù)端注冊(cè)事件的類(lèi)型,所以當(dāng)客戶(hù)端會(huì)話(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ì)話(huà)機(jī)制

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

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

          ?

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

          ?

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

          • 會(huì)話(huà) ID:會(huì)話(huà) ID 作為一個(gè)會(huì)話(huà)的標(biāo)識(shí)符,當(dāng)我們創(chuàng)建一次會(huì)話(huà)的時(shí)候,ZooKeeper 會(huì)自動(dòng)為其分配一個(gè)唯一的 ID 編碼。

          • 會(huì)話(huà)超時(shí)時(shí)間:一般來(lái)說(shuō),一個(gè)會(huì)話(huà)的超時(shí)時(shí)間就是指一次會(huì)話(huà)從發(fā)起后到被服務(wù)器關(guān)閉的時(shí)長(zhǎng)。而設(shè)置會(huì)話(huà)超時(shí)時(shí)間后,服務(wù)器會(huì)參考設(shè)置的超時(shí)時(shí)間,最終計(jì)算一個(gè)服務(wù)端自己的超時(shí)時(shí)間。而這個(gè)超時(shí)時(shí)間則是最終真正用于 ZooKeeper 中服務(wù)端用戶(hù)會(huì)話(huà)管理的超時(shí)時(shí)間。

          • 會(huì)話(huà)關(guān)閉狀態(tài):會(huì)話(huà)關(guān)閉 isClosing 狀態(tài)屬性字段表示一個(gè)會(huì)話(huà)是否已經(jīng)關(guān)閉。如果服務(wù)器檢查到一個(gè)會(huì)話(huà)已經(jīng)因?yàn)槌瑫r(shí)等原因失效時(shí), ZooKeeper 會(huì)在該會(huì)話(huà)的 isClosing 屬性值標(biāo)記為關(guān)閉,再之后就不對(duì)該會(huì)話(huà)進(jìn)行操作了。

          「會(huì)話(huà)狀態(tài)」

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

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

          ?

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

          ?

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

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

          ?

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

          ?

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

          「會(huì)話(huà)異常」

          在 ZooKeeper 中,會(huì)話(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ì)話(huà)。

          • 這是因?yàn)?ZooKeeper 實(shí)際起作用的超時(shí)時(shí)間是通過(guò)客戶(hù)端和服務(wù)端協(xié)商決定。

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

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

          「分桶策略」

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

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

          ZooKeeper 服務(wù)端接收請(qǐng)求后,會(huì)更新會(huì)話(huà)的過(guò)期時(shí)間,來(lái)保證會(huì)話(huà)的存活狀態(tài)。

          • 所以在 ZooKeeper 的會(huì)話(huà)管理中,最主要的工作就是管理會(huì)話(huà)的過(guò)期時(shí)間。
          ?

          ZooKeeper 中采用了獨(dú)特的會(huì)話(huà)管理方式來(lái)管理會(huì)話(huà)的過(guò)期時(shí)間。

          ?

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

          這就降低了會(huì)話(huà)管理的難度,因?yàn)槊看涡∨康奶幚頃?huì)話(huà)過(guò)期也提高了會(huì)話(huà)處理的效率。

          「ZooKeeper 這種會(huì)話(huà)管理的好處?」

          ZooKeeper 這種分段的會(huì)話(huà)管理策略大大提高了計(jì)算會(huì)話(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ì)話(huà)進(jìn)行管理。

          在這種場(chǎng)景下,要想通過(guò)對(duì)每一個(gè)會(huì)話(huà)進(jìn)行管理和檢查并不合適,所以采用將同一個(gè)時(shí)間段的會(huì)話(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ì)話(huà)過(guò)期的相關(guān)操作都是圍繞這個(gè)隊(duì)列進(jìn)行的。

          • 可以說(shuō) ZooKeeper 底層就是采用這個(gè)隊(duì)列結(jié)構(gòu)來(lái)管理會(huì)話(huà)過(guò)期的。

          「一個(gè)會(huì)話(huà)過(guò)期隊(duì)列是由若干個(gè) bucket 組成的。」

          • 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à)。

          將會(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ì)話(huà)過(guò)期,每當(dāng)要進(jìn)行會(huì)話(huà)過(guò)期操作時(shí),ZooKeeper 會(huì)喚醒一個(gè)處于休眠狀態(tài)的線程進(jìn)行會(huì)話(huà)過(guò)期操作,之后會(huì)按照上面介紹的操作檢索過(guò)期隊(duì)列,取出過(guò)期的會(huì)話(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)?strong style="color: rgb(53, 148, 247);">「super 模式」有如此大的權(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ì)話(huà)的授權(quán)信息存儲(chǔ)在 ZooKeeper 服務(wù)端的內(nèi)存中,如果客戶(hù)端會(huì)話(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ì)話(huà)請(qǐng)求操作后,首先會(huì)判斷該條請(qǐng)求是否是事務(wù)性的會(huì)話(huà)請(qǐng)求。

          ?

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

          所謂事務(wù)性請(qǐng)求,是指 ZooKeeper 服務(wù)器執(zhí)行完該條會(huì)話(huà)請(qǐng)求后,是否會(huì)導(dǎo)致執(zhí)行該條會(huì)話(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ì)話(huà)請(qǐng)求后,再將數(shù)據(jù)同步給其他角色服務(wù)器,從而保證事務(wù)性會(huì)話(huà)請(qǐng)求的執(zhí)行順序,進(jìn)而保證整個(gè) ZooKeeper 集群的數(shù)據(jù)一致性。

          ?

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

          ?

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

          ZooKeeper 集群中的 Follow 和 Observer 服務(wù)器,都會(huì)檢查當(dāng)前接收到的會(huì)話(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ì)話(huà)請(qǐng)求。

          • 之后將該條消息提交到自己的 Leader 服務(wù)器請(qǐng)求處理鏈中,開(kāi)始進(jìn)行事務(wù)性的會(huì)話(huà)請(qǐng)求操作。

          • 如果不是事務(wù)性請(qǐng)求,ZooKeeper 集群則交由 Follow 和 Observer 角色服務(wù)器處理該條會(huì)話(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ì)話(huà)請(qǐng)求而沒(méi)有查詢(xún)操作,那么 Follow 和 Observer 服務(wù)器就只能充當(dāng)一個(gè)請(qǐng)求轉(zhuǎn)發(fā)服務(wù)器的角色, 所有的會(huì)話(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à)會(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ì)話(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ì)話(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)是正常的。換句話(huà)說(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)是正常的。換句話(huà)說(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ì)話(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ì)話(huà)請(qǐng)求后,系統(tǒng)集群采用主服務(wù)器來(lái)處理該條會(huì)話(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ì)話(huà)請(qǐng)求后,集群中的其他 Follow 角色服務(wù)器會(huì)將該請(qǐng)求轉(zhuǎn)發(fā)給 Leader 角色服務(wù)器進(jìn)行處理。

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

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

          「崩潰恢復(fù)」

          當(dāng)集群中的 Leader 發(fā)生故障的時(shí)候,整個(gè)集群就會(huì)因?yàn)槿鄙?Leader 服務(wù)器而無(wú)法處理來(lái)自客戶(hù)端的事務(wù)性的會(huì)話(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_zxidvote_myid更新為收到的票中的vote_zxidvote_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è)?code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">/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ì)話(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ì)話(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ì)話(huà)請(qǐng)求。

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

          ?

          加權(quán)輪詢(xún)法

          ?

          加權(quán)輪詢(xún)的方式與輪詢(xún)算法的方式很相似,唯一的不同在于選擇機(jī)器的時(shí)候,不只是單純按照順序的方式選擇,還根據(jù)機(jī)器的配置和性能高低有所側(cè)重,配置性能好的機(jī)器往往首先分配。

          ?

          加權(quán)隨機(jī)法

          ?

          加權(quán)隨機(jī)法和我們上面提到的隨機(jī)算法一樣,在采用隨機(jī)算法選舉服務(wù)器的時(shí)候,會(huì)考慮系統(tǒng)性能作為權(quán)值條件。

          ?

          最小連接數(shù)法

          ?

          最小連接數(shù)算法是指,根據(jù)后臺(tái)處理客戶(hù)端的連接會(huì)話(huà)條數(shù),計(jì)算應(yīng)該把新會(huì)話(huà)分配給哪一臺(tái)服務(wù)器。

          一般認(rèn)為,連接數(shù)越少的機(jī)器,在網(wǎng)絡(luò)帶寬和計(jì)算性能上都有很大優(yōu)勢(shì),會(huì)作為最優(yōu)先分配的對(duì)象。

          「利用 ZooKeeper 實(shí)現(xiàn) 負(fù)載均衡 算法」

          ?

          這里我們通過(guò)采用最小連接數(shù)算法,來(lái)確定究竟如何均衡地分配網(wǎng)絡(luò)會(huì)話(huà)請(qǐng)求給后臺(tái)客戶(hù)端。

          ?

          如下圖所示,建立的 ZooKeeper 數(shù)據(jù)模型中 Severs 節(jié)點(diǎn)可以作為存儲(chǔ)服務(wù)器列表的父節(jié)點(diǎn)。

          在它下面創(chuàng)建 servers_host1、servers_host2、servers_host3等臨時(shí)節(jié)點(diǎn)來(lái)存儲(chǔ)集群中的服務(wù)器運(yùn)行狀態(tài)信息。

          整個(gè)實(shí)現(xiàn)的過(guò)程如下圖所示。

          • 首先,在接收到客戶(hù)端的請(qǐng)求后,通過(guò) getData 方法獲取服務(wù)端 Severs 節(jié)點(diǎn)下的服務(wù)器列表,其中每個(gè)節(jié)點(diǎn)信息都存儲(chǔ)有當(dāng)前服務(wù)器的連接數(shù)。

          • 通過(guò)判斷選擇最少的連接數(shù)作為當(dāng)前會(huì)話(huà)的處理服務(wù)器,并通過(guò) setData 方法將該節(jié)點(diǎn)連接數(shù)加 1。

          • 最后,當(dāng)客戶(hù)端執(zhí)行完畢,再調(diào)用 setData 方法將該節(jié)點(diǎn)信息減 1。

          • 我們定義當(dāng)服務(wù)器接收到會(huì)話(huà)請(qǐng)求后。在 ZooKeeper 服務(wù)端增加連接數(shù)的 addBlance 方法。

          • 我們通過(guò) readData 方法獲取服務(wù)器最新的連接數(shù),之后將該連接數(shù)加 1,再通過(guò) writeData 方法將新的連接數(shù)信息寫(xiě)入到服務(wù)端對(duì)應(yīng)節(jié)點(diǎn)信息中。

          • 當(dāng)服務(wù)器處理完該會(huì)話(huà)請(qǐng)求后,需要更新服務(wù)端相關(guān)節(jié)點(diǎn)的連接數(shù)。

          • 具體的操作與 addBlance 方法基本一樣,只是對(duì)獲取的連接信息進(jìn)行減一操作。

          「這里注意:」

          我們?nèi)粘S玫降呢?fù)載均衡器主要是選擇后臺(tái)處理的服務(wù)器,并給其分發(fā)請(qǐng)求。

          ?

          而通過(guò) ZooKeeper 實(shí)現(xiàn)的服務(wù)器,只提供了服務(wù)器的篩選工作。

          ?

          在請(qǐng)求分發(fā)的過(guò)程中,還是通過(guò)負(fù)載算法計(jì)算出要訪問(wèn)的服務(wù)器,之后客戶(hù)端自己連接該服務(wù)器,完成請(qǐng)求操作。

          開(kāi)源框架使用案例

          「Dubbo與ZooKeeper」

          Dubbo 是阿里巴巴開(kāi)發(fā)的一套開(kāi)源的技術(shù)框架,是一款高性能、輕量級(jí)的開(kāi)源 Java RPC 框架。

          「用ZooKeeper做注冊(cè)中心」

          在整個(gè) Dubbo 框架的實(shí)現(xiàn)過(guò)程中,注冊(cè)中心是其中最為關(guān)鍵的一點(diǎn),它保證了整個(gè) PRC 過(guò)程中服務(wù)對(duì)外的透明性。

          而 Dubbo 的注冊(cè)中心也是通過(guò) ZooKeeper 來(lái)實(shí)現(xiàn)的。

          如下圖所示,在整個(gè) Dubbo 服務(wù)的啟動(dòng)過(guò)程中,服務(wù)提供者會(huì)在啟動(dòng)時(shí)向 /dubbo/com.foo.BarService/providers目錄寫(xiě)入自己的 URL 地址,這個(gè)操作可以看作是一個(gè) ZooKeeper 客戶(hù)端在 ZooKeeper 服務(wù)器的數(shù)據(jù)模型上創(chuàng)建一個(gè)數(shù)據(jù)節(jié)點(diǎn)。

          服務(wù)消費(fèi)者在啟動(dòng)時(shí)訂閱 /dubbo/com.foo.BarService/providers 目錄下的提供者 URL 地址,并向 /dubbo/com.foo.BarService/consumers 目錄寫(xiě)入自己的 URL 地址。

          該操作是通過(guò) ZooKeeper 服務(wù)器在 /consumers 節(jié)點(diǎn)路徑下創(chuàng)建一個(gè)子數(shù)據(jù)節(jié)點(diǎn),然后再在請(qǐng)求會(huì)話(huà)中發(fā)起對(duì) /providers 節(jié)點(diǎn)的 watch 監(jiān)控

          「Kafka與ZooKeeper」

          「Zookeeper的作用」

          由于 Broker 服務(wù)器采用分布式集群的方式工作,那么在服務(wù)的運(yùn)行過(guò)程中,難免出現(xiàn)某臺(tái)機(jī)器因異常而關(guān)閉的狀況。

          為了保證整個(gè) Kafka 集群的可用性,需要在系統(tǒng)中監(jiān)控整個(gè)機(jī)器的運(yùn)行情況。而 Kafka 可以通過(guò) ZooKeeper 中的數(shù)據(jù)節(jié)點(diǎn),將網(wǎng)絡(luò)中機(jī)器的運(yùn)行統(tǒng)計(jì)存儲(chǔ)在數(shù)據(jù)模型中的 brokers 節(jié)點(diǎn)下。

          在 Kafka 的 Topic 信息注冊(cè)中也需要使用到 ZooKeeper ,在 Kafka 中同一個(gè)Topic 消息容器可以分成多個(gè)不同片,而這些分區(qū)既可以存在于一臺(tái) Broker 服務(wù)器中,也可以存在于不同的 Broker 服務(wù)器中。

          而在 Kafka 集群中,每臺(tái) Broker 服務(wù)器又相對(duì)獨(dú)立。

          為了能夠讀取這些以分布式方式存儲(chǔ)的分區(qū)信息,Kafka 會(huì)將這些分區(qū)信息在 Broker 服務(wù)器中的對(duì)應(yīng)關(guān)系存儲(chǔ)在 ZooKeeper 數(shù)據(jù)模型的 topic 節(jié)點(diǎn)上,每一個(gè) topic 在 ZooKeeper 數(shù)據(jù)節(jié)點(diǎn)上都會(huì)以 /brokers/topics/[topic] 的形式存在。

          參考資料

          《從Paxos到Zookeeper 分布式一致性原理與實(shí)踐》

          ··········? END? ··············

          面試指北/免費(fèi)提問(wèn)/簡(jiǎn)歷修改/求職指南/學(xué)習(xí)打卡,歡迎加入JavaGuide 知識(shí)星球。這是星球的詳細(xì)介紹以及加入方法:JavaGuide 知識(shí)星球詳細(xì)介紹?(一定一定一定確定自己真的需要再加入,一定一定要看完詳細(xì)介紹之后再加我)。

          如果本文對(duì)你有幫助的話(huà),歡迎點(diǎn)贊&在看&分享,這對(duì)我繼續(xù)分享&創(chuàng)作優(yōu)質(zhì)文章非常重要。感謝????

          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日韩国产在线播放 | x8x8拨牐拨牐精品视频 | 色逼AV| 天天摸日日摸人人看 | 看黄色操逼网 |