日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

zookeeper的ZAB协议学习

發布時間:2024/2/28 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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的角色

  • LOOKING:進入leader選舉狀態
  • FOLLOWING:leader選舉結束,進入follower狀態
  • LEADING:leader選舉結束,進入leader狀態
  • OBSERVING:處于觀察者狀態
    寫的話寫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的通信方式,更加有助于理解。

  • 在zookeeper中,服務器中的連接是兩兩互聯,構成網狀狀態,server與server之間直接使用的socket的長連接(俗稱BIO),每兩個server之間只會建立一個連接,sid大的去主動連接sid小的。
  • 消息的發送不像http模式下一個請求過來之后要返回一個相應那樣一一對應。因為tcp是全雙工的,流式的,所以這里請求和相應是獨立的,也就是可能連續發了5條消息,后面又收到其中3個消息的答復,這種哪條消息是請求,哪條消息是答復,需要通過消息的類型進行識別,消息之間的配對(a是b的答復)也需要通過消息號等匹配起來。
  • 同樣的,zookeeper是使用tcp長連接來保證接收方接收到的消息的順序是和發送方發送消息的順序是一致的,這個也是實現數據全局有序的重要保證
  • 6. zab的幾個過程

    1. leader選舉過程 zabState為ELECTION

    下面的代碼部分都在FastLeaderElection,方法lookForLeader()作為入口

  • 選舉開始之后,每個server都會初始化內存中的狀態部分中的proposedLeader(使用當前節點的sid),proposedZxid(使用當前節點的zxid),proposedEpoch(使用當前節點的epoch),state(為LOOKING),zabState(為ELECTION) 作為當前節點的本地選票信息,然后廣播出去一個投票信息,廣播的信息的格式就是上面的選舉過程中發送的信息,leader字段使用的是proposedLeader,zxid是proposedZxid,peerEpoch 是proposedEpoch (注意著三個信息因為最開始初始化的時候是本機的信息,所以這里廣播出去的也是本機的信息,但是隨著選舉過程的推進,后面可能就不是本機的信息了,但是后面介紹的其他幾個字段都還是本機的信息) electionEpoch是當前機器的logicalclock,state是當前節點的state,configData是當前節點的config,等不再贅述,可以直接參考上面的表格。
  • 然后如果當前是looking狀態的話就會等待和收取廣播消息(可能是自己發出去的,也可能是別人發出去的)假設收取的廣播消息為n,
  • 如果是自己的,就直接記錄到recvset當中,key是當前的sid,

  • 如果是別人的,而且n.state是LOOKING,先比較選舉邏輯時鐘electionEpoch

  • 如果廣播消息中的邏輯時鐘和當前節點的logicalclock一樣大小,則比較n的選票信息和當前節點的本地選票信息比較(主要就是當前節點的proposedLeader,proposedZxid,proposedEpoch),比較的規則參看下面的選票信息比較規則
  • 如果選票n勝出,則修改本地的選票信息,主要就是當前節點的proposedLeader,proposedZxid,proposedEpoch
  • 如果n沒有本地選票信息新,則不作更新
  • 將選票n放入recvset當中,key為n.sid,對應消息的發送方sid
  • 廣播本地選票信息(可能會重復發送,不影響)
  • 如果本地邏輯時鐘落后,則直接把本地之前收到的選票全部作廢(清空recvset),重置當前節點的logicalclock為消息中的electionEpoch,然后進行選票信息比對,注意這里收到的選票信息不是和本地選票信息比較(因為proposedLeader,proposedZxid,proposedEpoch在對應的之前的electionEpoch中可能已經被改變過了),而是和本機的sid,zxid,epoch信息比較
  • 如果選票n勝出,則修改本地的選票信息為n中對響應的信息,主要就是當前節點的proposedLeader,proposedZxid,proposedEpoch
  • 如果n沒有本地選票信息新,則更新本地選票信息為本機信息(和選舉剛開始時候的初始化信息是一致的)
  • 將選票n放入recvset當中,key為n.sid,對應消息的發送方sid
  • 廣播本地選票信息(可能會重復發送,不影響)
  • 如果n中的邏輯時鐘electionEpoch小于當前節點的logicalclock,則忽略這個選票信息
  • 查看recvset中的信息是否對本地選票中的proposedLeader達成了多數一致
  • 如果未達成則進入大步驟中的2,繼續處理收到的廣播消息
  • 如果達成多數贊成proposedLeader,則會繼續輪詢接收消息的隊列看看有沒有優先級更高的選票(這里的優先級更高的就是使用下面的選票信息比較規則最后勝出)
  • 如果在處理隊列中的消息發現有優先級更高的消息,則會把這個消息再放回到接收消息的隊列中,跳到大步驟中的2,繼續處理收到的廣播消息,選出更加合適的leader
  • 如果接收消息的隊列已經空了,且沒有優先級更高的選票,則會等待200ms
  • 有優先級更高的消息的話,同3.2.1,跳到下一輪大循環
  • 有消息,優先級不高,同3.2.2,繼續消費隊列
  • 還是沒有消息,可以認為這次選舉結束了,將當前server的state設置為leading,following,observing三個狀態中的一個,同時清空接收消息的隊列
  • 如果是別人的,而且n.state是OBSERVING,接著進入步驟2,不以OBSERVING的消息為準,因為他沒有選舉權限

  • 如果是別人的,而且n.state是FOLLOWING,LEADING,這個時候

  • 判斷n和本地是否處于用一個選舉周期中n.electionEpoch == logicalclock.get(),如果在一個選舉周期中則放入recvset,并查看recvset中的信息是否對n選票中的n.leader達成了多數一致,如果達成了并且leader的選票也認為自己是leader,那么就結束選舉,這種情況應該是本次選舉基本可以認為結束了,但是當前節點收到的消息比較滯后的情況,這個時候可能會受到leader和其他follower的信息,這個時候還是按照原來的規則計算即可
  • 如果上一步沒有計算出來leader放入另一個計票容器outofelection當中,這一步沒有要求選舉周期是一致的,然后看看outofelection中是否對n.leader達到了多數一致性,如果達成了也會結束選舉,這個應該應用的場景就是類似某個follower和leader失去聯系了,然后發起選舉,結果收到了其他人都告訴他leader存在的消息(這個時候消息的選舉周期和當前節點肯定不一致),然后,當前節點就會接受當前leader存在的事實,防止頻繁進行選舉過程。
  • 選票信息比較規則

    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

  • 如果follower的zxid比leader的小則leader會發送后面的數據給對應的follower(也是使用兩階段提交的方式),在發送完后會發送一個Leader.NEWLEADER數據包,follower在同步完成后發送響應的ack消息
  • 如果follower的數據zxid比leader的大,則對應的數據都會被刪除,完成后也要發送ack消息
    在leader收到過半的follower的ack消息之后就認為數據同步完成了,后面就可以進入原子播報階段了
  • 4. 原子播報階段,這個時候使用的是二階段提交模式

    zabState為 BROADCAST

  • 對于每一個事務請求過來的時候,都要由leader進行FIFO處理,假如是follower或者observer接收到了這個請求,那么會把這個請求轉發給leader
  • leader接收到事務請求后,會先生成一個zxid(epoch+有序遞增的事務id),然后將該事務存儲到本地日志,
  • 接著將這個事務廣播所有的follower進行事務日志存儲,
  • follower 在接收到leader事務請求時,要么選擇執行該事務,要么選擇拋棄leader,重新發起leader選舉(但是會變成無效,直到他重新認領原來的leader,又會通過同步的方式進行數據同步)
  • 等待過半的follower都回應ack表示可以存儲之后,再發送一個commit信息給所有的follower進行提交(更新lastcommited并應用log到狀態機當中),然后本地也進行一個提交(更新lastcommited并應用log到狀態機當中),之后返回給客戶端成功。
  • ??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的選票比較規則以及多數投票一致性上可以得出,

  • 肯定不會丟commit的數據,應為這個log必然已經存在了多個節點上
  • 同時,對于只存在之前leader上的數據,如果leader掛了那么他的只有自己有的數據也會被忽略掉,即使他后來又回來,但是這個時候epoch偏低,所以不會接受他的日志。
  • 但是對于那種有部分機器接受到了廣播請求,只是存了log,這個時候leader掛了,該數據還沒有走到commit階段,這個時候如果新的leader恰好有這條數據,那么從zookeeper來看他是會保留這一條數據,在raft協議中,這樣的數據也是會被保留,但是raft要求client重試請求的時候也要攜帶請求編號,來確認這個請求是否已經做了,而不會重復做兩次(比如是創建節點),zookeeper這塊是如何做的呢,解析zookeeper的log中發現石油session和cxid,zxid的,這樣的話也就滿足了冪等,可以通過這個來判斷這個操作是否已經提交了。
  • 事務日志

    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协议学习的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。