Zookeeper基礎原理 1.Zookeeper簡介 2.順序一致性 3.ZooKeeper數據結構 4.Zookeeper集羣 5.客戶端 6.應用場景 參考

1.Zookeeper簡介

ZooKeeper 是一個開源的分佈式協調框架,是Apache Hadoop 的一個子項目,主要用來解決分佈式集羣中應用系統的一致性問題。

ZooKeeper本質上是一個分佈式的小文件存儲系統(Zookeeper=文件系統+監聽機制)。提供基於類似於文件系統的目錄樹方式的數據存儲,並且可以對樹中的節點進行有效管理,從而用來維護和監控存儲的數據的狀態變化。通過監控這些數據狀態的變化,從而可以達到基於數據的集羣管理、統一命名服務、分佈式配置管理、分佈式消息隊列、分佈式鎖、分佈式協調等功能。

Zookeeper從設計模式角度來理解:是一個基於觀察者模式設計的分佈式服務管理框架,它負責存儲和管理大家都關心的數據,然後接受觀察者的註冊,一旦這些數據的狀態發生變化,Zookeeper 就將負責通知已經在Zookeeper上註冊的那些觀察者做出相應的反應。

2.順序一致性

強一致性:又稱線性一致性(linearizability )
1.任意時刻,所有節點中的數據是一樣的,
2.一個集羣需要對外部提供強一致性,所以只要集羣內部某一臺服務器的數據發生了改變,那麼就需要等待集羣內其他服務器的數據同步完成後,才能正常的對外提供服務
3.保證了強一致性,務必會損耗可用性

弱一致性:
1.系統中的某個數據被更新後,後續對該數據的讀取操作可能得到更新後的值,也可能是更改前的值。
2.即使過了不一致時間窗口,後續的讀取也不一定能保證一致。

最終一致性:
1.弱一致性的特殊形式,不保證在任意時刻任意節點上的同一份數據都是相同的,但是隨着時間的遷移,不同節點上的同一份數據總是在向趨同的方向變化
2.存儲系統保證在沒有新的更新的條件下,最終所有的訪問都是最後更新的值

順序一致性:
1.任何一次讀都能讀到某個數據的最近一次寫的數據。
2.對其他節點之前的修改是可見(已同步)且確定的,並且新的寫入建立在已經達成同步的基礎上。

Zookeeper是順序一致性。

3.ZooKeeper數據結構

ZooKeeper的數據模型是層次模型,層次模型常見於文件系統。層次模型和key-value模型是兩種主流的數據模型。ZooKeeper使用文件系統模型主要基於以下兩點考慮:

  • 文件系統的樹形結構便於表達數據之間的層次關係
  • 文件系統的樹形結構便於爲不同的應用分配獨立的命名空間( namespace )

ZooKeeper的層次模型稱作Data Tree,Data Tree的每個節點叫作Znode。不同於文件系統,每個節點都可以保存數據,每一個 ZNode 默認能夠存儲 1MB 的數據,每個 ZNode 都可以通過其路徑唯一標識,每個節點都有一個版本(version),版本從0開始計數。

節點分類

  • 1)持久節點(PERSISTENT): 這樣的znode在創建之後即使發生ZooKeeper集羣宕機或者client宕機也不會丟失。
  • 2)臨時節點(EPHEMERAL ): client宕機或者client在指定的timeout時間內沒有給ZooKeeper集羣發消息,這樣的znode就會消失。
  • 3)持久順序節點(PERSISTENT_SEQUENTIAL): znode除了具備持久性znode的特點之外,znode的名字具備順序性。
  • 4)臨時順序節點(EPHEMERAL_SEQUENTIAL): znode除了具備臨時性znode的特點之外,zorde的名字具備順序性。
  • 5)Container節點 (3.5.3版本新增):Container容器節點,當容器中沒有任何子節點,該容器節點會被zk定期刪除(定時任務默認60s 檢查一次)。 和持久節點的區別是 ZK 服務端啓動後,會有一個單獨的線程去掃描,所有的容器節點,當發現容器節點的子節點數量爲 0 時,會自動刪除該節點。可以用於 leader 或者鎖的場景中。
  • 6) TTL節點: 帶過期時間節點,默認禁用,需要在zoo.cfg中添加 extendedTypesEnabled=true 開啓。 注意:ttl不能用於臨時節點

節點狀態信息:

  • cZxid :Znode創建的事務id。
  • ctime:節點創建時的時間戳。
  • mZxid :Znode被修改的事務id,即每次對znode的修改都會更新mZxid。
    對於zk來說,每次的變化都會產生一個唯一的事務id,zxid(ZooKeeper Transaction Id),通過zxid,可以確定更新操作的先後順序。例如,如果zxid1小於zxid2,說明zxid1操作先於zxid2發生,zxid對於整個zk都是唯一的,即使操作的是不同的znode。
  • pZxid: 表示該節點的子節點列表最後一次修改的事務ID,添加子節點或刪除子節點就會影響子節點列表,但是修改子節點的數據內容則不影響該ID(注意: 只有子節點列表變更了纔會變更pzxid,子節點內容變更不會影響pzxid)
  • mtime:節點最新一次更新發生時的時間戳.
  • cversion :子節點的版本號。當znode的子節點有變化時,cversion 的值就會增加1。
  • dataVersion:數據版本號,每次對節點進行set操作,dataVersion的值都會增加1(即使設置的是相同的數據),可有效避免了數據更新時出現的先後順序問題。
  • ephemeralOwner:如果該節點爲臨時節點, ephemeralOwner值表示與該節點綁定的session id。如果不是, ephemeralOwner值爲0(持久節點)。
    在client和server通信之前,首先需要建立連接,該連接稱爲session。連接建立後,如果發生連接超時、授權失敗,或者顯式關閉連接,連接便處於closed狀態, 此時session結束。
  • dataLength : 數據的長度
  • numChildren :子節點的數量(只統計直接子節點的數量)

監聽通知(watcher)機制

  • 一個Watch事件是一個一次性的觸發器,當被設置了Watch的數據發生了改變的時候,則服務器將這個改變發送給設置了Watch的客戶端,以便通知它們。
  • Zookeeper採用了 Watcher機制實現數據的發佈訂閱功能,多個訂閱者可同時監聽某一特定主題對象,當該主題對象的自身狀態發生變化時例如節點內容改變、節點下的子節點列表改變等,會實時、主動通知所有訂閱者。
  • watcher機制事件上與觀察者模式類似,也可看作是一種觀察者模式在分佈式場景下的實現方式。

watcher的過程:

  • 客戶端向服務端註冊watcher
  • 服務端事件發生觸發watcher
  • 客戶端回調watcher得到觸發事件情況

注意:Zookeeper中的watch機制,必須客戶端先去服務端註冊監聽,這樣事件發送纔會觸發監聽,通知給客戶端。

支持的事件類型:

  • None: 連接建立事件
  • NodeCreated: 節點創建
  • NodeDeleted: 節點刪除
  • NodeDataChanged:節點數據變化
  • NodeChildrenChanged:子節點列表變化
  • DataWatchRemoved:節點監聽被移除
  • ChildWatchRemoved:子節點監聽被移除

永久性Watch
在被觸發之後,仍然保留,可以繼續監聽ZNode上的變更,是Zookeeper 3.6.0版本新增的功能

addWatch的作用是針對指定節點添加事件監聽,支持兩種模式

  • PERSISTENT,持久化訂閱,針對當前節點的修改和刪除事件,以及當前節點的子節點的刪除和新增事件。
  • PERSISTENT_RECURSIVE,持久化遞歸訂閱,在PERSISTENT的基礎上,增加了子節點修改的事件觸發,以及子節點的子節點的數據變化都會觸發相關事件(滿足遞歸訂閱特性)

4.Zookeeper集羣

集羣角色

  • Leader: 領導者。
    事務請求(寫操作)的唯一調度者和處理者,保證集羣事務處理的順序性;集羣內部各個服務器的調度者。對於create、setData、delete等有寫操作的請求,則要統一轉發給leader處理,leader需要決定編號、執行操作,這個過程稱爲事務。
  • Follower: 跟隨者
    處理客戶端非事務(讀操作)請求(可以直接響應),轉發事務請求給Leader;參與集羣Leader選舉投票。
  • Observer: 觀察者
    對於非事務請求可以獨立處理(讀操作),對於事務性請求會轉發給leader處理。Observer節點接收來自leader的inform信息,更新自己的本地存儲,不參與提交和選舉投票。通常在不影響集羣事務處理能力的前提下提升集羣的非事務處理能力。

Observer應用場景:

  • 提升集羣的讀性能。因爲Observer和不參與提交和選舉的投票過程,所以可以通過往集羣裏面添加observer節點來提高整個集羣的讀性能。
  • 跨數據中心部署。 比如需要部署一個北京和香港兩地都可以使用的zookeeper集羣服務,並且要求北京和香港客戶的讀請求延遲都很低。解決方案就是把香港的節點都設置爲observer。

leader節點可以處理讀寫請求,follower只可以處理讀請求。follower在接到寫請求時會把寫請求轉發給leader來處理。

Zookeeper數據一致性保證:

  • 全局可線性化(Linearizable )寫入∶先到達leader的寫請求會被先處理,leader決定寫請求的執行順序。
  • 客戶端FIFO順序∶來自給定客戶端的請求按照發送順序執行。

4.1 Zookeeper Leader 選舉原理

服務器啓動時的 leader 選舉
每個節點啓動的時候都 LOOKING 觀望狀態,接下來就開始進行選舉主流程。這裏選取三臺機器組成的集羣爲例。第一臺服務器 server1啓動時,無法進行 leader 選舉,當第二臺服務器 server2 啓動時,兩臺機器可以相互通信,進入 leader 選舉過程。

  • 1)每臺 server 發出一個投票,由於是初始情況,server1 和 server2 都將自己作爲 leader 服務器進行投票,每次投票包含所推舉的服務器myid、zxid、epoch,使用(myid,zxid)表示,此時 server1 投票爲(1,0),server2 投票爲(2,0),然後將各自投票發送給集羣中其他機器。
  • 2)接收來自各個服務器的投票。集羣中的每個服務器收到投票後,首先判斷該投票的有效性,如檢查是否是本輪投票(epoch)、是否來自 LOOKING 狀態的服務器。
  • 3)分別處理投票。針對每一次投票,服務器都需要將其他服務器的投票和自己的投票進行對比,對比規則如下:
     a. 優先比較 epoch
     b. 檢查 zxid,zxid 比較大的服務器優先作爲 leader
     c. 如果 zxid 相同,那麼就比較 myid,myid 較大的服務器作爲 leader 服務器
  • 4)統計投票。每次投票後,服務器統計投票信息,判斷是都有過半機器接收到相同的投票信息。server1、server2 都統計出集羣中有兩臺機器接受了(2,0)的投票信息,此時已經選出了 server2 爲 leader 節點。
  • 5)改變服務器狀態。一旦確定了 leader,每個服務器響應更新自己的狀態,如果是 follower,那麼就變更爲 FOLLOWING,如果是 Leader,變更爲 LEADING。此時 server3繼續啓動,直接加入變更自己爲 FOLLOWING。

運行過程中的 leader 選舉
當集羣中 leader 服務器出現宕機或者不可用情況時,整個集羣無法對外提供服務,進入新一輪的 leader 選舉。

  • 1)變更狀態。leader 掛後,其他非 Oberver服務器將自身服務器狀態變更爲 LOOKING。
  • 2)每個 server 發出一個投票。在運行期間,每個服務器上 zxid 可能不同。
  • 3)處理投票。規則同啓動過程。
  • 4)統計投票。與啓動過程相同。
  • 5)改變服務器狀態。與啓動過程相同。

4.2 Zookeeper 數據同步流程

在 Zookeeper 中,主要依賴 ZAB 協議來實現分佈式數據一致性。
ZAB 協議分爲兩部分:

  • 消息廣播
  • 崩潰恢復

消息廣播
Zookeeper 使用單一的主進程 Leader 來接收和處理客戶端所有事務請求,並採用 ZAB 協議的原子廣播協議,將事務請求以 Proposal 提議廣播到所有 Follower 節點,當集羣中有過半的Follower 服務器進行正確的 ACK 反饋,那麼Leader就會再次向所有的 Follower 服務器發送commit 消息,將此次提案進行提交。這個過程可以簡稱爲 2pc 事務提交,整個流程可以參考下圖,注意 Observer 節點只負責同步 Leader 數據,不參與 2PC 數據同步過程。

崩潰恢復
在正常情況消息下廣播能運行良好,但是一旦 Leader 服務器出現崩潰,或者由於網絡原理導致 Leader 服務器失去了與過半 Follower 的通信,那麼就會進入崩潰恢復模式,需要選舉出一個新的 Leader 服務器。在這個過程中可能會出現兩種數據不一致性的隱患,需要 ZAB 協議的特性進行避免。

  • Leader 服務器將消息 commit 發出後,立即崩潰
  • Leader 服務器剛提出 proposal 後,立即崩潰

ZAB 協議的恢復模式使用了以下策略:

  • 選舉 zxid 最大的節點作爲新的 leader
  • 新 leader 將事務日誌中尚未提交的消息進行處理

5.客戶端

5.1 原生客戶端

ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, ZKClientConfig)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider, ZKClientConfig)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, ZKClientConfig)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long, byte[])
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long, byte[], boolean, HostProvider)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long, byte[], boolean, HostProvider, ZKClientConfig)
ZooKeeper(String connectString, int  sessionTimeout, Watcher watcher, long, byte[], boolean)

5.2 Curator

Curator 是一套由netflix 公司開源的,Java 語言編程的 ZooKeeper 客戶端框架,Curator項目是現在ZooKeeper 客戶端中使用最多,對ZooKeeper 版本支持最好的第三方客戶端,並推薦使用,Curator 把我們平時常用的很多 ZooKeeper 服務開發功能做了封裝,例如 Leader 選舉、分佈式計數器、分佈式鎖。這就減少了技術人員在使用 ZooKeeper 時的大部分底層細節開發工作。在會話重新連接、Watch 反覆註冊、多種異常處理等使用場景中,用原生的 ZooKeeper 處理比較複雜。而在使用 Curator 時,由於其對這些功能都做了高度的封裝,使用起來更加簡單,不但減少了開發時間,而且增強了程序的可靠性。

curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath(pathWithParent);

說明:

  • guaranteed:該函數的功能如字面意思一樣,主要起到一個保障刪除成功的作用,其底層工作方式是:只要該客戶端的會話有效,就會在後臺持續發起刪除請求,直到該數據節點在 ZooKeeper 服務端被刪除。
  • deletingChildrenIfNeeded:指定了該函數後,系統在刪除該數據節點的時候會以遞歸的方式直接刪除其子節點,以及子節點的子節點。

Curator Caches
Curator 引入了 Cache 來實現對 Zookeeper 服務端事件監聽,Cache 事件監聽可以理解爲一個本地緩存視圖與遠程 Zookeeper 視圖的對比過程。Cache 提供了反覆註冊的功能。Cache 分爲兩類註冊類型:節點監聽和子節點監聽。

6.應用場景

ZooKeeper適用於存儲和協同相關的關鍵數據,不適合用於大數據量存儲。

有了上述衆多節點特性,使得 zookeeper 能開發不出不同的經典應用場景,比如:

  • 註冊中心
  • 數據發佈/訂閱(常用於實現配置中心)
  • 負載均衡
  • 命名服務
  • 分佈式協調/通知
  • 集羣管理
  • Master選舉
  • 分佈式鎖
  • 分佈式隊列

6.1分佈式鎖

目前分佈式鎖,比較成熟、主流的方案:

  • 1)基於數據庫的分佈式鎖。db操作性能較差,並且有鎖表的風險,一般不考慮。
  • 2)基於Redis的分佈式鎖。適用於併發量很大、性能要求很高而可靠性問題可以通過其他方案去彌補的場景。
  • 3)基於ZooKeeper的分佈式鎖。適用於高可靠(高可用),而併發量不是太高的場景。

基於Zookeeper設計思路一
使用臨時 znode 來表示獲取鎖的請求,創建 znode成功的用戶拿到鎖。

思考:上述設計存在什麼問題?

如果所有的鎖請求者都 watch 鎖持有者,當代表鎖持有者的 znode 被刪除以後,所有的鎖請求者都會通知到,但是隻有一個鎖請求者能拿到鎖。這就是羊羣效應。

基於Zookeeper設計思路二
使用臨時順序 znode 來表示獲取鎖的請求,創建最小後綴數字 znode 的用戶成功拿到鎖。

Curator 可重入分佈式鎖工作流程

總結

  • 優點:ZooKeeper分佈式鎖(如InterProcessMutex),具備高可用、可重入、阻塞鎖特性,可解決失效死鎖問題,使用起來也較爲簡單。
  • 缺點:因爲需要頻繁的創建和刪除節點,性能上不如Redis。

在高性能、高併發的應用場景下,不建議使用ZooKeeper的分佈式鎖。而由於ZooKeeper的高可用性,因此在併發量不是太高的應用場景中,還是推薦使用ZooKeeper的分佈式鎖。

6.2 註冊中心

Spring Cloud整合Zookeeper註冊中心核心源碼入口: ZookeeperDiscoveryClientConfiguration。

參考

  • 圖靈學院課程vip.tulingxueyuan.cn
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章