ZooKeeper和ZAB協議


前言

ZooKeeper是一個提供高可用,一致性,高性能的保證讀寫順序的存儲系統。ZAB協議為ZooKeeper專門設計的一種支持數據一致性的原子廣播協議。

演示環境

$ uname -a
Darwin 18.6.0 Darwin Kernel Version 18.6.0: Thu Apr 25 23:16:27 PDT 2019; root:xnu-4903.261.4~2/RELEASE_X86_64 x86_64

安裝

brew cask install java
brew install zookeeper

配置

這里演示的是在同一台機器部署3個ZooKeeper進程的偽集群。

$ cat /usr/local/etc/zookeeper/zoo1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/var/run/zookeeper/data1
clientPort=2181
server.1=localhost:2888:3888
server.2=localhost:4888:5888
server.3=localhost:6888:7888
$ echo "1" >
/usr/local/var/run/zookeeper/data1/myid
  • tickTime ZooKeeper中使用的基本時間單元,以毫秒為單位,默認是2000。它用來調節心跳和超時。
  • initLimit 默認值是10,即tickTime屬性值的10倍。表示允許follower連接並同步到leader的最大時間。如果ZooKeeper管理的數據量很大的話可以增加這個值。
  • syncLimit 默認值是5,即tickTime屬性值的5倍。表示lead和follower進行心跳檢測的最大延遲時間。如果在設置的時間內follower無法於leader進行通信,那么follower將會被丟棄。
  • dataDir ZooKeeper用來存儲內存數據庫快照的目錄,並且除非指定其他目錄,否則數據庫更新的事務日志也會存儲在該目錄。可以配置dataLogDir指定ZooKeeper事務日志的存儲目錄。
  • clientPort 服務器監聽客戶端連接的端口,默認是2181。
  • server.id = host:port1:port2 集群模式中用於感知其他機器,每一行代表一個ZooKeeper實例配置。id被成為Server ID用來表示實例在集群中的序號。同時需要將每個實例的ID寫入dataDir的myid文件中。port1用於數據同步。port2用於選舉。

集群中第2個實例的配置為:

$ cat /usr/local/etc/zookeeper/zoo2.cfg 
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/var/run/zookeeper/data2
clientPort=2182
server.1=localhost:2888:3888
server.2=localhost:4888:5888
server.3=localhost:6888:7888
$ cat /usr/local/var/run/zookeeper/data2/myid 
2

集群中第3個示例的配置為:

$ cat /usr/local/etc/zookeeper/zoo3.cfg 
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/var/run/zookeeper/data3
clientPort=2183
server.1=localhost:2888:3888
server.2=localhost:4888:5888
server.3=localhost:6888:7888
$ cat /usr/local/var/run/zookeeper/data3/myid 
3

啟動集群:

$ zkServer start /usr/local/etc/zookeeper/zoo1.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo1.cfg
Starting zookeeper ... STARTED
$ zkServer start /usr/local/etc/zookeeper/zoo2.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2.cfg
Starting zookeeper ... STARTED
$ zkServer start /usr/local/etc/zookeeper/zoo3.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo3.cfg
Starting zookeeper ... STARTED
$ zkServer status /usr/local/etc/zookeeper/zoo1.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo1.cfg
Mode: follower
$ zkServer status /usr/local/etc/zookeeper/zoo2.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2.cfg
Mode: leader
$ zkServer status /usr/local/etc/zookeeper/zoo3.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo3.cfg
Mode: follower

從上面的狀態檢查可以看出,第二實例是leader,其他兩個實例是follower。

操作

下面我將演示在集群中讀寫節點。

$ zkCli -server localhost:2182
Connecting to localhost:2182
Welcome to ZooKeeper!
JLine support is enabled

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2182(CONNECTED) 2] create /test "i am test"
Created /test
[zk: localhost:2182(CONNECTED) 3] get /test
i am test
cZxid = 0x200000002
ctime = Tue Jul 02 16:35:15 CST 2019
mZxid = 0x200000002
mtime = Tue Jul 02 16:35:15 CST 2019
pZxid = 0x200000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 0

在實例2中創建/test節點,接下來在實例3中也能讀取到該節點,說明ZooKeeper集群數據的一致性。

ZooKeeper語義保證

ZooKeeper簡單高效,同時提供以下語義保證,從而使我們可以利用這些特性提供復雜的服務:

  • 順序性:客戶端發起的更新請求會按發送順序被應用到ZooKeeper上。
  • 原子性:更新操作要么成功要么失敗,不會出現中間狀態。
  • 可靠性:一個更新操作一旦被接受即不會意外丟失,除非被其他更新操作覆蓋。
  • 最終一致性:寫操作最終(而非立即)會對客戶端可見。

ZooKeeper Watch 機制

所有對ZooKeeper的讀操作都可以附帶一個Watch。一旦相應的數據有變化,該Watch即被觸發。

Watch有以下特點:

  • 主動推送:Watch被觸發時,由ZooKeeper服務器主動將更新推送給客戶端,而不需要客戶端輪詢。
  • 一次性:數據變化時Watch只會被觸發一次。如果客戶端想得到后續更新的通知,必須要在Watch被觸發后重新再注冊一個Watch。
  • 順序性:如果多個更新觸發了多個Watch,那Watch被觸發的順序與更新的順序一致。
  • 可見性:如果一個客戶端在讀請求中附帶Watch,Watch被觸發的同時再次讀取數據,客戶端在得到Watch消息之前肯定不可能看到更新后的數據。換句話說,更新通知先於更新結果。

ZAB協議

為了保證寫操作的一致性與可用性,ZooKeeper在paxos的基礎上設計了一種名為原子廣播(ZAB)的支持崩潰恢復的一致性協議。基於該協議,ZooKeeper實現了一種主從模式的系統架構來保持集群中各個副本之間的數據一致性。

根據ZAB協議,所有的寫操作都必須通過leader來完成,leader寫入本地日志后再復制到所有的follower節點。如果客戶端對follower/observer發起寫請求,follower/observer會將請求轉發到leader,然后由leader處理完成后再將結果轉發回follower/observer發送給客戶端。

ZAB協議分為廣播模式崩潰恢復模式

leader處理寫請求(廣播模式)的步驟為:

1.leader為事務請求生成唯一的事務ID(ZXID),ZAB協議會將每個事務Proposal按照ZXID的先后順序來進行排序和處理。

2.將Proposal發送給follower並等待follower回復ACK。

3.leader收到超過半數的ACK(leader默認對自己有一個ACK)后向所有的follower/observer發送commit,同時leader自身也會完成commit。

4.將處理結果返回給客戶端。

上述過程成為ZooKeeper的兩階段提交。

崩潰恢復:

當leader實例宕機崩潰,或者因為網絡原因導致其與過半的follower都失去聯系,那么就會進入崩潰恢復階段。

leader宕機或者與過半的follower失聯都會導致leader重新選舉(選舉算法文章后面會介紹)。選舉結束后會緊着進入數據崩潰恢復,以保證數據一致性,也就是數據同步。需要保證已經commit的事務被所有服務器都提交,同時需要丟棄那些只在leader服務器提交的事務。所以選出來的新leader要擁有集群中最高編號的ZXID。在新的leader選舉出來后就會進行數據同步工作,leader會將那些沒有被follower同步的事務以Proposal消息的形式發送給follower,並在每個Proposal消息后面緊跟着發送一個commit消息,表示該事務已經被提交。然后follower服務器會將同步過來的事務Proposal都成功應用到本地數據庫。

ZXID是個64位的無符號整形,高32位是epoch,代表leader選擇周期,低32位是累加計數,每一輪選舉后該計數會清零。leader服務器沒產生一個事務,ZXID的低32位就會加一,沒完成一次leader選舉,就會將ZXID的高32位加一。這樣做是為了保證新leader生成的ZXID肯定是大於舊leader之前產生的ZXID。

領導選舉算法

服務器狀態:

  • LOOKING 不確定Leader狀態。該狀態下的服務器認為當前集群中沒有Leader,會發起Leader選舉。
  • FOLLOWING 跟隨者狀態。表明當前服務器角色是Follower,並且它知道Leader是誰。
  • LEADING 領導者狀態。表明當前服務器狀態是leader,它會維護與Follower間的心跳。
  • OBSERVING 觀察者狀態。表明當前服務器角色是Observer,與Follower不同是不參與選舉,也不參與集群寫操作的投票。

選票數據結構:

每個服務器在進行領導者選舉是,會發送如下關鍵信息:

  • logicClock 每個服務器都會維護一個自增的整數,名為logicClock,它表示這是該服務器第幾輪發起的投票。
  • state 表示該服務器當前的狀態。
  • self_id 當前服務器的myid
  • self_zxid 當前服務器上所保存的數據的最大zxid
  • vote_id 被推舉的服務器的myid
  • vote_zxid 被推舉的服務器上所保存的數據的最大zxid 

(快速領導者選舉算法)選票PK:

選票PK是基於(logicClock,self_id, self_zxid) 與 (vote_logicClock, vote_self_id, vote_self_zxid)對比:
先比較logicClock,如果相等再比較zxid,如果zxid相等,再比較myid。最后如果vote大於自身,則改票,也投vote。

總結

文章一開始演示ZooKeeper的部署和操作給讀者一個直觀感受,然后介紹了ZooKeeper的ZAB協議和領導者選舉原理。

參考

https://cwiki.apache.org/confluence/display/ZOOKEEPER/ProjectDescription

https://dbaplus.cn/news-141-1875-1.html

 


免責聲明!

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



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