ZooKeeper 分布式數(shù)據(jù)結(jié)構(gòu)(四)

前面說了ZooKeeper一些基礎(chǔ)性的東西,包括客戶端編程框架。這里我們來探索如何更好的運(yùn)用ZooKeeper。
開始之前,我想先借用Linus Torvalds(Linux創(chuàng)始人)的一句話。

Bad programmers worry about the code. Good programmers worry about data structures and their relationships
差的程序員關(guān)注代碼,好的程序員關(guān)注數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)之間的關(guān)系。

與文無關(guān)

不管如何,我們能感受到數(shù)據(jù)結(jié)構(gòu)的重要性,而平時我們開發(fā)所涉及到的各種東西,本質(zhì)還是:
程序=算法+數(shù)據(jù)結(jié)構(gòu)。數(shù)據(jù)結(jié)構(gòu)也是編程的基礎(chǔ),我們這里說如何通過ZooKeeper構(gòu)建分布式系統(tǒng),和它涉及的數(shù)據(jù)結(jié)構(gòu)。

涉及到的數(shù)據(jù)結(jié)構(gòu):

  • Barrir (障礙)
  • Quene (隊(duì)列)
  • Lock (鎖)
  • Leader Selection
  • Group MemberShip
  • Service Discovery

這是一篇介紹技術(shù)的文章,但是里面一張圖也沒有,捂臉...

Barrier

Barrier中文譯為柵欄,障礙。意思是暫時阻塞部分東西,直到某個條件滿足。比如:“百米賽跑的起跑線,把起跑線當(dāng)做障礙,大家還不能跑,只有人到齊了才能跑”。意思就是滿足某個條件之后才能繼續(xù)接下來的操作。

可以參考多線程的:CountDownLatch和CyclicBarrir。

使用ZooKeeper算法實(shí)現(xiàn):(使用偽代碼)

  1. 首先,創(chuàng)建一個障礙節(jié)點(diǎn) /zk_barrier
  2. 如果障礙節(jié)點(diǎn)存在的話,也就是系統(tǒng)中是存在某個障礙,需要滿足某個條件的。
  3. 客戶端在/zk_barrier節(jié)點(diǎn)調(diào)用exists()方法,注冊watch事件。
    1. 如果exists()方法返回為false,也就是條件滿足,客戶端可以進(jìn)行接下來的操作了。
    2. 如果exists()方法返回為true,客戶端就等待watch的事件。
  4. 當(dāng)障礙節(jié)點(diǎn)需要的條件滿足的時候,負(fù)責(zé)/zk_barrier節(jié)點(diǎn)的客戶端會刪除這個節(jié)點(diǎn)。
  5. 刪除節(jié)點(diǎn)觸發(fā)watch事件,客戶端再次調(diào)用exists()方法再障礙節(jié)點(diǎn)上。
  6. 如果exists()返回為true的話,客戶端繼續(xù)原本的操作,等待條件滿足。

上面的這個結(jié)構(gòu)相對比較簡單,還有種結(jié)構(gòu)用來 鎖住分布式計(jì)算的開始和結(jié)束,也叫做Double Barrier. 雙重障礙的實(shí)現(xiàn),當(dāng)節(jié)點(diǎn)的數(shù)量滿足條件的時候,開始任務(wù),當(dāng)節(jié)點(diǎn)的數(shù)量為0的時候,結(jié)束任務(wù)。

算法實(shí)現(xiàn):
第一部分:

  1. 首先創(chuàng)建一個/barrier節(jié)點(diǎn),其余客戶端在障礙節(jié)點(diǎn)上注冊事件,并且在/barrier節(jié)點(diǎn)下面創(chuàng)建臨時節(jié)點(diǎn),也就是以/barrier節(jié)點(diǎn)為父節(jié)點(diǎn)。 新創(chuàng)建的節(jié)點(diǎn)一般是客戶端自己的主機(jī)名。

  2. 客戶端同時注冊一個事件,檢查/barrier/ready節(jié)點(diǎn)是否存在。

  3. 系統(tǒng)中已經(jīng)預(yù)定義了 N 數(shù)字,這個數(shù)目為任務(wù)開始的最小滿足節(jié)點(diǎn)數(shù)。節(jié)點(diǎn)的數(shù)目必須要等于或多與N的時候,才開始任務(wù)。

  4. 當(dāng)有新的節(jié)點(diǎn)加入的時候,新的節(jié)點(diǎn)都獲取一下當(dāng)前/barrier節(jié)點(diǎn)下子節(jié)點(diǎn)的數(shù)目。注意getChildren沒有設(shè)置監(jiān)聽事件。
    M = getChildren(/barrier, watch=false)

  5. 如果M小于N,繼續(xù)等待ready節(jié)點(diǎn)的出現(xiàn)。

  6. 如果M等于N,然后這個客戶端在/barrier節(jié)點(diǎn)下創(chuàng)建/barrier/ready節(jié)點(diǎn)。

  7. 由于之前有節(jié)點(diǎn)注冊了監(jiān)聽事件,每個客戶端都會開始任務(wù)。
    第二部分:

  8. 客戶端完成它需要完成的任務(wù)之后,刪除它在/barrier節(jié)點(diǎn)下創(chuàng)建的子節(jié)點(diǎn)。

  9. 刪除完之后,客戶端再調(diào)用
    M = getChildren(/barrier, watch=true)。
    如果M大于0,客戶端仍然等待通知。
    如果M等于0,客戶端退出。

上面可能有個風(fēng)險(xiǎn)是網(wǎng)絡(luò)流量過于龐大,一旦ready節(jié)點(diǎn)注冊,它像所有的節(jié)點(diǎn)發(fā)送通知。有個辦法是每個客戶端注冊ready的下一個最小的序列臨時節(jié)點(diǎn)。這樣每次只有一個節(jié)點(diǎn)收到通知。不會一下子所有的節(jié)點(diǎn)都被喚醒。

Queue

分布式隊(duì)列也是分布式系統(tǒng)中一種非常常見的數(shù)據(jù)結(jié)構(gòu),一種比較特殊的隊(duì)列叫做。生產(chǎn)者-消費(fèi)者 隊(duì)列。有一個集合,生產(chǎn)者生產(chǎn)的東西存放在這個集合里面,消費(fèi)者從集合中拿出東西來消費(fèi)。它遵守FIFO(先進(jìn)先出原則)。

直接看偽代碼:

  1. 創(chuàng)建一個/QUEUE節(jié)點(diǎn),用作實(shí)現(xiàn)隊(duì)列的集合。

  2. 作為生產(chǎn)者的客戶端,調(diào)用create()方法創(chuàng)建名為"queue-"的節(jié)點(diǎn),這個節(jié)點(diǎn)是臨時節(jié)點(diǎn),同時是序列節(jié)點(diǎn),并且是/QUEUE的子節(jié)點(diǎn)。一般節(jié)點(diǎn)名稱像queue-N

  3. 作為消費(fèi)者的客戶端,在/QUEUE節(jié)點(diǎn)上調(diào)用getChildren()方法,同時設(shè)置監(jiān)聽事件。
    M = getChildren(/_QUEUE_, true)
    對子節(jié)點(diǎn)排序,拿出數(shù)字最小的節(jié)點(diǎn)進(jìn)行消費(fèi)。然后刪除最小的那個節(jié)點(diǎn)。
    有可能在刪除節(jié)點(diǎn)的時候,因?yàn)橛衅渌墓?jié)點(diǎn)在對這個節(jié)點(diǎn)進(jìn)行訪問,導(dǎo)致刪除失敗,這時候我恩需要再次重試刪除

  4. 消費(fèi)者的客戶端不斷的從子節(jié)點(diǎn)列表中取出節(jié)點(diǎn)進(jìn)行消費(fèi),一旦列表中的節(jié)點(diǎn)都被消費(fèi)了。消費(fèi)者重新調(diào)用getChildren方法。

  5. 直到getChildren()返回為空的列表的時候,這意味著/QUEUE節(jié)點(diǎn)下沒有更多的節(jié)點(diǎn)了。

有更多精力的話可以實(shí)現(xiàn)優(yōu)先級隊(duì)列。

注意:建議看一下ZooKeeper官方對這兩種數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)地址為http://zookeeper.apache.org/doc/r3.4.6/zookeeperTutorial.html

分布式鎖

分布式鎖也就是意味著在任何時間,系統(tǒng)中最多只能有一個客戶端持有這把鎖。一般在下面場景的時候有用:

  • 寫入共享數(shù)據(jù)庫或文件
  • 處理I/O請求

簡單描述一下共享鎖:假設(shè)在lock-node下同時創(chuàng)建了三個子節(jié)點(diǎn),l1,l2,l3。那么創(chuàng)建l1的子節(jié)點(diǎn)擁有鎖。當(dāng)客戶端想釋放鎖的時候,只需要刪除l1節(jié)點(diǎn),然后創(chuàng)建l2的客戶端就擁有了鎖,依次類推。

偽代碼實(shí)現(xiàn):
第一部分:獲取鎖

  1. 在/locknode下創(chuàng)建子節(jié)點(diǎn),這個節(jié)點(diǎn)是 臨時有序的,create("/_locknode_/lock-",CreateMode=EPHEMERAL_SEQUENTIAL)
  2. 調(diào)用getChilren(/locknode/lock-,false)方法,不設(shè)置監(jiān)聽器。
  3. 檢查獲取到的節(jié)點(diǎn)列表,如果自己創(chuàng)建的節(jié)點(diǎn)擁有最小的序號,那么當(dāng)前客戶端擁有鎖。退出算法。
  4. 調(diào)用exists("/_locknode_/<最小的節(jié)點(diǎn)>, True)方法
  5. 如果exists返回為false,那么重新進(jìn)行第2步。
  6. 如果exists為true,那么等待存在的這個節(jié)點(diǎn)的 watch事件。然后進(jìn)行第4步。

第二部分:釋放鎖

  1. 持有鎖的客戶端刪除節(jié)點(diǎn),觸發(fā)下一個節(jié)點(diǎn)獲取鎖。
  2. 比當(dāng)前節(jié)點(diǎn)序號大的程度最小節(jié)點(diǎn)將會獲取到鎖。

Leader選取

Leader選取算法有兩個要求:

  • 大多數(shù)時間內(nèi),只有一個Leader
  • 在任何時間內(nèi),要么沒有Leader要么只有一個Leader

暫時我們先把Leader選取算法理解為鎖算法,誰擁有了鎖,誰就是Leader。關(guān)于Leader選取算法還需要深入分析。

Group Membership 成員關(guān)系

組員關(guān)系也是分布式系統(tǒng)的核心組件,目的是為了維護(hù)服務(wù)的可靠性和一致性。它可以讓組內(nèi)的任何一個實(shí)體知道當(dāng)前組的狀況,誰加入了進(jìn)來,誰離開了。

實(shí)現(xiàn)起來較為簡單,偽代碼如下:

  1. 創(chuàng)建/Membership持久節(jié)點(diǎn),代表成員關(guān)系樹
  2. 加入組的節(jié)點(diǎn)在/Membership節(jié)點(diǎn)下創(chuàng)建臨時節(jié)點(diǎn)。
  3. 所有的組內(nèi)成員都有注冊/Membership的監(jiān)聽事件,因此可以知悉/Membership節(jié)點(diǎn)下的變化
  4. 當(dāng)有新的節(jié)點(diǎn)加入進(jìn)來,其它的節(jié)點(diǎn)會收到通知。當(dāng)有節(jié)點(diǎn)離開,或故障了,ZooKeeper也會自動的刪除這個節(jié)點(diǎn),所有的組員也會知道。
  5. 當(dāng)有節(jié)點(diǎn)想知道其它節(jié)點(diǎn)的狀態(tài)。使用getChildren()獲取/Membership節(jié)點(diǎn)下的子節(jié)點(diǎn)即可。

服務(wù)發(fā)現(xiàn)

如果有認(rèn)真看上面的幾種實(shí)現(xiàn),我想大部分人都該了解ZooKeeper的套路了,服務(wù)發(fā)現(xiàn),也就是我們要知道提供某個服務(wù)的服務(wù)器有哪些。我們?nèi)フ艺l來提供服務(wù)。

可以理解為“物以類聚,人以群分”,這樣一群一群的,就變成了上面的成員關(guān)系管理。數(shù)據(jù)庫服務(wù)的一組,web服務(wù)的一組。文件服務(wù)器的一組,想要知道這個組內(nèi)的狀態(tài),獲取這個組的節(jié)點(diǎn)就好了。比如web服務(wù)器,可以創(chuàng)建/web_service節(jié)點(diǎn),然后web服務(wù)都去這個節(jié)點(diǎn)找。

其余的就不多說了。和成員關(guān)系基本一樣

實(shí)現(xiàn)

光有理論不去實(shí)現(xiàn)怎么行呢,代碼貼出來一方面不太好講解,編程靠的是思想,你把東西想明白了,開發(fā)起來才容易,自己都想不明白的事情,又如何指望計(jì)算機(jī)來幫你處理呢。

關(guān)于本次所有涉及的理論部分,實(shí)現(xiàn)的地方有兩處:

  1. ZooKeeper的github項(xiàng)目實(shí)現(xiàn) https://github.com/apache/zookeeper/tree/master/src/recipes ,它實(shí)現(xiàn)了Quene,Lock,Leader Selection
  2. Apache Curaror直接給我們封裝好了常用的分布式數(shù)據(jù)結(jié)構(gòu),追求快速的話,可以直接使用Apache Curator. maven包為org.apache.curator.curator-recipes,貌似curator也實(shí)現(xiàn)了服務(wù)發(fā)現(xiàn)。感興趣的都可以自己看。

最后

這次主要還是理論偏多,但是大型項(xiàng)目所依賴的也不過是這些基礎(chǔ)的部分。關(guān)于ZooKeeper的應(yīng)用場景,如何使用到這些數(shù)據(jù)結(jié)構(gòu)的,下面我們再談。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容