Zookeeper集群完成Leader選舉后,會進行Leader和Follower的數據同步(或叫狀態同步),完成同步是保證服務器數據一致,可以提供服務的前提,接下來記錄下Zookeeper數據同步相關的內容,主要參考文末書籍和博文。
數據同步分類
Zookeeper中數據同步一共有四類,如下。
- DIFF:直接差異化同步
- TRUNC+DIFF:先回滾再差異化同步
- TRUNC:僅回滾同步
- SNAP:全量同步
不同的場景,會有不同的數據同步方式,具體選擇哪種方式,還需要參考以下三個參數,根據這三個參數的大小對比結果,選擇對應的數據同步方式。
- peerLastZxid:Learner服務器(Follower或observer)最后處理的zxid。
- minCommittedLog:Leader服務器proposal緩存隊列committedLog中的最小的zxid。
- maxCommittedLog:Leader服務器proposal緩存隊列committedLog中的最大的zxid。
上面幾個參數的初始化工作,是在數據同步前完成的,如下圖所示是看文末書粗略理解的,可能不正確,如有錯誤請留言批評指正,非常感謝!
首先從Zookeeper內存數據庫中獲取到請求對應的提議緩存隊列,每個proposal都是有對應zxid的,Leader發出的proposal不是直接寫入到Follower,而是先會存於緩存隊列中,等待Follower一個個的寫入。緩存隊列中的proposal事務和Leader本地提交的事務保持一致,Follower根據自己的實際情況,對這個proposal隊列進行處理。
按照上面的理解,下圖中Follower最后處理的zxid即為0x500000004,Leader服務器提交在隊列中的最小zxid為0x500000003,最大zxid為0x500000005,因此minCommittedLog為0x500000003,maxCommittedLog為0x500000005。接下來通過比較這幾個參數,來決定進行以上哪類數據同步。
DIFF
場景:上圖中的情況,就會走DIFF直接差異化同步,其中peerLastZxid在minCommittedLog和maxCommittedLog之間,這種情況也可以理解為Follower沒有同步完Leader存於提議緩存隊列的請求,參考文末書籍,接下來Follower和Leader之間會進行如下圖所示的多次數據包通信。
- Learner在注冊的最后階段,會給Leader發送ACKEPOCH數據包,將當前Learner的紀元currentEpoch和最新事務序號lastZxid發送過去,告訴Leader自己的狀態。
- 在確認了需要使用DIFF直接差異化同步后,Leader會發送DIFF指令給Learner,告訴它即將開始同步差異化的proposal(即Leader提交的但是Learner還未提交的proposal)。
- 對於上上圖的情況,只有一個proposal需要同步,以此為例,Leader服務器會為一個proposal發送兩個數據包給Learner來完成同步,分別是PROPOSAL內容數據包和COMMIT指令數據包,這兩個一組擁有相同的zxid。如果有多個需要同步的proposal,就重復發送proposal對應的這兩個包給Learner來實行同步直到最后完成所有的同步。
- 請求緩存隊列中的proposal同步完成后,Leader會發送一個NEWLEADER指令到Learner。
- Learner收到NEWLeader指令后,會反饋一個ACK消息到Leader,表示自己確認完成請求緩存隊列中proposal的同步。
- 以上是針對一個Learner的同步,會單獨在一個LearnerHandler線程進行處理,其他的Learner也會有對應的LearnerHandler線程來處理,Leader主線程會等待Learner的同步結果。
- 當滿足“過半策略”后,Leader服務器會向所有完成同步的Learner發送UPTODATE指令,告訴它們數據已經是最新的了,並且集群因為過半達到數據一致可以對外提供服務。
- 最后Learner在接受到UPTODATE指令后,會停止與Leader的數據同步,並再次反饋一個ACK消息。
TRUNC+DIFF
場景:這種場景是比較特殊的情況,簡單來說就是,當Leader將事務提交到本地事務日志中后,正准備將proposal發送給其他的Follower進行投票時突然宕機,這個時候Zookeeper集群會選取出新的Leader對外服務,並且可能提交了幾個事務,此后當老Leader再次上線,新Leader發現它身上有自己沒有的事務,就需要回滾抹去老Leader上自己沒有的事務,再讓老Leader同步完自己新提交的事務,這就是TRUNC+DIFF的場景。
- 如上圖所示,當Leader准備將zxid為0x500000003的proposal發送Learner投票就宕機了,導致Leader上會多出一條未在集群同步的數據。
- 此時選取了新的Leader,並且epoch在上次的基礎上加1,Zookeeper集群進入了新的時代,並且新Leader提交了兩個事務,zxid分別為0x600000001和0x600000002。
- 當老Leader重新上線后,新Leader發現它身上有一個0x500000003事務記錄是自己沒有的,這個時候對於老Leader來說,peerLastZxid為0x500000003,而minCommittedLog為0x500000001,maxCommittedLog為0x600000002,peerLastZxid在minCommittedLog和maxCommittedLog之間。這個時候新Leader會發送TRUNC指令給這個老Leader(是Learner,老Leader只是叫起來好理解),讓它截取一部分事務記錄,這樣老Leader會截取到最靠近peerLastZxid同時又存在於提議緩存隊列的事務,即截取掉0x500000003的事務記錄。
- 截取完成后,后面就是DIFF差異化同步了,流程跟上面一樣。
TRUNC
場景:這種情況,就是Learner上的peerLastZxid的值,比maxCommittedLog還要大,這樣只需要截取多余的部分事務記錄就可以了,無需DIFF差異化同步。
具體過程略,TRUNC回滾同步可以參考以上兩種同步方式的過程來理解。
SNAP
SNAP全量同步在兩種情況下會發生。
場景1:Learner上的peerLastZxid的值,比minCommittedLog還要小。
場景2:Leader服務器沒有提議緩存隊列,peerLastZxid不等於lastProcessedZxid,lastProcessedZxid是Leader服務器數據恢復后最大的zxid(不太明白,暫時放這里)。
這兩種情況下Learner和提議緩存隊列之間,要么事務有不重疊的地方,要么無法使用提議緩存隊列,因此只能使用SNAP全量同步。
全量同步就是將Leader上的全量內存數據都同步到Learner,Leader會先給Learner發送一個SNAP指令,然后Leader會准備數據,從內存數據庫中獲取全量的數據節點和會話超時時間記錄器后,將其序列化后發送給Learner,Learner接收到后對其進行反序列化后存儲內存數據庫中,完成全量同步,這種方式看上去比較簡單粗暴。
PS:Zookeeper狀態同步流程的代碼主要在LearnerHandler和Learner兩個類中。
以上,理解不一定正確,學習就是一個不斷認識和糾錯的過程。
參考博文:
(1)https://blog.csdn.net/a3125504x/article/details/106727988
(2)https://blog.csdn.net/weixin_36145588/article/details/75043611
(3)《從Paxos到Zookeeper-分布式一致性原理與實踐》數據同步