1. 程式人生 > >The ZooKeeper service

The ZooKeeper service

ZooKeeper是一個分散式的應用程式協調服務。

2 ZooKeeper的工作原理 Zookeeper 的核心是原子廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫做Zab(Zookeeper Atomic Broadcast)協議。Zab協議有兩種模式,它們分別是恢復模式(recovery選主)和 廣播模式(broadcast同步)。當服務啟動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數Server完成了和leader的狀態同步以 後,恢復模式就結束了。狀態同步保證了leader和Server具有相同的系統狀態。

1、ZooKeeper資料模型: 類似於一個標準的檔案系統,具有層次關係的資料結構 每個子目錄項如NameService都被稱作為znode。 ZNode根據其本身的特性,可以分為下面兩類: Regular ZNode: 常規型ZNode, Ephemeral ZNode: (ɪ’fem(ə)r(ə)l)(臨時的)型別的目錄節點不能有子節點目錄。 Zookeeper的客戶端和伺服器通訊採用長連線方式,每個客戶 端和伺服器通過心跳來保持連線,這個連線狀態稱為session,如果znode是臨時節點,這個session失效,znode也就刪除了。(3s/一次,200次)。 如果Client因為Timeout和Zookeeper Server失去連線,client處在CONNECTING狀態,會自動嘗試再去連線Server,如果在session有效期內再次成功連線到某個Server,則回到CONNECTED狀態。

2、ZooKeeper Watch:

Zookeeper從設計模式的角度來看,是一個基於觀察者設計模式設計的。簡單來說就是

Client可以在某個ZNode上設定一個Watcher,來Watch該ZNode上的變化。如果該ZNode上有相應的變化,就會觸發這個Watcher,把相應的事件通知給設定Watcher的Client。需要注意的是,ZooKeeper中的Watcher是一次性的,即觸發一次就會被取消,如果想繼續Watch的話,需要客戶端重新設定Watcher。

3、ZooKeeper特性 :

讀、寫(更新)模式:

在ZooKeeper叢集中,讀可以從任意一個ZooKeeper Server讀。寫的請求會先Forwarder到Leader,然後由Leader來通過ZooKeeper中的原子廣播協議,將請求廣播給所有的Follower,Leader收到一半以上的寫成功的訊息後,就認為該寫成功了,就會將該寫進行持久化,並告訴客戶端寫成功了。

FIFO 對於每一個ZooKeeper客戶端而言,所有的操作都是遵循FIFO順序的,這一特性是由下面兩個基本特性來保證的:一是ZooKeeper Client與Server之間的網路通訊是基於TCP,TCP保證了Client/Server之間傳輸包的順序;二是ZooKeeper Server執行客戶端請求也是嚴格按照FIFO順序的。 為 了保證事務的順序一致性,zookeeper採用了遞增的事務id號(zxid)來標識事務。所有的提議(proposal)都在被提出的時候加上了 zxid。實現中zxid是一個64位的數字,它高32位是epoch用來標識leader關係是否改變,每次一個leader被選出來,它都會有一個新 的epoch,標識當前屬於那個leader的統治時期。低32位用於遞增計數。

ZooKeeper典型應用場景 1.名字服務(NameService) : 每個ZNode都可以由其路徑唯一標識,路徑本身也比較簡潔直觀,另外ZNode上還可以儲存少量資料,這些都是實現統一的NameService的基礎。通過簡單的名字,訪問對應的伺服器叢集。

2.配置管理(Configuration Management) : 在這裡插入圖片描述

一:分散式互斥鎖 在傳統的應用程式中,執行緒、程序的同步,都可以通過作業系統提供的機制來完成。但是在分散式系統中,多個程序之間的同步,作業系統層面就無能為力了。 zookeeper中,並沒有像JAVA裡一樣有Synchronized或者是ReentrantLock機制來實現鎖機制,但是在zookeeper中,實現起來更簡單:我們可以講將zk的一個數據節點代表一個鎖,當多個客戶端同時呼叫create()節點建立節點的時候,zookeeper會保證只會有一個客戶端建立成功,那麼我們就可以讓這個建立成功的客戶端讓其持有鎖,而其它的客戶端則註冊Watcher監聽當持有鎖的客戶端釋放鎖後,監聽的客戶端就會收到Watcher通知,然後再去試圖獲取鎖,這樣反覆即可。 Zookeeper的三種角色: 1.leader和follower

  ZooKeeper需要在所有的服務(可以理解為伺服器)中選舉出一個Leader,然後讓這個Leader來負責管理叢集。此時,叢集中的其它伺服器則 成為此Leader的Follower。並且,當Leader故障的時候,需要ZooKeeper能夠快速地在Follower中選舉出下一個 Leader。這就是ZooKeeper的Leader機制,下面我們將簡單介紹在ZooKeeper中,Leader選舉(Leader Election)是如何實現的。 

此操作實現的核心思想是:首先建立一個EPHEMERAL目錄節點,例如“/election”。然後。每一個ZooKeeper伺服器在此目錄 下建立一個SEQUENCE|EPHEMERAL型別的節點,例如“/election/n_”。在SEQUENCE標誌下,ZooKeeper將自動地 為每一個ZooKeeper伺服器分配一個比前一個分配的序號要大的序號。此時建立節點的ZooKeeper伺服器中擁有最小序號編號的伺服器將成為 Leader。 在實際的操作中,還需要保障:當Leader伺服器發生故障的時候,系統能夠快速地選出下一個ZooKeeper伺服器作為Leader。一個簡 單的解決方案是,讓所有的follower監視leader所對應的節點。當Leader發生故障時,Leader所對應的臨時節點將會自動地被刪除,此 操作將會觸發所有監視Leader的伺服器的watch。這樣這些伺服器將會收到Leader故障的訊息,並進而進行下一次的Leader選舉操作。但 是,這種操作將會導致“從眾效應”的發生,尤其當叢集中伺服器眾多並且頻寬延遲比較大的時候,此種情況更為明顯。 在Zookeeper中,為了避免從眾效應的發生,它是這樣來實現的:每一個follower對follower叢集中對應的比自己節點序號小一 號的節點(也就是所有序號比自己小的節點中的序號最大的節點)設定一個watch。只有當follower所設定的watch被觸發的時候,它才進行 Leader選舉操作,一般情況下它將成為叢集中的下一個Leader。很明顯,此Leader選舉操作的速度是很快的。因為,每一次Leader選舉幾 乎只涉及單個follower的操作。 2.Observer observer的行為在大多數情況下與follower完全一致, 但是他們不參加選舉和投票, 而僅僅接受(observing)選舉和投票的結果. Zookeeper叢集,選舉機制

zookeeper選舉機制 FastLeaderElection演算法通過非同步的通訊方式來收集其它節點的選票,同時在分析選票時又根據投票者的當前狀態來作不同的處理,以加快Leader的選舉程序。 每個在zookeeper伺服器啟動先讀取當前儲存在磁碟的資料,zookeeper中的每份資料都有一個對應的id值,這個值是依次遞增的;換言之,越新的資料,對應的ID值就越大。 在讀取資料完畢之後,每個zookeeper伺服器傳送自己選舉的leader,這個協議中包含了以下幾部分的資料: 1)、所選舉leader的id(就是配置檔案中寫好的每個伺服器的id) ,在初始階段,每臺伺服器的這個值都是自己伺服器的id,也就是它們都選舉自己為leader。 2)、伺服器最大資料的id,這個值大的伺服器,說明存放了更新的資料。 3)、邏輯時鐘的值,這個值從0開始遞增,每次選舉對應一個值,也就是說:如果在同一次選舉中,那麼這個值應該是一致的,邏輯時鐘值越大,說明這一次選舉leader的程序更新。 4)、本機在當前選舉過程中的狀態,有以下幾種:LOOKING,FOLLOWING,OBSERVING,LEADING

每臺伺服器將自己伺服器的以上資料傳送到叢集中的其他伺服器之後,同樣的也需要接收來自其他伺服器的資料,它將做以下的處理: 

A、如果所接收資料伺服器的狀態還是在選舉階段(LOOKING 狀態),那麼首先判斷邏輯時鐘值,又分為以下三種情況: a) 如果傳送過來的邏輯時鐘大於目前的邏輯時鐘,那麼說明這是更新的一次選舉,此時需要更新一下本機的邏輯時鐘值,程式碼如下: if (n.epoch > logicalclock) { logicalclock = n.epoch; recvset.clear(); if(totalOrderPredicate(n.leader, n.zxid,getInitId(), getInitLastLoggedZxid())) updateProposal(n.leader, n.zxid); else updateProposal(getInitId(),getInitLastLoggedZxid()); sendNotifications();

其中的totalOrderPredicate函式就是根據傳送過來的封包中的leader id,資料id來與本機儲存的相應資料進行判斷的函式(首先看資料id,資料id大者勝出;其次再判斷leader id,leader id大者勝出),返回true則呼叫updateProposal函式更新資料。 b) 傳送過來資料的邏輯時鐘小於本機的邏輯時鐘 說明對方在一個相對較早的選舉程序中,這裡只需要將本機的資料廣播出去 c) 兩邊的邏輯時鐘相同,此時也只是呼叫totalOrderPredicate函式判斷是否需要更新本機的資料,將最新的選舉結果廣播出去

B、如果所接收伺服器不在選舉狀態,也就是在FOLLOWING或者LEADING狀態 a) 如果邏輯時鐘相同,將該資料儲存到recvset,如果所接收伺服器宣稱自己是leader,那麼將判斷是不是有半數以上的伺服器選舉它,如果是則設定選舉狀態退出選舉過程 如果邏輯時鐘不相同,那麼說明在另一個選舉過程中已經有了選舉結果,於是將該選舉結果加入到outofelection集合中,再根 據outofelection來判斷是否可以結束選舉,如果可以也是儲存邏輯時鐘,設定選舉狀態,退出選舉過程

以一個簡單的例子來說明整個選舉的過程. 假設有五臺伺服器組成的zookeeper叢集,它們的id從1-5,同時它們都是最新啟動的,也就是沒有歷史資料,在存放資料量這一點上,都是一樣的.假設這些伺服器依序啟動,來看看會發生什麼

  1. 伺服器1啟動,此時只有它一臺伺服器啟動了,它發出去的報沒有任何響應,所以它的選舉狀態一直是LOOKING狀態
  2. 伺服器2啟動,它與最開始啟動的伺服器1進行通訊,互相交換自己的選舉結果,由於兩者都沒有歷史資料,所以id值較大的伺服器2勝出,但是由於沒有達到超 過半數以上的伺服器都同意選舉它(這個例子中的半數以上是3),所以伺服器1,2還是繼續保持LOOKING狀態.
  3. 伺服器3啟動,根據前面的理論分析,伺服器3成為伺服器1,2,3中的老大,而與上面不同的是,此時有三臺伺服器選舉了它,所以它成為了這次選舉的leader.
  4. 伺服器4啟動,根據前面的分析,理論上伺服器4應該是伺服器1,2,3,4中最大的,但是由於前面已經有半數以上的伺服器選舉了伺服器3,所以它只能接收當小弟的命了.
  5. 伺服器5啟動,同4一樣,當小弟