ZAB(Zookeeper Atomic Broadcast) 協議是為分布式協調服務 ZooKeeper 專門設計的一種支持崩潰恢復的原子廣播協議。在 ZooKeeper 中,主要依賴 ZAB 協議來實現分布式數據一致性,基於該協議ZooKeeper 實現了一種主備模式的系統架構來保持集群中各個副本之間的數據一致性。
二、ZAB 協議介紹
ZAB 協議包含兩種基本模式,分別是崩潰恢復和原子廣播。需要注意的是:leader 節點可以處理事務請求和非事務請求,follower 節點只能處理非事務請求,如果 follower 節點接收到非事務請求,會把這個請求轉發給 leader 服務器。
三、消息廣播的原理(zxid):消息廣播的過程實際上是一個簡化版本的二階段提交過程。
這里需要注意的是: leader 的投票過程,不需要 Observer 的 ack,也就是 Observer 不需要參與投票過程,但是 Observer 必須要同步 Leader 的數據從而在處理請求的時候保證數據的一致性,用來實現提升讀性能。
四、崩潰恢復的實現原理
1. 已經被處理的消息不能丟:當 leader 收到合法數量 follower 的 acks 后,就向各個 follower 廣播 COMMIT 命令,同時也會在本地執行 COMMIT 並向連接的客戶端返回 acks 。但是如果在各個 follower 在收到 COMMIT 命令前 leader 就掛了,導致剩下的服務器並沒有執行都這條消息。即 leader 在發 commit 命令前掛了(此時 follower 已經將該消息存入本地,只是沒有 commit ),導致 follower 沒有收到這條已經被 leader 處理的消息。
2. 被丟棄的消息不能再次出現:當 leader 接收到消息請求生成 proposal 后就掛了,其他 follower 並沒有收到此 proposal,因此經過恢復模式重新選了 leader 后,這條消息是被跳過的。 此時之前掛了的 leader 重新啟動並注冊成了 follower,他保留了被跳過消息的proposal 狀態,與整個系統的狀態是不一致的,需要將其刪除。
2. zxid 是 64 位,高 32 位是 epoch 編號,每經過一次 Leader 選舉產生一個新的 leader,新的 leader 會將 epoch 號+1,低 32 位是消息計數器,每接收到一條消息這個值+1,新 leader 選舉后這個值重置為 0,這樣設計的好處在於老的 leader 掛了以后重啟,它不會被選舉為 leader,因此此時它的 zxid 肯定小於當前新的 leader。當老的 leader 作為 follower 接入新的 leader 后,新的 leader 會讓它將所有的擁有舊的 epoch 號的未被 COMMIT 的 proposal 清除。
選主:leader選舉是zk中最重要的技術之一,也是保證分布式數據一致性的關鍵所在。當集群中的一台服務器處於如下兩種情況之一時,就會進入leader選舉階段——服務器初始化啟動、服務器運行期間無法與leader保持連接。 選舉階段,集群間互傳的消息稱為投票,投票Vote主要包括二個維度的信息:ID、ZXID
- ID:被推舉的leader的服務器ID,集群中的每個zk節點啟動前就要配置好這個全局唯一的ID。
- ZXID:被推舉的leader的事務ID ,該值是從機器DataTree內存中取的,即事務已經在機器上被commit過了。
節點進入選舉階段后的大體執行邏輯如下:
- (1)設置狀態為LOOKING,初始化內部投票Vote (id,zxid) 數據至內存,並將其廣播到集群其它節點。節點首次投票都是選舉自己作為leader,將自身的服務ID、處理的最近一個事務請求的ZXID(ZXID是從內存數據庫里取的,即該節點最近一個完成commit的事務id)及當前狀態廣播出去。然后進入循環等待及處理其它節點的投票信息的流程中。
- (2)循環等待流程中,節點每收到一個外部的Vote信息,都需要將其與自己內存Vote數據進行PK,規則為取ZXID大的,若ZXID相等,則取ID大的那個投票。若外部投票勝選,節點需要將該選票覆蓋之前的內存Vote數據,並再次廣播出去;同時還要統計是否有過半的贊同者與新的內存投票數據一致,無則繼續循環等待新的投票,有則需要判斷leader是否在贊同者之中,在則退出循環,選舉結束后根據選舉結果及各自角色切換狀態,leader切換成LEADING、follower切換到FOLLOWING、observer切換到OBSERVING狀態。
偽代碼:
if (接收到的投票的選舉周期 > 本服務器當前的選舉周期) { // 修改本服務器的選舉周期為接收到的投票的選舉周期 // 清空本服務器的投票箱(表示選舉周期落后,重新開始投票) // 比較接收到的選票所選擇的服務器與本服務器的數據誰更新,本服務器將選票投給數據較新者 // 發送選票 } else if(接收到的投票的選舉周期 < 本服務器當前的選舉周期){ // 接收到的投票的選舉周期落后了,本服務器直接忽略此投票 } else if(選舉周期一致) { // 比較接收到的選票所選擇的服務器與本服務器當前所選擇的服務器的數據誰更新,本服務器將選票投給數據較新者 // 發送選票 }
ZooKeeper集群在進行領導者選舉的過程中不能對外提供服務