一、什麼是zab協議?
ZAB 協議是為分散式協調服務 ZooKeeper 專門設計的一種支援崩潰恢復的原子廣播協議。在 ZooKeeper 中,主要依賴 ZAB 協議來實現分散式資料一致性,基於該協議,ZooKeeper 實現了一種主備模式的系統架構來保持叢集中各個副本之間的資料一致性。
Zab 協議分為兩大塊:
- 廣播(boardcast):Zab 協議中,所有的寫請求都由 leader 來處理。正常工作狀態下,leader 接收請求並通過廣播協議來處理。
- 恢復(recovery):當服務初次啟動,或者 leader 節點掛了,系統就會進入恢復模式,直到選出了有合法數量 follower 的新 leader,然後新 leader 負責將整個系統同步到最新狀態。
1.1廣播(boardcast)
廣播的過程實際上是一個簡化的二階段提交過程:
- Leader 接收到訊息請求後,將訊息賦予一個全域性唯一的 64 位自增 id,叫做:zxid,通過 zxid 的大小比較即可實現因果有序這一特性。
- Leader 通過先進先出佇列(通過 TCP 協議來實現,以此實現了全域性有序這一特性)將帶有 zxid 的訊息作為一個提案(proposal)分發給所有 follower。
- 當 follower 接收到 proposal,先將 proposal 寫到硬碟,寫硬碟成功後再向 leader 回一個 ACK。
- 當 leader 接收到合法數量的 ACKs 後,leader 就向所有 follower 傳送 COMMIT 命令,同事會在本地執行該訊息。
- 當 follower 收到訊息的 COMMIT 命令時,就會執行該訊息廣播過程
相比於完整的二階段提交,Zab 協議最大的區別就是不能終止事務,follower 要麼回 ACK 給 leader,要麼拋棄 leader,在某一時刻,leader 的狀態與 follower 的狀態很可能不一致,因此它不能處理 leader 掛掉的情況,所以 Zab 協議引入了恢復模式來處理這一問題。從另一角度看,正因為 Zab 的廣播過程不需要終止事務,也就是說不需要所有 follower 都返回 ACK 才能進行 COMMIT,而是隻需要合法數量(2f+1 臺伺服器中的 f+1 臺) 的follower,也提升了整體的效能。
1.2恢復(recovery)
由於之前講的 Zab 協議的廣播部分不能處理 leader 掛掉的情況,Zab 協議引入了恢復模式來處理這一問題。為了使 leader 掛了後系統能正常工作,需要解決以下兩個問題:
- 已經被處理的訊息不能丟
- 被丟棄的訊息不能再次出現
已經被處理的訊息不能丟
這一情況會出現在以下場景:當 leader 收到合法數量 follower 的 ACKs 後,就向各個 follower 廣播 COMMIT 命令,同時也會在本地執行 COMMIT 並向連線的客戶端返回「成功」。但是如果在各個 follower 在收到 COMMIT 命令前 leader 就掛了,導致剩下的伺服器並沒有執行都這條訊息。
如圖 1-1,訊息 1 的 COMMIT 命令 Server1(leader)和 Server2(follower) 上執行了,但是 Server3 還沒有收到訊息 1 的 COMMIT 命令,此時 leader Server1 已經掛了,客戶端很可能已經收到訊息 1 已經成功執行的回覆,經過恢復模式後需要保證所有機器都執行了訊息 1。
為了實現已經被處理的訊息不能丟這個目的,Zab 的恢復模式使用了以下的策略:
- 選舉擁有 proposal 最大值(即 zxid 最大) 的節點作為新的 leader:由於所有提案被 COMMIT 之前必須有合法數量的 follower ACK,即必須有合法數量的伺服器的事務日誌上有該提案的 proposal,因此,只要有合法數量的節點正常工作,就必然有一個節點儲存了所有被 COMMIT 訊息的 proposal 狀態。
- 新的 leader 將自己事務日誌中 proposal 但未 COMMIT 的訊息處理。
- 新的 leader 與 follower 建立先進先出的佇列, 先將自身有而 follower 沒有的 proposal 傳送給 follower,再將這些 proposal 的 COMMIT 命令傳送給 follower,以保證所有的 follower 都儲存了所有的 proposal、所有的 follower 都處理了所有的訊息。
通過以上策略,能保證已經被處理的訊息不會丟
被丟棄的訊息不能再次出現
這一情況會出現在以下場景:當 leader 接收到訊息請求生成 proposal 後就掛了,其他 follower 並沒有收到此 proposal,因此經過恢復模式重新選了 leader 後,這條訊息是被跳過的。 此時,之前掛了的 leader 重新啟動並註冊成了 follower,他保留了被跳過訊息的 proposal 狀態,與整個系統的狀態是不一致的,需要將其刪除。
如圖 1-2 ,在 Server1 掛了後系統進入新的正常工作狀態後,訊息 3被跳過,此時 Server1 中的 P3 需要被清除。
Zab 通過巧妙的設計 zxid 來實現這一目的。一個 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 崩潰,資料一致性怎麼保證?leader 崩潰之後,叢集會選出新的 leader,然後就會進入恢復階段,新的 leader 具有所有已經提交的提議,因此它會保證讓 followers 同步已提交的提議,丟棄未提交的提議(以 leader 的記錄為準),這就保證了整個叢集的資料一致性。
- 選舉 leader 的時候,整個叢集無法處理寫請求的,如何快速進行 leader 選舉?這是通過 Fast Leader Election 實現的,leader 的選舉只需要超過半數的節點投票即可,這樣不需要等待所有節點的選票,能夠儘早選出 leader。