zookeeper的ZAB协议学习
文章目錄
- 1. zookeeper的復制狀態機
- 2. zookeeper的角色
- 3. zab協議的階段
- 4. zookeeper的數據模型
- 1. 在zookeeper每個服務節點的持久化數據
- 2. 內存中的狀態
- 3. 選舉過程中發送的信息
- 5.消息通信機制
- 6. zab的幾個過程
- 1. leader選舉過程 zabState為ELECTION
- 2. leader確認階段
- 3. leader的數據同步階段
- 4. 原子播報階段,這個時候使用的是二階段提交模式
- 7. 小結
- 1. 線性一致性讀
- 2. 與client的線性一致性保持
- 部分源碼,附
- 腦圖,幫助理解
- 部分參考鏈接
??看完raft的論文,再來看zookeeper關于ZAB協議的介紹,感覺raft寫的真是好,zookeeper我能夠搜索到的論文實際上都只是講了一個大概,感覺 這一篇還算是稍微講了一些東西,但是還是很不全面。迫不得已看了一下源碼,只看了leader選舉部分的實現 FastLeaderElection,以及leader選舉相關的工作,其實zookeeper的項目對于java開發人員還是很友好的,直接 git clone下來,在本地看的很爽。
下面嘗試按照raft論文的方式來對zab協議進行闡述
1. zookeeper的復制狀態機
zk的數據存在內存當中(高性能),但是同時記錄操作日志+內存快照(二進制),持久化(類似于Redis)。
狀態機+命令日志:內存中保存數據的最終狀態,命令日志中保存所有的操作過程,內存快照中保存某一時間節點的狀態機中的數據。
所以zk和raft基本一致,也是復制狀態機的工作模式,由日志復制的線性化來保證系統的線性化。
2. zookeeper的角色
寫的話寫leader,讀取的話可以通過FOLLOWING,OBSERVING,而且拓展OBSERVING的話可以提供更多讀的能力,但是不會降低寫入的速度。
3. zab協議的階段
??這個階段的劃分不同的論文好像有不同的說法,這里先以zookeeper的代碼中的為準介紹一下,再引述一些其他的方式,在zookeeper的源碼中對zabState是這樣定義的,有4種狀態
ELECTION : leader選舉階段
DISCOVERY: leader確認階段
SYNCHRONIZATION: 數據同步階段
BROADCAST: 原子播報階段
也有一些文章前面三個階段合起來稱為崩潰恢復階段,這種也是可以的,這種情況下zab協議就被描述為奔潰恢復和原子播報兩個階段。
4. zookeeper的數據模型
1. 在zookeeper每個服務節點的持久化數據
這一塊兒介紹的可能不是很全,主要關注了和選舉相關的一些數據
| logs[]: | 日志 |
| zxid : | 最后的log的zxid,這個zxid是一個64位的數字,高32位被稱為epoch,類似raft日志中的term, 低32位是遞增的counter類似于raft中的index,但是這里的counter不是全局遞增的,每次leader選舉出來之后,counter會被初始化為0,但是zxid還是全局遞增的。所以日志是全局有效的。 |
| epoch: | zxid的高32位,會單獨持久化 |
| lastCommited | 最新的commited的zxid |
2. 內存中的狀態
| logicalclock : | 這個是選舉專用的邏輯時鐘,在服務啟動后第一次選舉開始的時候會初始化一個FastLeaderElection實例,logicalclock是他的一個屬性,會被初始化為0;后期有可能因為一些異常原因重建這個實例,默認情況下服務不重啟,這個logicalclock會是遞增的狀態,而且在zookeeper的代碼中,有些地方把這個也叫epoch或electEpoch,頗具迷惑性 |
| proposedLeader: | 當前節點認為的應該做leader的server id,根據當前節點收到的廣播消息會動態變化,選舉剛開始的時候初始化為當前節點的sid |
| proposedZxid: | 對應的應該做leader的server的zxid,根據當前節點收到的廣播消息會動態變化,選舉剛開始的時候初始化為當前節點的zxid |
| proposedEpoch: | 對應的應該做leader的server的epoch,這個epoch是zxid中的epoch,但是不一定相等,因為新的epoch生成了,但是包含這個epoch的zxid可能還沒有生成,,根據當前節點收到的廣播消息會動態變化,選舉剛開始的時候初始化為當前節點的epoch |
| state | 每個節點處于的角色狀態,可能是LOOKING,FOLLOWING,LEADING,OBSERVING,會隨著選舉過程逐漸變化,在節點啟動或者當前節點要發起leader選舉的時候是LOOKING,leader選出來后是后面三種的一種 |
| zabState | 每個節點處于的zab協議的階段,可能是ELECTION,DISCOVERY,SYNCHRONIZATION,BROADCAST,在節點啟動或者leader選舉開始的時候初始化為ELECTION,選舉完成后epoch的確認階段為DISCOVERY,數據同步階段為SYNCHRONIZATION,數據同步完成之后是原子播報階段,對應的則是BROADCAST |
| Map<Long, Vote> recvset: | 用來收集looking狀態下的大家的選票信息,key是投票者的server id, Vote是對應的server投出的票,這個map數據結構是當前server用來記錄同樣處于LOOKING狀態的server發出來的投票信息,如果這個達到了多數一致,那么久認為leader選出來了。 |
| Map<Long, Vote> outofelection : | 這個對應收集的是leader或者是follower或者leader發出來的信息,這個也是按照多數生效(也就是超過半數的leader+follower信息發過來才認為真正找到了leader,感覺這個還是比較嚴格的),同時還會要求必須有leader廣播的信息認為自己是leader. |
上面字段中的proposedLeader,proposedZxid,proposedEpoch,logicalclock是創建本地廣播的選票信息的主要來源(new Vote對象的時候使用到這些變量),所以我們為了下面描述起來更加方便,將這些變量稱為本地選票信息。
vote的信息
| leader | 投票認為的leader的server id |
| zxid | 認為的leader的zxid |
| electionEpoch | 選舉的邏輯時鐘logicalclock |
| state | 投票者的server state ,一般是LOOKING |
| configData | 集群的服務器配置,用來驗證quorum,這個字段應該是包含了當前集群有哪些節點 |
| peerEpoch | 被認為是leader的節點的epoch |
vote的信息是一個選票的信息,就是下面廣播的投票信息是一致的
3. 選舉過程中發送的信息
| leader | 投票認為的leader的server id |
| zxid | 認為的leader的zxid |
| electionEpoch | 選舉的邏輯時鐘logicalclock |
| state | 投票者的server state ,一般是LOOKING |
| configData | 集群的服務器配置,用來驗證quorum,這個字段應該是包含了當前集群有哪些節點 |
| peerEpoch | 被認為是leader的節點的epoch |
5.消息通信機制
在正式了解zookeeper的zab工作模式以前有必要先簡單介紹一下zookeeper的通信方式,更加有助于理解。
6. zab的幾個過程
1. leader選舉過程 zabState為ELECTION
下面的代碼部分都在FastLeaderElection,方法lookForLeader()作為入口
如果是自己的,就直接記錄到recvset當中,key是當前的sid,
如果是別人的,而且n.state是LOOKING,先比較選舉邏輯時鐘electionEpoch
如果是別人的,而且n.state是OBSERVING,接著進入步驟2,不以OBSERVING的消息為準,因為他沒有選舉權限
如果是別人的,而且n.state是FOLLOWING,LEADING,這個時候
選票信息比較規則
1.誰的peerEpoch高誰誰勝利
2.如果peerEpoch相等,則誰的zxid更大誰勝出
3.如果peerEpoch,zxid都相等,那么誰的sid大誰勝出
4.這里補充一個疑惑點,為什么還要先比較peerEpoch,直接比較zxid不就行了么,因為zxid不是包含了epoch的信息么,肯能是因為某個節點當選了master然后很快超時了重新選舉了?有待后續探索
2. leader確認階段
??zabState為DISCOVERY,對應代碼在QuorumPeer的run()方法之中,這里針對不用角色的節點leading,following,observing,都會有DISCOVERY階段。
??這個階段就是leader會生成新的epoch(從各個follower收集到的最大的epoch+1),并使用(epoch,0)組合生成zxid,把自己的zxid封裝成Leader.LEADERINFO包發送給發送給follower,然后follower確認這個epoch是大于等于自己當前看到的epoch的。如果不是就會拋異常,不承認當前leader(理論上不應該發生),如果接受了就會更新當前服務器的epoch,封裝成Leader.LEADERINFO包發送給leader,在leader收到過半的follower的ack消息之后就說明大家都承認他是leader了,后面就可以開始數據同步工作了。
這里的epoch一般是和zxid中的一致的,因為follower的都是從master的消息當中得到的。
3. leader的數據同步階段
??在過半follower回復了ack消息之后,leader就可以開始數據同步工作了,數據同步的時候是采用強leader的方式,也就是大家的數據都要和leader的對齊,這時zabState為SYNCHRONIZATION
在leader收到過半的follower的ack消息之后就認為數據同步完成了,后面就可以進入原子播報階段了
4. 原子播報階段,這個時候使用的是二階段提交模式
zabState為 BROADCAST
??zk的leader處理事務時FIFO機制保持了數據的一致性,這個可以保證leader上的順序性,同時,leader在給follower的信息傳遞中也通過tcp的有序機制保證了follower每臺節點上的日志的順序一致性。所以日志可以保持全局有序性,這個和raft是一致的。
7. 小結
1. 線性一致性讀
zk的寫是具備線性一致性的,但是讀的話要分兩種情況,如果是普通的read,則不滿足線性一致性,因為讀取沒有走zab的流程,這個時候這個請求可能到了某個follower,而該follower還沒有同步到這個數據的話,可能讀不到最新的數據,但是這只是zookeeper對讀的一種優化,可以更快響應,如果對數據的及時性有非常高的要求的話,那么園長(zookeeper也被稱為動物園園長)也提供了線性一致性的讀方式,就是在真正讀取之前調用一下zk.sync()方法,這個方法會獲取當前最大的zxid,知道本機提交這個zxid才會返回,也就保證了在這之前執行的操作在本機都是可見的了。
2. 與client的線性一致性保持
從leader的選票比較規則以及多數投票一致性上可以得出,
事務日志
ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 1/20/20 4:29:59 AM UTC session 0x30014da58050000 cxid 0x0 zxid 0x10000000c createSession 300001/20/20 4:30:00 AM UTC session 0x30014da58050000 cxid 0x4 zxid 0x10000000d create '/test_zookeeper/test/item,,v{s{31,s{'djdigest,'CgcA1GMivoBYyZZWuQDgeLuz5L45jmuVDyLKi2J0swQ=:MEonRpvlUHyT9yHsCnPddPJ0QVCMGVM5ylIV0Zv/VaY=}}},F,21/20/20 4:30:00 AM UTC session 0x30014da58050000 cxid 0x5 zxid 0x10000000e delete '/test_zookeeper/test/item1/20/20 4:30:00 AM UTC session 0x30014da58050000 cxid 0x6 zxid 0x10000000f setACL '/,v{s{31,s{'djdigest,'T9ihPbFmp0odTgrtigbbYJgBkC5Pe6XkWO543Hl1+jc=:dk8WmGqk2QsbWdyxv98BRJWaiW3xEGxpvbzVVx8z8ig=}}},21/20/20 4:30:00 AM UTC session 0x30014da58050000 cxid 0x8 zxid 0x100000010 setACL '/zookeeper,v{s{31,s{'djdigest,'V4AoltP7EqnmD6tlXT9D+yzozXnf2aN/FTmYOVekewQ=:vvEn1n8041x6LDHUgnGC/+tAAwKpFePtXZAdWP4hY3Y=}}},2部分源碼,附
/*** Messages that a peer wants to send to other peers.* These messages can be both Notifications and Acks* of reception of notification.*/public static class ToSend {/** Proposed leader in the case of notification*/ long leader;/** id contains the tag for acks, and zxid for notifications*/ long zxid;/** Epoch*/ long electionEpoch;/** Current state;*/ QuorumPeer.ServerState state;/** Address of recipient,這里是接收端的server id,實際上這個字段的信息并不會發出去*/ long sid;/** Used to send a QuorumVerifier (configuration info)*/ byte[] configData = dummyData;/** Leader epoch*/ long peerEpoch;} public class Vote {private final int version;private final long id; //leader的idprivate final long zxid; // leader的zxidprivate final long electionEpoch; // leader的邏輯時鐘logicalclockprivate final long peerEpoch; //leader的epoch}zxid的初始化
這個方法會找到當前收到的最大epoch然后執行+1操作得到當前的epoch long epoch = getEpochToPropose(self.getId(), self.getAcceptedEpoch()); zk.setZxid(ZxidUtils.makeZxid(epoch, 0));epoch和clock是不是一個,東西,每個選票的信息有
每一個投票者會維護一個
Map<Long, Vote> recvset = new HashMap<Long, Vote>();用來記錄自己收到的投票信息
map的key是server id,也就可以收集當前選舉輪次中每個server的投票信息
vote的數據結構是期望leader的id,期望leader的zxid,期望leader的投票周期(邏輯時鐘),期望leader的zxid中的epoch部分
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));找到和當前認為的leader是一致的vote
voteSet = getVoteTracker(recvset, new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch));腦圖,幫助理解
部分參考鏈接
https://www.jianshu.com/p/90e00da6d780
https://zhouj000.github.io/2019/02/11/zookeeper-03/
https://www.cnblogs.com/leesf456/p/6140503.html
總結
以上是生活随笔為你收集整理的zookeeper的ZAB协议学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法训练营05-二叉树
- 下一篇: 算法训练营07-递归使用练习