1. 程式人生 > >如果有人問你ZooKeeper是什麽,就把這篇文章發給他。

如果有人問你ZooKeeper是什麽,就把這篇文章發給他。

可用性 一半 connect hbase 視圖 因此 Paxos算法 再次 ref

前言

提到ZooKeeper,相信大家都不會陌生。Dubbo,Kafka,Hadoop等等項目裏都能看到它的影子。但是你真的了解 ZooKeeper 嗎?如果面試官讓你給他講講 ZooKeeper 是個什麽東西,你能回答到什麽地步呢?

我會用兩個篇幅介紹ZooKeeper ,第一篇是概念性的認識,這篇你會得到 ZooKeeper 是什麽,ZooKeeper 設計的目標,ZooKeeper 能做什麽和ZooKeeper 基本的概念。第二篇我會從實戰出發,安裝ZooKeeper,寫一些ZooKeeper 具體應用場景的代碼實現。

一、ZooKeeper是什麽

ZooKeeper 是一個分布式的,開放源碼的分布式應用程序協調服務,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要組件。它是一個為分布式應用提供一致性服務的軟件,提供的功能包括:配置維護、域名服務、分布式同步、組服務等。

官網:http://zookeeper.apache.org/

源碼:https://github.com/apache/zookeeper

二、ZooKeeper 的由來

下面這段內容摘自《從Paxos到Zookeeper 》 ,本文中很多的名詞介紹也來自本書。

Zookeeper 最早起源於雅虎研究院的一個研究小組。在當時,研究人員發現,在雅虎內部很多大型系統基本都需要依賴一個類似的系統來進行分布式協調,但是這些系統往往都存在分布式單點問題。所以,雅虎的開發人員就試圖開發一個通用的無單點問題的分布式協調框架,以便讓開發人員將精力集中在處理業務邏輯上。
關於“ZooKeeper”這個項目的名字,其實也有一段趣聞。在立項初期,考慮到之前內部很多項目都是使用動物的名字來命名的(例如著名的Pig項目),雅虎的工程師希望給這個項目也取一個動物的名字。時任研究院的首席科學家 RaghuRamakrishnan 開玩笑地說:“在這樣下去,我們這兒就變成動物園了!”此話一出,大家紛紛表示就叫動物園管理員吧一一一因為各個以動物命名的分布式組件放在一起,雅虎的整個分布式系統看上去就像一個大型的動物園了,而 Zookeeper 正好要用來進行分布式環境的協調一一於是,Zookeeper 的名字也就由此誕生了。

三、ZooKeeper的特性

  • 順序一致性,從同一個客戶端發起的事務請求,最終將會嚴格地按照其發起順序被應用到Zookeeper中去。

  • 原子性,所有事務請求的處理結果在整個集群中所有機器上的應用情況是一致的,即整個集群要麽都成功應用了某個事務,要麽都沒有應用。

  • 單一視圖,無論客戶端連接的是哪個 Zookeeper 服務器,其看到的服務端數據模型都是一致的。

  • 可靠性,一旦服務端成功地應用了一個事務,並完成對客戶端的響應,那麽該事務所引起的服務端狀態變更將會一直被保留,除非有另一個事務對其進行了變更。
  • 實時性,Zookeeper 保證在一定的時間段內,客戶端最終一定能夠從服務端上讀取到最新的數據狀態。

四、ZooKeeper的設計目標

  • 簡單的數據結構
    Zookeeper 使得分布式程序能夠通過一個共享的樹形結構的名字空間來進行相互協調,即Zookeeper 服務器內存中的數據模型由一系列被稱為ZNode的數據節點組成,Zookeeper 將全量的數據存儲在內存中,以此來提高服務器吞吐、減少延遲的目的。
  • 可以構建集群
    Zookeeper 集群通常由一組機器構成,組成 Zookeeper 集群的而每臺機器都會在內存中維護當前服務器狀態,並且每臺機器之間都相互通信。
  • 順序訪問
    對於來自客戶端的每個更新請求,Zookeeper 都會分配一個全局唯一的遞增編號,這個編號反映了所有事務操作的先後順序。

  • 高性能
    Zookeeper 和Redis一樣全量數據存儲在內存中,100%讀請求壓測QPS 12-13W。

五、關於 ZooKeeper 的一些重要概念

5.1 Zookeeper 集群:

Zookeeper 是一個由多個 server 組成的集群,一個 leader,多個 follower。(這個不同於我們常見的Master/Slave模式)leader 為客戶端服務器提供讀寫服務,除了leader外其他的機器只能提供讀服務。
每個 server 保存一份數據副本全數據一致,分布式讀 follower,寫由 leader 實施更新請求轉發,由 leader 實施更新請求順序進行,來自同一個 client 的更新請求按其發送順序依次執行數據更新原子性,一次數據更新要麽成功,要麽失敗。全局唯一數據視圖,client 無論連接到哪個 server,數據視圖都是一致的實時性,在一定事件範圍內,client 能讀到最新數據。
技術分享圖片

5.2 集群角色

Leader:是整個 Zookeeper 集群工作機制中的核心 。Leader 作為整個 ZooKeeper 集群的主節點,負責響應所有對 ZooKeeper 狀態變更的請求。
主要工作:

  • 事務請求的唯一調度和處理,保障集群處理事務的順序性。
  • 集群內各服務器的調度者。

Leader 選舉是 Zookeeper 最重要的技術之一,也是保障分布式數據一致性的關鍵所在。我們以三臺機器為例,在服務器集群初始化階段,當有一臺服務器Server1啟動時候是無法完成選舉的,當第二臺機器 Server2 啟動後兩臺機器能互相通信,每臺機器都試圖找到一個leader,於是便進入了 leader 選舉流程.

  1. 每個 server 發出一個投票
    投票的最基本元素是(SID-服務器id,ZXID-事物id)
  2. 接受來自各個服務器的投票
  3. 處理投票
    優先檢查 ZXID(數據越新ZXID越大),ZXID比較大的作為leader,ZXID一樣的情況下比較SID
  4. 統計投票
    這裏有個過半的概念,大於集群機器數量的一半,即大於或等於(n/2+1),我們這裏的由三臺,大於等於2即為達到“過半”的要求。
    這裏也有引申到為什麽 Zookeeper 集群推薦是單數。
集群數量 至少正常運行數量 允許掛掉的數量
2 2的半數為1,半數以上最少為2 0
3 3的半數為1.5,半數以上最少為2 1
4 4的半數為2,半數以上最少為3 1
5 5的半數為2.5,半數以上最少為3 2
6 6的半數為3,半數以上最少為4 2

通過以上可以發現,3臺服務器和4臺服務器都最多允許1臺服務器掛掉,5臺服務器和6臺服務器都最多允許2臺服務器掛掉,明顯4臺服務器成本高於3臺服務器成本,6臺服務器成本高於5服務器成本。這是由於半數以上投票通過決定的。

  1. 改變服務器狀態
    一旦確定了 leader,服務器就會更改自己的狀態,且一半不會再發生變化,比如新機器加入集群、非 leader 掛掉一臺。

Follower :是 Zookeeper 集群狀態的跟隨者。他的邏輯就比較簡單。除了響應本服務器上的讀請求外,follower 還要處理leader 的提議,並在 leader 提交該提議時在本地也進行提交。另外需要註意的是,leader 和 follower 構成ZooKeeper 集群的法定人數,也就是說,只有他們才參與新 leader的選舉、響應 leader 的提議。

Observer :服務器充當一個觀察者的角色。如果 ZooKeeper 集群的讀取負載很高,或者客戶端多到跨機房,可以設置一些 observer 服務器,以提高讀取的吞吐量。Observer 和 Follower 比較相似,只有一些小區別:首先 observer 不屬於法定人數,即不參加選舉也不響應提議,也不參與寫操作的“過半寫成功”策略;其次是 observer 不需要將事務持久化到磁盤,一旦 observer 被重啟,需要從 leader 重新同步整個名字空間。

5.3會話(Session)

Session 指的是 ZooKeeper 服務器與客戶端會話。在 ZooKeeper 中,一個客戶端連接是指客戶端和服務器之間的一個 TCP 長連接。客戶端啟動的時候,首先會與服務器建立一個 TCP 連接,從第一次連接建立開始,客戶端會話的生命周期也開始了。通過這個連接,客戶端能夠通過心跳檢測與服務器保持有效的會話,也能夠向Zookeeper 服務器發送請求並接受響應,同時還能夠通過該連接接收來自服務器的Watch事件通知。 Session 的 sessionTimeout 值用來設置一個客戶端會話的超時時間。當由於服務器壓力太大、網絡故障或是客戶端主動斷開連接等各種原因導致客戶端連接斷開時,只要在sessionTimeout規定的時間內能夠重新連接上集群中任意一臺服務器,那麽之前創建的會話仍然有效。在為客戶端創建會話之前,服務端首先會為每個客戶端都分配一個sessionID。由於 sessionID 是 Zookeeper 會話的一個重要標識,許多與會話相關的運行機制都是基於這個 sessionID 的,因此,無論是哪臺服務器為客戶端分配的 sessionID,都務必保證全局唯一。

5.3.1 會話(Session)

在Zookeeper客戶端與服務端成功完成連接創建後,就創建了一個會話,Zookeeper會話在整個運行期間的生命周期中,會在不同的會話狀態中之間進行切換,這些狀態可以分為CONNECTING、CONNECTED、RECONNECTING、RECONNECTED、CLOSE等。

一旦客戶端開始創建Zookeeper對象,那麽客戶端狀態就會變成CONNECTING狀態,同時客戶端開始嘗試連接服務端,連接成功後,客戶端狀態變為CONNECTED,通常情況下,由於斷網或其他原因,客戶端與服務端之間會出現斷開情況,一旦碰到這種情況,Zookeeper客戶端會自動進行重連服務,同時客戶端狀態再次變成CONNCTING,直到重新連上服務端後,狀態又變為CONNECTED,在通常情況下,客戶端的狀態總是介於CONNECTING 和CONNECTED 之間。但是,如果出現諸如會話超時、權限檢查或是客戶端主動退出程序等情況,客戶端的狀態就會直接變更為CLOSE狀態。

5.3.2 會話創建

Session是Zookeeper中的會話實體,代表了一個客戶端會話,其包含了如下四個屬性

  1. sessionID。會話ID,唯一標識一個會話,每次客戶端創建新的會話時,Zookeeper都會為其分配一個全局唯一的sessionID。
  2. TimeOut。會話超時時間,客戶端在構造Zookeeper實例時,會配置sessionTimeout參數用於指定會話的超時時間,Zookeeper客戶端向服務端發送這個超時時間後,服務端會根據自己的超時時間限制最終確定會話的超時時間。
  3. TickTime。下次會話超時時間點,為了便於Zookeeper對會話實行”分桶策略”管理,同時為了高效低耗地實現會話的超時檢查與清理,Zookeeper會為每個會話標記一個下次會話超時時間點,其值大致等於當前時間加上TimeOut。
  4. isClosing。標記一個會話是否已經被關閉,當服務端檢測到會話已經超時失效時,會將該會話的isClosing標記為”已關閉”,這樣就能確保不再處理來自該會話的心情求了。

Zookeeper為了保證請求會話的全局唯一性,在SessionTracker初始化時,調用initializeNextSession方法生成一個sessionID,之後在Zookeeper運行過程中,會在該sessionID的基礎上為每個會話進行分配,初始化算法如下

```
    public static long initializeNextSession(long id) {
      long nextSid = 0;
      // 無符號右移8位使為了避免左移24後,再右移8位出現負數而無法通過高8位確定sid值
      nextSid = (System.currentTimeMillis() << 24) >>> 8;
      nextSid = nextSid | (id << 56);
      return nextSid;
    }

    ```

5.3.3 會話管理

Zookeeper的會話管理主要是通過SessionTracker來負責,其采用了分桶策略(將類似的會話放在同一區塊中進行管理)進行管理,以便Zookeeper對會話進行不同區塊的隔離處理以及同一區塊的統一處理。
技術分享圖片
----------------------------

5.4 數據節點 Znode

在Zookeeper中,“節點"分為兩類,第一類同樣是指構成集群的機器,我們稱之為機器節點;第二類則是指數據模型中的數據單元,我們稱之為數據節點一一ZNode。
Zookeeper將所有數據存儲在內存中,數據模型是一棵樹(Znode Tree),由斜杠(/)的進行分割的路徑,就是一個Znode,例如/foo/path1。每個上都會保存自己的數據內容,同時還會保存一系列屬性信息。
技術分享圖片

5.4.1 節點類型

在Zookeeper中,node可以分為持久節點和臨時節點和順序節點三大類。
可以通過組合生成如下四種類型節點
1. PERSISTENT
持久節點,節點創建後便一直存在於Zookeeper服務器上,直到有刪除操作來主動清楚該節點。
2. PERSISTENT_SEQUENTIAL
持久順序節點,相比持久節點,其新增了順序特性,每個父節點都會為它的第一級子節點維護一份順序,用於記錄每個子節點創建的先後順序。在創建節點時,會自動添加一個數字後綴,作為新的節點名,該數字後綴的上限是整形的最大值。
3.EPEMERAL
臨時節點,臨時節點的生命周期與客戶端會話綁定,客戶端失效,節點會被自動清理。同時,Zookeeper規定不能基於臨時節點來創建子節點,即臨時節點只能作為葉子節點。
4.EPEMERAL_SEQUENTIAL
臨時順序節點,在臨時節點的基礎添加了順序特性。

5.5 版本——保證分布式數據原子性操作

每個數據節點都具有三種類型的版本信息,對數據節點的任何更新操作都會引起版本號的變化。

version– 當前數據節點數據內容的版本號
cversion– 當前數據子節點的版本號
aversion– 當前數據節點ACL變更版本號

  上述各版本號都是表示修改次數,如version為1表示對數據節點的內容變更了一次。即使前後兩次變更並沒有改變數據內容,version的值仍然會改變。version可以用於寫入驗證,類似於CAS。

5.6watcher事件監聽器

ZooKeeper允許用戶在指定節點上註冊一些Watcher,當數據節點發生變化的時候,ZooKeeper服務器會把這個變化的通知發送給感興趣的客戶端
技術分享圖片

5.7 ACL 權限控制——保障數據的安全

ACL是Access Control Lists 的簡寫, ZooKeeper采用ACL策略來進行權限控制,有以下權限:
CREATE:創建子節點的權限
READ:獲取節點數據和子節點列表的權限
WRITE:更新節點數據的權限
DELETE:刪除子節點的權限
ADMIN:設置節點ACL的權限

5.8 Paxos算法

Paxos算法是基於消息傳遞且具有高度容錯特性的一致性算法,是目前公認的解決分布式一致性問題最有效的算法之一。(其他算法有二階段提交、三階段提交等)
篇幅較長 可以參考https://www.cnblogs.com/linbingdong/p/6253479.html

六、ZooKeeper 可以做什麽?

  • 分布式服務註冊與訂閱

    在分布式環境中,為了保證高可用性,通常同一個應用或同一個服務的提供方都會部署多份,達到對等服務。而消費者就須要在這些對等的服務器中選擇一個來執行相關的業務邏輯,比較典型的服務註冊與訂閱,代表:dubbo。
    技術分享圖片

  • 分布式配置中心
    發布與訂閱模型,即所謂的配置中心,顧名思義就是發布者將數據發布到ZK節點上,供訂閱者獲取數據,實現配置信息的集中式管理和動態更新。代表:百度的disconf。
    github:https://github.com/knightliao/disconf

  • 命名服務
    在分布式系統中,通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等信息。被命名的實體通常可以是集群中的機器,提供的服務地址,進程對象等等——這些我們都可以統稱他們為名字(Name)。其中較為常見的就是一些分布式服務框架中的服務地址列表。通過調用ZK提供的創建節點的API,能夠很容易創建一個全局唯一的path,這個path就可以作為一個名稱。
    技術分享圖片

  • 分布式鎖
    分布式鎖,這個主要得益於ZooKeeper為我們保證了數據的強一致性。鎖服務可以分為兩類,一個是保持獨占,另一個是控制時序。
    所謂保持獨占,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖。通常的做法是把zk上的一個znode看作是一把鎖,通過create znode的方式來實現。所有客戶端都去創建 /distribute_lock 節點,最終成功創建的那個客戶端也即擁有了這把鎖。
    控制時序,就是所有視圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全局時序了。做法和上面基本類似,只是這裏 /distribute_lock 已絆預先存在,客戶端在它下面創建臨時有序節點(這個可以通過節點的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL來指定)。Zk的父節點(/distribute_lock)維持一份sequence,保證子節點創建的時序性,從而也形成了每個客戶端的全局時序
  • Master選舉

  • 負載均衡
    技術分享圖片

    參考

  • 《從Paxos到Zookeeper 》
  • 《ZooKeeper深入淺出 》

推薦閱讀

互聯網公司面試必問的Redis題目

互聯網公司面試必問的mysql題目(下)

最近,我組建了個群聊。學習Java進階技術幹貨、實踐分享,職位內推,一起聊聊理想。誌同道合的朋友,歡迎你的加入。


技術分享圖片

最後,祝大家國慶節快樂!歡迎長按關註~

技術分享圖片

如果有人問你ZooKeeper是什麽,就把這篇文章發給他。