zookeeper學習之原理

  • 2019 年 10 月 3 日
  • 筆記

一、zookeeper 是什麼

Zookeeper是一個分佈式協調服務,可用於服務發現,分佈式鎖,分佈式領導選舉,配置管理等。這一切的基礎,都是Zookeeper提供了一個類似於Linux文件系統的樹形結構(可認為是輕量級的內存文件系統,但只適合存少量信息,完全不適合存儲大量文件或者大文件),同時提供了對於每個節點的監控與通知機制。既然是一個文件系統,就不得不提Zookeeper是如何保證數據的一致性的。


 

二、zookeeper 集群架構

Zookeeper集群是一個基於主從複製的高可用集群,通常 Master服務器作為主服務器提供寫服務,其他的 Slave 服務器通過異步複製的方式獲取 Master 服務器最新的數據,並提供讀服務,在 ZooKeeper 中沒有選擇傳統的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三種角色,每個角色承擔如下:

  • Leader 一個Zookeeper集群同一時間只會有一個實際工作的Leader,它會發起並維護與各Follwer及Observer間的心跳。所有的寫操作必須要通過Leader完成再由Leader將寫操作廣播給其它服務器。
  • Follower 一個Zookeeper集群可能同時存在多個Follower,它會響應Leader的心跳。Follower可直接處理並返回客戶端的讀請求,同時會將寫請求轉發給Leader處理,並且負責在Leader處理寫請求時,對請求進行投票(“過半寫成功”策略)。
  • Observer 角色與Follower類似,但是無投票權。

 在集群中zookeeper是如何保證master與slave數據一致性?

為了保證寫操作的一致性與可用性,Zookeeper專門設計了一種名為原子廣播(ZAB)的支持崩潰恢復的一致性協議。基於該協議,Zookeeper實現了一種主從模式的系統架構來保持集群中各個副本之間的數據一致性。

寫數據時保證一致性:Zookeeper 客戶端會隨機連接到 Zookeeper 集群的一個節點,如果是讀請求,就直接從當前節點中讀取數據;如果是寫請求且當前節點不是leader,那麼節點就會向 leader 提交事務,leader 會廣播事務,只要有超過半數節點寫入成功,該寫請求就會被提交(類 2PC 協議)。

服務器運行時期的Leader選舉(當leader當機後如何選主)?

zookeeper 在集群模式下,leader宕機也不會影響繼續提供服務,但是leader宕機在從新選主過程時無法對外提供服務,會有一個短暫的停頓過程(這裡就是與eureka的區別)。

  • 集群已存在leader現在又假如一台服務器:對於集群中已經存在Leader而言,此種情況一般都是某台機器啟動得較晚,在其啟動之前,集群已經在正常工作,對這種情況,該機器試圖去選舉Leader時,會被告知當前服務器的Leader信息,對於該機器而言,僅僅需要和Leader機器建立起連接,並進行狀態同步即可。
  • 集群存在leader宕機需要重新選舉leader:例如server3  宕機了。則剩餘的 每個Server發出一個投票。Server1和Server2都會將自己作為Leader服務器來進行投票,每次投票會包含所推舉的服務器的myid和ZXID,使用(myid, ZXID)來表示,此時Server1的投票為(1, 0),Server2的投票為(2, 0),然後各自將這個投票發給集群中其他機器。當新的leader選擇出來以後,第二步就是數據同步保證所有的節點與leader數據一致。

            處理投票。針對每一個投票,服務器都需要將別人的投票和自己的投票進行PK,PK規則如下

    · 優先檢查ZXID。ZXID比較大的服務器優先作為Leader。

    · 如果ZXID相同,那麼就比較myid。myid較大的服務器作為Leader服務器。

為什麼最好使用奇數台服務器構成 ZooKeeper 集群?

zookeeper有這樣一個特性:集群中只要有過半的機器是正常工作的,那麼整個集群對外就是可用的。也就是說如果有2個zookeeper,那麼只要有1個死了zookeeper就不能用了,因為1沒有過半,所以2個zookeeper的死亡容忍度為0;同理,要是有3個zookeeper,一個死了,還剩下2個正常的,過半了(2>3/2),所以3個zookeeper的容忍度為1。如果是4台zookeeper 如果掛掉2台, 還剩下2台 (2 不大於 4/2),顯然不過半所以集群還是不可用,4台的容忍度還是1。因此不是 不能部署偶數台,而是偶數台對於高可用作用不大,浪費服務器。


 

三、ZooKeeper 的一些重要概念

ZooKeeper 將數據保存在內存中,這也就保證了 高吞吐量和低延遲(但是內存限制了能夠存儲的容量不太大,此限制也是保持znode中存儲的數據量較小的進一步原因)。

ZooKeeper 是高性能的。 在“讀”多於“寫”的應用程序中尤其地高性能,因為“寫”會導致所有的服務器間同步狀態。(“讀”多於“寫”是協調服務的典型場景。)

會話(Session)

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

在為客戶端創建會話之前,服務端首先會為每個客戶端都分配一個sessionID。由於 sessionID 是 Zookeeper 會話的一個重要標識,許多與會話相關的運行機制都是基於這個 sessionID 的,因此,無論是哪台服務器為客戶端分配的 sessionID,都務必保證全局唯一。

Watcher

  Watcher(事件監聽器),是Zookeeper中的一個很重要的特性。Zookeeper允許用戶在指定節點上註冊一些Watcher,並且在一些特定事件觸發的時候,ZooKeeper服務端會將事件通知到感興趣的客戶端上去,該機制是Zookeeper實現分佈式協調服務的重要特性。

ACL

Zookeeper採用ACL(AccessControlLists)策略來進行權限控制,類似於 UNIX 文件系統的權限控制。Zookeeper 定義了如下5種權限


 

四、zookeeper 的數據結構

ZooKeeper 允許分佈式進程通過共享的層次結構命名空間進行相互協調,這與標準文件系統類似。 名稱空間由 ZooKeeper 中的數據寄存器組成 – 稱為znode,這些類似於文件和目錄。 與為存儲設計的典型文件系統不同,ZooKeeper數據保存在內存中,這意味着ZooKeeper可以實現高吞吐量和低延遲。

 

1、PERSISTENT–持久化目錄節點      客戶端與zookeeper斷開連接後,該節點依舊存在

2、PERSISTENT_SEQUENTIAL-持久化順序編號目錄節點   客戶端與zookeeper斷開連接後,該節點依舊存在,只是Zookeeper給該節點名稱進行順序編號

3、EPHEMERAL-臨時目錄節點   客戶端與zookeeper斷開連接後,該節點被刪除

4、EPHEMERAL_SEQUENTIAL-臨時順序編號目錄節點   客戶端與zookeeper斷開連接後,該節點被刪除,只是Zookeeper給該節點名稱進行順序編號


 

五、zookeeper的作用

1、命名服務

在zookeeper的文件系統里創建一個目錄,即有唯一的path,在我們使用tborg無法確定上遊程序的部署機器時即可與下遊程序約定好path,通過path即能互相探索發現。

2、配置管理

 程序總是需要配置的,如果程序分散部署在多台機器上,要逐個改變配置就變得困難。好吧,現在把這些配置全部放到zookeeper上去,保存在 Zookeeper 的某個目錄節點中,然後所有相關應用程序對這個目錄節點進行監聽,一旦配置信息發生變化,每個應用程序就會收到 Zookeeper 的通知,然後從 Zookeeper 獲取新的配置信息應用到系統中就好。

 3、集群管理

所謂集群管理無在乎兩點:是否有機器退出和加入、選舉master。

第一點,所有機器約定在父目錄GroupMembers下創建臨時目錄節點,然後監聽父目錄節點的子節點變化消息。一旦有機器掛掉,該機器與 zookeeper的連接斷開,其所創建的臨時目錄節點被刪除,所有其他機器都收到通知:某個兄弟目錄被刪除,於是,所有人都知道他掉線了。新機器加入 也是類似,所有機器收到通知:新兄弟目錄加入。

對於第二點,我們稍微改變一下,所有機器創建臨時順序編號目錄節點,每次選取編號最小的機器作為master就好。

4、分佈式鎖

       有了zookeeper的一致性文件系統,鎖的問題變得容易。鎖服務可以分為兩類,一個是保持獨佔,另一個是控制時序。

        對於第一類,我們將zookeeper上的一個znode看作是一把鎖,通過createznode的方式來實現。所有客戶端都去創建 /distribute_lock 節點,最終成功創建的那個客戶端也即擁有了這把鎖。用完刪除掉自己創建的distribute_lock 節點就釋放出鎖。

        對於第二類, /distribute_lock 已經預先存在,所有客戶端在它下面創建臨時順序編號目錄節點,和選master一樣,編號最小的獲得鎖,用完刪除,依次方便。