Zookeeper中幾個重要概念


zookeeper是hadoop下面的一個子項目, 用來協調跟hadoop相關的一些分布式的框架, 如hadoop, hive, pig等, 其實他們都是動物, 所以叫zookeeper ——“動物園管理員”。動物園里當然有好多的動物,游客可以根據動物園提供的向導圖到不同的場館觀賞各種類型的動物,而不是像走在原始叢林里,心驚膽顫的被動物所觀賞。為了讓各種不同的動物呆在它們應該呆的地方,而不是相互串門,或是相互廝殺,就需要動物園管理員按照動物的各種習性加以分類和管理,這樣我們才能更加放心安全的觀賞動物。回到我們企業級應用系統中,隨着信息化水平的不斷提高,我們的企業級系統變得越來越龐大臃腫,性能急劇下降,客戶抱怨頻頻。拆分系統是目前我們可選擇的解決系統可伸縮性和性能問題的唯一行之有效的方法。但是拆分系統同時也帶來了系統的復雜性——各子系統不是孤立存在的,它們彼此之間需要協作和交互,這就是我們常說的分布式系統。各個子系統就好比動物園里的動物,為了使各個子系統能正常為用戶提供統一的服務,必須需要一種機制來進行協調——這就是ZooKeeper——動物園管理員。

ZooKeeper本質上是一個分布式的小文件存儲系統。原本是Apache Hadoop的一個組件,現在被拆分為一個Hadoop的獨立子項目,在HBase(Hadoop的另外一個被拆分出來的子項目,用於分布式環境下的超大數據量的DBMS)中也用到了ZooKeeper集群。它主要是用來解決分布式應用中經常遇到的一些數據管理問題,如:統一命名服務、狀態同步服務、集群管理、分布式應用配置項的管理等。ZooKeeper有如下的特性:

1) 簡單

ZooKeeper核心是一個精簡的文件系統,它提供了一些簡單的文件操作以及附加的功能,例如排序和通知。

2) 易表達

ZooKeeper的數據結構原型是一棵znode樹(類似Linux的文件系統),並且它們是一些已經被構建好的塊,可以用來構建大型的協作數據結構和協議。

3) 高可用性

ZooKeeper可以運行在一組服務器上,同時它們被設計成高可用性,為你的應用程序避免單點故障。

4) 松耦合交互

ZooKeeper提供的Watcher機制使得各客戶端與服務器的交互變得松耦合,每個客戶端無需知曉其他客戶端的存在,就可以和其他客戶端進行數據交互。

5) 豐富的API

ZooKeeper為開發人員提供了一套豐富的API,減輕了開發人員編寫通用協議的負擔。

zookeeper其實是集群中每個節點都維護着一棵相同的樹, 樹的結構跟linux的目錄結構的概念差不多, 以/為跟節點, 下邊可以擴展任意的節點和葉子節點, 每個節點都可以寫入數據. 基於zookeeper的分布式鎖的實現, 其實是得益於zookeeper同步文件的強大性, 我們相信每時每刻我們訪問zookeeper的樹時, 相同節點返回的數據都是一致的. 這要靠zookeeper內部的一些算法來實現. 特別是leader的選舉算法。

本文從使用者的角度介紹Zookeeper配置文件的管理、集群管理、分布式鎖、Leader 選舉、隊列管理

分布式鎖

在分布式環境中,如果多個server一起訪問,可能會造成垃圾數據或重復執行,這時需要對資源(如數據庫)限制單server訪問,我們可以考慮用到分布式鎖。

下面介紹幾種可能的實現方式,並且對比每種實現方式的優缺點:

1. 利用節點名稱的唯一性來實現共享鎖

ZooKeeper抽象出來的節點結構是一個和unix文件系統類似的小型的樹狀的目錄結構。ZooKeeper機制規定:同一個目錄下只能有一個唯一的文件名。例如:我們在Zookeeper目錄/test目錄下創建,兩個客戶端創建一個名為Lock節點,只有一個能夠成功。

算法思路: 利用名稱唯一性,加鎖操作時,只需要所有客戶端一起創建/test/Lock節點,只有一個創建成功,成功者獲得鎖。解鎖時,只需刪除/test/Lock節點,其余客戶端再次進入競爭創建節點,直到所有客戶端都獲得鎖。

基於以上機制,利用節點名稱唯一性機制的共享鎖算法流程如圖所示:

利用節點名稱的唯一性來實現共享鎖

該共享鎖實現很符合我們通常多個線程去競爭鎖的概念,利用節點名稱唯一性的做法簡明、可靠。由上述算法容易看出,由於客戶端會同時收到/test/Lock被刪除的通知,重新進入競爭創建節點,故存在"驚群現象"。

使用該方法進行測試鎖的性能列表如下:

Lock機制的互斥測試

總結 這種方案的正確性和可靠性是ZooKeeper機制保證的,實現簡單。缺點是會產生“驚群”效應,假如許多客戶端在等待一把鎖,當鎖釋放時候所有客戶端都被喚醒,僅僅有一個客戶端得到鎖。

2. 利用臨時順序節點實現共享鎖的一般做法

首先介紹一下,Zookeeper中有一種節點叫做順序節點,故名思議,假如我們在/lock/目錄下創建節3個點,ZooKeeper集群會按照提起創建的順序來創建節點,節點分別為/lock/0000000001、/lock/0000000002、/lock/0000000003。

ZooKeeper中還有一種名為臨時節點的節點,臨時節點由某個客戶端創建,當客戶端與ZooKeeper集群斷開連接,則開節點自動被刪除。

利用上面這兩個特性,我們來看下獲取實現分布式鎖的基本邏輯:

  • 客戶端調用create()方法創建名為“locknode/guid-lock-”的節點,需要注意的是,這里節點的創建類型需要設置為EPHEMERAL_SEQUENTIAL。
  • 客戶端調用getChildren(“locknode”)方法來獲取所有已經創建的子節點,同時在這個節點上注冊上子節點變更通知的Watcher。
  • 客戶端獲取到所有子節點path之后,如果發現自己在步驟1中創建的節點是所有節點中序號最小的,那么就認為這個客戶端獲得了鎖。
  • 如果在步驟3中發現自己並非是所有子節點中最小的,說明自己還沒有獲取到鎖,就開始等待,直到下次子節點變更通知的時候,再進行子節點的獲取,判斷是否獲取鎖。

釋放鎖的過程相對比較簡單,就是刪除自己創建的那個子節點即可。

上面這個分布式鎖的實現中,大體能夠滿足了一般的分布式集群競爭鎖的需求。這里說的一般性場景是指集群規模不大,一般在10台機器以內。

不過,細想上面的實現邏輯,我們很容易會發現一個問題,步驟4,“即獲取所有的子點,判斷自己創建的節點是否已經是序號最小的節點”,這個過程,在整個分布式鎖的競爭過程中,大量重復運行,並且絕大多數的運行結果都是判斷出自己並非是序號最小的節點,從而繼續等待下一次通知——這個顯然看起來不怎么科學。客戶端無端的接受到過多的和自己不相關的事件通知,這如果在集群規模大的時候,會對Server造成很大的性能影響,並且如果一旦同一時間有多個節點的客戶端斷開連接,這個時候,服務器就會像其余客戶端發送大量的事件通知——這就是所謂的驚群效應。而這個問題的根源在於,沒有找准客戶端真正的關注點。

我們再來回顧一下上面的分布式鎖競爭過程,它的核心邏輯在於:判斷自己是否是所有節點中序號最小的。於是,很容易可以聯想的到的是,每個節點的創建者只需要關注比自己序號小的那個節點。

3. 利用臨時順序節點實現共享鎖的改進實現

下面是改進后的分布式鎖實現,和之前的實現方式唯一不同之處在於,這里設計成每個鎖競爭者,只需要關注”locknode”節點下序號比自己小的那個節點是否存在即可。

算法思路:對於加鎖操作,可以讓所有客戶端都去/lock目錄下創建臨時順序節點,如果創建的客戶端發現自身創建節點序列號是/lock/目錄下最小的節點,則獲得鎖。否則,監視比自己創建節點的序列號小的節點(比自己創建的節點小的最大節點),進入等待。

對於解鎖操作,只需要將自身創建的節點刪除即可。

具體算法流程如下圖所示:

利用臨時順序節點實現共享鎖

使用上述算法進行測試的的結果如下表所示:

Lock機制的互斥測試

該算法只監控比自身創建節點序列號小(比自己小的最大的節點)的節點,在當前獲得鎖的節點釋放鎖的時候沒有“驚群”。

總結 利用臨時順序節點來實現分布式鎖機制其實就是一種按照創建順序排隊的實現。這種方案效率高,避免了“驚群”效應,多個客戶端共同等待鎖,當鎖釋放時只有一個客戶端會被喚醒。

4. 使用menagerie

其實就是對方案3的一個封裝,不用自己寫代碼了。直接拿來用就可以了。

menagerie基於Zookeeper實現了java.util.concurrent包的一個分布式版本。這個封裝是更大粒度上對各種分布式一致性使用場景的抽象。其中最基礎和常用的是一個分布式鎖的實現: org.menagerie.locks.ReentrantZkLock,通過ZooKeeper的全局有序的特性和EPHEMERAL_SEQUENTIAL類型znode的支持,實現了分布式鎖。具體做法是:不同的client上每個試圖獲得鎖的線程,都在相同的basepath下面創建一個EPHEMERAL_SEQUENTIAL的node。EPHEMERAL表示要創建的是臨時znode,創建連接斷開時會自動刪除; SEQUENTIAL表示要自動在傳入的path后面綴上一個自增的全局唯一后綴,作為最終的path。因此對不同的請求ZK會生成不同的后綴,並分別返回帶了各自后綴的path給各個請求。因為ZK全局有序的特性,不管client請求怎樣先后到達,在ZKServer端都會最終排好一個順序,因此自增后綴最小的那個子節點,就對應第一個到達ZK的有效請求。然后client讀取basepath下的所有子節點和ZK返回給自己的path進行比較,當發現自己創建的sequential node的后綴序號排在第一個時,就認為自己獲得了鎖;否則的話,就認為自己沒有獲得鎖。這時肯定是有其他並發的並且是沒有斷開的client/線程先創建了node。

選舉

隊列 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM