Zookeeper 架構原理的淺嘗就止(上)

第一章   Zookeeper 的架構原理和使用

第 2 節

Zookeeper 架構原理的淺嘗就止(上)

那麼接下來讓我們先來看下 Zookeeper 集羣到底可以支撐什麼?

Zookeeper 集羣到底可以支撐什麼?這個不知道你有沒有考慮過。

你腦子裏可能會有這想法,Zookeeper 集羣可以抗高併發、性能很高,而且高可用。沒錯,這個是分佈式系統或者中間件一般都要支持的東西。但是 Zookeeper 還有一個區別於其他中間件最大區別的是,它可以支持或者說是實現數據的順序一致性。

那我們首先就來看看,Zookeeper 的順序一致性支撐了什麼。

順序一致性,意思就是 Zookeeper 所有的寫請求都是有序的。

要想保證這一點,一種方式就是集羣中只有一臺機器可以寫, 其他機器的寫請求都會轉發到同一臺機器上,而且所有寫請求都會分配一個 zk 集羣全局的唯一遞增編號(zxid),這樣每個寫請求都有了自己的順序 id。

也就是說,單機寫 + 順序 id 就保證客戶端發起的寫請求都是有序的了。

大體如下圖所示:

你知道了 Zookeeper 順序一致性,” 順序 “其實就是代表了順序寫的操作。那麼一致性呢?

我們都知道,數據一致性的意思是無論連接到哪臺機器上去,看到的都是一樣的數據,不能有數據不一致。也就是說每臺 Zookeeper 服務端,數據都是一樣的。

你可以思考下,和它相似的架構還有什麼呢?我給大家舉一個例子,Eureka 也是這樣的, 是 peer-peer 架構,每個節點的數據完全一致。

而且要想保證數據一致性,Zookeeper 的寫請求其實具備了原子性。原子性的意思就是要麼全部機器都成功,要麼全部機器都別成功。負責寫的那一臺 Zookeeper 機器收到了寫請求之後都會同步給其他機器,保證數據的強一致。如果你熟悉 CAP 的話,Zookeeper 的這種操作,明顯就是符合了 CP 的特點了。

有人問我,這種一致性是強一致,還是最終一致?我的回答是如果真要在這二者選一個的話,應該是最終一致。其實準確的說,Zookeeper 官方給出的就是順序一致性。是比最終一致性更好一點的一致性,因爲它保證了順序寫。

因爲這種一致性主要是根據兩階段提交 2PC 的 ZAB 協議實現的。當之後你研究了 ZooKeeper 的 ZAB 協議之後,你會發現,其實過半 Zookeeper 返回 ack,負責寫的 Zookeeper 就會發送 commit 給其他所有機器了,只要 Zookeeper 進行了 commit,這個數據就會被客戶端讀取到了。那麼有沒有可能此時有的 Zookeeper 已經 commit 了,但是有的 Zookeeper 還沒有 commit?絕對會的!但是最後當所有的 commit 提交後,所有 Zookeeper 還是會最終一致的。

2、Zookeeper 支持的順序一致性有什麼問題麼?

瞭解了 Zookeeper 可以支持順序一致性這個特點後,那你再進一步思考下,只有一臺順序寫會有什麼問題?

機器的寫 QPS 其實就會受單機限制了,是不是?每臺數據一致,是不是就代表數據沒有分佈式存儲在各個節點上,那也就是說 Zookeeper 無法支持海量數據存儲了。

這也決定了 Zookeeper 集羣只適合小集羣部署了。而且 Z****ookeeper 集羣適合讀多寫少的場景。

小集羣部署,是因爲,如果機器很大,假設有 30 臺 Zookeeper,一個寫請求出去,要起碼等待 10 臺以上的 Zookeeper 返回 ack,才能發送 commit,才能告訴你寫請求成功了,性能肯定是極差的。

適合讀多寫少的場景,是因爲,如果入寫壓力過大,比如大量的服務的上線、註冊、心跳的壓力,達到了每秒幾萬,甚至上十萬,zk 的單個 leader 寫入是扛不住那麼大的壓力的。

Zookeeper 集羣掛掉了,對一個技術公司的而言重大打擊。幾乎依賴 Zookeeper 的中間件會出問題,Zookeeper 如果是註冊中心的話,服務的上線、註冊、心跳也會有問題。導致業務系統也會有很大影響。

3、Zookeeper 集羣支撐了高可用、高併發、高性能

**高可用:**意思是哪怕 Zookeeper 集羣中掛掉一部分,只要不超過一半的機器,都能保證可用,數據不會丟失。比如,3 臺機器可以掛 1 臺,5 臺機器可以掛 2 臺。服務端可用性:負責寫的 Zookeeper 掛掉,通過自動選舉重新選出新的負責寫 Zookeeper。客戶端可用性:首先客戶端通過一個 TCP 連接和某一臺 Zookeeper 保持一個會話,一般稱作 Session,這樣客戶端就可以通過心跳線程,發現之前建連接的服務端如果掛了,Session 連接就會超時了,客戶端會重試連接新的 Zookeeper。

**高性能:**每臺 Zookeeper 機器都在內存維護數據,所以 Zookeeper 集羣的性能肯定會很高。

**高併發:**高性能決定了併發能力是很高的,只要基於純內存數據結構來處理,並且使用 NIO 的通信機制控制海量連接的建立,併發能力是很高的,比如 3 臺 Zookeeper 都是高配置的物理機,16 核 32G,寫入幾萬 QPS,讀,所有機器都可以讀,3 臺起碼可以支撐十幾萬 QPS。

具體是如何實現支撐高併發、高可用、高性能,這裏暫時不展開說明,後面第二章源碼原理會詳細剖析的。第一章這裏只是拋出來常見的 Zookeeper 知識和原理,讓大家熟悉下,不然看源碼的時候會不理解,抓不住重點等。

簡單可以總結下,如圖所示:

Zookeeper 集羣內存數據結構和節點角色劃分

再瞭解了 Zookeeper 集羣架構支撐了 3 高、順序一致性後。你可能會有一些疑問。實現高性能內存結構是什麼?哪些 Zookeeper 節點是負責寫數據的,哪些是負責讀數據的?

其實這個兩個問題比較簡單。

哪些 Zookeeper 節點是負責寫數據的,哪些是負責讀數據的:一般 Zookeeper 節點劃分爲三類角色,Leader、Follower、Observer。

簡單地講,Zookeeper 節點是負責寫數據的是 Leader,當然 Leader 也可以讀。而只能負責讀的 Zookeeper 稱爲 Follower。負責讀的 Zookeeper 節點,還有一種成爲 Observer,Observer 也是隻讀的,它和 Follower 的區別就是不會參與 Leader 選舉和 ZAB 協議實現的一致性,過半寫的環節,只是單純同步數據而已,所有可能數據存在一定的不一致的問題。

實現高性能內存結構就是:**ZNODE,它是類似於文件目錄的一個樹形結構。**一般這個內存數據節點分爲兩種,持久化節點,臨時節點。持久節點就是哪怕客戶端斷開連接,一直存在。臨時節點,就是隻要客戶端斷開連接,節點就沒了。

如果算上保證順序性的話,額外還會有兩種,持久化順序節點和臨時順序節點。順序節點,最大的特點就是創建節點的時候自增增加全局遞增的序號。而且每個 znode 節點還可以做操作的權限控制(ACL)。

**說白了,其實這些本質都是在 ZNODE 中設置一些屬性來實現的而已。**這裏只是先簡單介紹下,讓你們回顧一下,之後我會帶大家詳細分析每種節點的功能實現原理的。

那你知道這四種的一些應用場景麼?我給大家舉幾個例子吧:

到這裏,相信你已經瞭解了 Zookeeper 內存數據結構和節點角色,結合之前的圖,會得到如下所示:

Zookeeper 通信機制

zk 集羣啓動之後,自己分配好角色。首先 Zookeeper 服務端和服務端之間直接會建立連接進行同步數據,Zookeeper 服務端和客戶端也會建立連接通信。

你們覺得它們建立連接方式一樣麼?

當然是不同的,Zookeeper 服務端和服務端之間的併發並不高,使用傳統的 BIO 的通信模型建立連接即可,而 Zookeeper 服務端和客戶端爲了支持高併發的訪問,一定會產生大量的連接,所以適合採用 NIO 的通信模型建立連接。

在 java 中,BIO 和 NIO 都有原生的實現。當然基於原生的 Java NIO 也有一些封裝實現,比如 Netty。Zookeeper 是怎麼選用的呢?

其實很簡單,Zookeeper 直接使用了 Java 原生的 BIO 和 NIO 方式。

爲什麼呢?爲了簡單。因爲簡單,就意味着代碼複雜度低,對其他技術沒有強依賴、維護性、可控性更好。Kafka 其實在 NIO 的使用上也是這樣的。

的確,Netty 使用了簡單的 API,處理了 NIO 的各種問題 連接異常、網絡閃斷、粘包拆包等問題,使用了單個 Acceptor 線程 + 多個 Processor 線程模型,EventLoopGroup 使用了自定義線程池等等有很多好處。但是 Zookeeper 在 Java 原生的 NIO 使用上,也多了很多有意思的事情,這個要等到在後續的章節在展開。

總而言之,到現在大家起碼知道了,服務端和服務端之間、服務端和客戶端之間的通信機制大體是什麼樣的、爲什麼這樣。

最終我們可以得到下面的圖:

小結

今天主要和大家聊了聊,Zookeeper 集羣可以支撐什麼、內存數據結構和節點角色劃分、通信機制這些基本的架構原理。

Zookeeper 集羣可以支撐什麼:順序一致性、高可用、高性能和小集羣下的高併發讀寫。Zookeeper 集羣適合讀多寫少的場景。

內存數據結構和節點角色劃分:內存使用 Znode 樹形數據結構維護數據,節點主要有 Leader、Follower、Observer 三種角色。

Zookeeper 集羣通信機制:客戶端和服務端通信使用了 Java 原生的 NIO 的 API,服務端和服務端之間使用了 Java 原生的 BIO 的 API。

有的人可能對這個已經比較熟悉了,有的人可能不太清楚,有的人可能之前學過,已經忘記的差不多了。

不管怎樣,這節的目的就是讓大家淺嘗就止,快速的熟悉或者回顧下 Zookeeper 集羣的基本原理,不詳細展開,這樣爲之後第二章剖析到底層核心源碼的時候鋪墊一些基礎。

金句甜點

貼標籤 Or 揭標籤

有人經常會問我,我是怎麼提高技術深度,學會這麼難懂的源碼原理的? 他自己就覺得好難啊。覺得原理很枯燥乏味,源碼更是晦澀難懂,無從下手。覺得自己學習不太行。

其實,生活中,最怕自己給自己蓋棺定論。把自己貼上了標籤。當你貼上標籤的那一刻,你就把自己禁錮了。其實真正坐牢的人不是做到監獄的人,真正坐牢的是給自己貼了標籤的人。貼的什麼標籤?舉個例子,我是一個內向的人,程序員好像很多人都會這麼說。但是這幾句話它就像一個咒語,這個咒語,它會讓你在各種場合下,合理的成爲一個躲在拐角處,不善言談的。這個標籤,倒過來約束了你,讓你表現的標籤表示的那樣。這其實很可怕,我是一個普通人,做不了什麼大事,我是一個女人,女人就該乾女人該乾的事,好多男人做的事,我不行。其實很多時候,我們只有人,哪有男人女人之分呢。

這個就是我們的標籤,把這個標籤揭掉,你纔可能會變得更好。有些標籤是你自己給自己貼的,有些是別人給你貼的。有太多都是別人給貼的,天天有很多人會給你貼,大多你也就接受了。但是當你揭掉這些,你就會變的更好,就不會覺得的源碼原理難了,是吧?

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/rg-lBWjkyPYhzIb9MFyyyQ