Zookeeper應用場景
資料釋出/訂閱(配置中心)
我們平常的開發過程中,經常會碰到這樣的需求:系統中需要一些通用的配置資訊,如一些執行時的開關、前端需要展示的通知資訊、資料庫配置資訊等等。這些需求通常都要求具備3個特性:
- 資料量比較小
- 資料內容在執行時會變化
- 叢集中的所有機器共享,資料一致
我們假設把這些資料儲存在應用的記憶體中,那麼資料共享是一個問題,資料變更的通知又是一個問題。
那我們使用Zookeeper怎麼實現呢?
- 配置儲存
我們可以先把資料儲存在Zookeeper的一個節點上。如/app/database_config
2. 配置獲取
應用啟動時,首先會去前面儲存的節點上去拿資料,並且在這個節點上註冊一個資料變更的Watcher監聽。一旦傳送資料變化,叢集中所有應用都能收到通知
3. 配置變更
我們需要修改的時候,就利用Zookeeper的修改內容介面對節點上的資料進行修改即可。Zookeeper會自動幫我們傳送資料變更通知。
命名服務
命名服務是分散式系統中比較常見的一個場景。比如我們需要在創單的時候給這個訂單生成一個全域性唯一的訂單號。如果是單體應用那麼還比較好做,資料庫就有自增ID,再拼上一些訂單號字首就可以了。但是如果是分散式環境下,就會麻煩一些。
那我們使用Zookeeper怎麼實現呢?
- 利用Zookeeper的順序節點能夠維護每個資料節點的順序的特性,就可以做到。
Master選舉
我們有時候有這樣的一個需求,在叢集環境下,只需要一個應用來處理某個耗時資源。我們可以怎麼處理呢?
先假設我們通過資料庫來處理,我們向一個表中插入同樣的一個id的資料,根據資料庫主鍵的唯一特性,只有一個應用能夠操作成功,那麼這個應用就可以被選中成為Master,來執行這個耗時的操作。
但是我們考慮下面這個問題:如果被選中的這個Master機器當機了,怎麼告訴其他機器重新選舉呢?不好實現。
利用Zookeeper可以實現這樣的需求。
- 我們所有的應用同時在Zookeeper上建立一個臨時節點,如:/master/binding,只有一個應用能夠建立成功,我們稱他為master應用,那麼其他沒建立成功的應用可以監聽這個節點。
- 一旦Master應用當機了,這個節點因為是臨時節點會被自動刪除,就將通知到其他的應用進行重新選舉。
分散式鎖
Zookeeper是一個比較知名的實現分散式鎖的一個方案。我們分排他鎖和共享鎖兩類來講解。
排他鎖
排他鎖,又稱寫鎖或獨佔鎖。如果事務T1對資料物件A加了排他鎖,那麼整個加鎖期間,其他事務都不能對這個物件A進行任何型別的操作,知道事務T1釋放了排他鎖。
下⾯我們就來看看如何藉助ZooKeeper實現排他鎖:
- 獲取鎖。我們先定義一個臨時節點作為鎖的節點。如:/order/lock。在執行時所有應用都會來建立這個節點,但是隻有一個應用能夠建立成功。建立成功的應用我們認為他獲取到了鎖。此時它可以執行它的業務方法。同時沒有獲取到鎖的應用可以註冊一個Watcher監聽,以便實時監聽節點變化
- 釋放鎖。當業務執行完畢後,我們要主動刪除這個臨時節點。此時其他的應用就可以去重新獲取鎖。
當持有鎖的機器當機了,也不會造成死鎖。因為臨時節點會自動刪除。
也可以建立臨時有序節點,只監聽比自己小一位的那個子節點。避免持有鎖的節點執行完釋放鎖的時候就需要通知所有監聽著的應用(羊群效應)。
共享鎖
共享鎖,又稱讀鎖。如果事務T1對物件A加了共享鎖,那麼當前事務只能對A進行讀操作。其他事務T2、T3也能對這個物件加共享鎖。
- 我們需要在節點上定義是讀操作還是寫操作。如/order/lock-W-0000001 、/order/lock-R-0000002。
- 應用呼叫建立節點介面,來建立臨時順序節點
- 應用呼叫getChildren介面獲取所有已建立的所有子節點列表
- 如果應用是寫請求,判斷它是不是最小的節點的話,如果是就獲取鎖成功。否正向比它序號小的前一個節點註冊監聽
- 如果應用是讀操作,判斷它是不是最小的節點,如果是最小的節點或者比它小的都是讀節點,就獲取鎖成功。否則向比自己序號小的最後一個寫節點註冊監聽。
- 等待watcher通知,繼續進入步驟2
分散式佇列
分散式佇列可以簡單分為兩⼤類:⼀種是常規的FIFO先⼊先出佇列模型,還有⼀種是等待佇列元素聚集後統⼀安排處理執⾏的Barrier模型
FIFO(First Input First Output)
使用Zookeeper實現FIFO佇列,也是依賴於Zookeeper的順序節點特性。思路如下:
- 所有應用都在/queue這個節點下建立臨時順序節點
- 呼叫getChildren介面來獲取他下面的所有子節點,相當於獲取佇列中的所有元素
- 判斷自己的序號是不是最小的,如果是就執行業務,執行完後刪除節點。如果不是,就監聽比自己序號小的最後一個節點
- 收到watcher後,重複步驟2
Barrier:分散式屏障
分散式屏障特指系統的一個協調條件,規定佇列中的元素聚齊後才能進行統一安排。比如所有的員工都把工作做完了,才一起下班去吃飯。
設計思路如下:
建立一個/queue_barrier節點,並且給這個節點賦值數字n,這個n代表只有當/queue_barrier節點下有n個子節點時,才能進行下一步操作。
- 呼叫getData獲取/queue_barrier節點的內容,假設內容是10
- 呼叫getChildren介面看看子節點個數到達10了沒,沒有就註冊子節點變更監聽.達到了就開始執行業務
- 接收到通知後,需要重複步驟2
ZAB協議
zookeeper就是使用了ZAB協議來實現的分散式資料一致性。ZAB協議是專門為zookeeper設計的一種支援崩潰恢復的原子廣播協議。
ZAB核心
ZAB協議定義了那些會改變Zookeeper伺服器資料狀態的事務請求的處理方式。
所有這些會修改狀態的事務請求必須由一個全域性唯一的伺服器來協調處理,也就是Leader伺服器。Leader伺服器負責將客戶端請求轉化為事務Proposal(提議),並將Proposal分發給叢集中所有的Follower伺服器,之後Leader伺服器需要等待所有Follower伺服器的反饋,一旦超過半數的Follower伺服器進行了正確的反饋後,Leader接下來就會再次向所有的Follower伺服器發Commit訊息,把剛才的Proposal進行提交。
ZAB兩種模式:崩潰恢復和訊息廣播
訊息廣播
在訊息⼴播過程中,Leader伺服器會為每⼀個Follower伺服器都各⾃分配⼀個單獨的佇列,然後將需要⼴播的事務Proposal依次放⼊這些佇列中去,並且根據 FIFO策略進⾏訊息傳送。每⼀個Follower伺服器在接收到這個事務Proposal之後,都會⾸先將其以事務⽇志的形式寫⼊到本地磁碟中去,並且在成功寫⼊後反饋給Leader伺服器⼀個Ack響應。當Leader伺服器接收到超過半數Follower的Ack響應後,就會⼴播⼀個Commit訊息給所有的Follower伺服器以通知其進⾏事務提交,同時Leader⾃身也會完成對事務的提交,⽽每⼀個Follower伺服器在接收到Commit訊息後,也會完成對事務的提交。
我們考慮如下兩個問題:
- Leader伺服器發出proposal還沒有提交時當機了
- Leader伺服器自己提交後,發出部分讓Follower COMMIT的請求後就當機了
解決這個問題需要ZAB的崩潰恢復模式
崩潰恢復
Leader當機後
- 要保證已經在Leader伺服器提交的事務被所有伺服器提交
- 丟棄那些只是在Leader伺服器提出proposal未提交的事務
針對上面兩個要求,如果讓Leader選舉演算法能夠保證新選舉出來的Leader伺服器擁有叢集中所有機器最⾼編號(即ZXID最⼤)的事務Proposal,那麼就可以保證這個新選舉出來的Leader⼀定具有所有已經提交的提案。他成了Leader後他有的事務肯定是已提交的所有事務了。
新Leader產生後,在正式開始⼯作(即接收客戶端的事務請求,然後提出新的提案)之前,
Leader伺服器會⾸先確認事務⽇志中的所有Proposal是否都已經被叢集中過半的機器提交了,即是否完成資料同步。
資料同步
它會為每一個Follower伺服器準備一個佇列,將那些沒有被各Follower伺服器同步的事務以Proposal訊息的方式傳送給Follower伺服器,並在每一個Proposal訊息後緊接著再傳送一個Commit訊息。等到Follower伺服器將所有尚未同步的事務都成功應用到本地資料庫後,Leader伺服器就會將該Follower伺服器加入真正可用的Follower列表中。
運⾏時狀態分析
在ZAB協議的設計中,每個程式都有可能處於如下三種狀態之⼀
- LOOKING:Leader選舉階段。
- FOLLOWING:Follower伺服器和Leader伺服器保持同步狀態。
- LEADING:Leader伺服器作為主程式領導狀態。
所有程式初始狀態都是LOOKING狀態,此時不存在Leader。接下來,程式會試圖選舉出⼀個新的Leader,如果程式發現已經選舉出新的Leader了,那麼它就會切換到FOLLOWING狀態,並開始和Leader保持同步。處於FOLLOWING狀態的程式稱為Follower,LEADING狀態的程式稱為Leader,當Leader崩潰或放棄領導地位時,其餘的Follower程式就會轉換到LOOKING狀態開始新⼀輪的Leader選舉。
⼀個Follower只能和⼀個Leader保持同步,Leader程式和所有的Follower程式之間都通過⼼跳檢測機制來感知彼此的情況。若Leader能夠在超時時間內正常收到⼼跳檢測,那麼Follower就會⼀直與該Leader保持連線,⽽如果在指定時間內Leader⽆法從過半的Follower程式那⾥接收到⼼跳檢測,或者TCP連線斷開,那麼Leader會放棄當前週期的領導,並轉換到LOOKING狀態,其他的Follower也會選擇放棄這個Leader,同時轉換到LOOKING狀態,之後會進⾏新⼀輪的Leader選舉。
ZAB與Paxos的聯絡和區別
聯絡:
- 都存在一個類似Leader的角色
- Leader程式都會等待超半數的Follower做出正確反饋後,才會將一個提議提交
- 在ZAB協議中,每個Proposal中都包含了⼀個epoch值,⽤來代表當前的Leader週期,在Paxos演算法中,同樣存在這樣的⼀個標識,名字為Ballot。
區別:
- Paxos演算法中,新選舉產⽣的主程式會進⾏兩個階段的⼯作,第⼀階段稱為讀階段,新的主程式和其他程式通訊來收集主程式提出的提議,並將它們提交。第⼆階段稱為寫階段,當前主程式開始提出⾃⼰的提議
- ZAB協議在Paxos基礎上新增了同步階段,此時,新的Leader會確儲存在過半的Follower已經提交了之前的Leader週期中的所有事務Proposal。這⼀同步階段的引⼊,能夠有效地保證Leader在新的週期中提出事務Proposal之前,所有的程式都已經完成了對之前所有事務Proposal的提交。
總的來說,ZAB協議和Paxos演算法的本質區別在於,兩者的設計⽬標不太⼀樣。ZAB協議主要⽤於構建⼀個⾼可⽤的分散式資料主備系統,⽽Paxos演算法則⽤於構建⼀個分散式的⼀致性狀態機系統。
最終,我們討論一個問題
Zookeeper是屬於強一致性還是弱一致性?
答案:寫入是強一致,讀取是順序一致性。Zookeeper官方說是順序一致性。
因為客戶讀連線ZooKeeper叢集后,所有的寫操作都必須傳送給叢集唯一的leader,這個leader在內部同步塊中賦予每個寫操作一個順序序列號(內部稱為zxid,是單調增加的),上一個寫操作不commit,下一個寫操作就不執行,這一點實際上已經實現了寫入的強一致性(線性化)了
通過嚴格按照ZXID的順序生效提案保證其順序一致性的。
Zookeeper它還為我們提供sync()方法,強制讀取在時候從Leader同步資料。