天天看點

ZooKeeper的一緻性算法賞析1 ZAB介紹2 ZAB協定源碼實作3 特殊情況的注意點4 未完待續

zab協定全稱就是zookeeper atomic broadcast protocol,是zookeeper用來實作一緻性的算法,分成如下4個階段。

先來解釋下部分名詞

electionepoch:每執行一次leader選舉,electionepoch就會自增,用來标記leader選舉的輪次

peerepoch:每次leader選舉完成之後,都會選舉出一個新的peerepoch,用來标記事務請求所屬的輪次

zxid:事務請求的唯一标記,由leader伺服器負責進行配置設定。由2部分構成,高32位是上述的peerepoch,低32位是請求的計數,從0開始。是以由zxid我們就可以知道該請求是哪個輪次的,并且是該輪次的第幾個請求。

lastprocessedzxid:最後一次commit的事務請求的zxid

leader election

discovery:

synchronization:

broadcast

上面簡單的描述了上述4個過程,這4個過程的較長的描述在zab的paper中可以找到,但是我看了之後基本和zab的源碼實作上相差有點大,這裡就不再提zab paper對上述4個過程的描述了,下面會詳細的說明zookeeper源碼中是具體怎麼來實作的

先看下zookeeper整體的實作情況,如下圖所示

ZooKeeper的一緻性算法賞析1 ZAB介紹2 ZAB協定源碼實作3 特殊情況的注意點4 未完待續

上述實作中recovery phase包含了zab協定中的discovery和synchronization。

加上前面已經介紹的幾個名詞

long lastprocessedzxid:最後一次commit的事務請求的zxid

linkedlist

concurrentmap outstandingproposals

concurrentlinkedqueue tobeapplied

對于上述幾個參數,整個broadcast的處理過程可以描述為:

leader針對用戶端的事務請求(leader為該請求配置設定了zxid),建立出一個議案,并将zxid和該議案存放至leader的outstandingproposals中

leader開始向所有的follower發送該議案,如果過半的follower回複ok的話,則leader認為可以送出該議案,則将該議案從outstandingproposals中删除,然後存放到tobeapplied中

leader對該議案進行送出,會向所有的follower發送送出該議案的指令,leader自己也開始執行送出過程,會将該請求的内容應用到zookeeper的記憶體樹中,然後更新lastprocessedzxid為該請求的zxid,同時将該請求的議案存放到上述committedlog,同時更新maxcommittedlog和mincommittedlog

leader就開始向用戶端進行回複,然後就會将該議案從tobeapplied中删除

leader選舉過程要關注的要點:

所有機器剛啟動時進行leader選舉過程

如果leader選舉完成,剛啟動起來的server怎麼識别到leader選舉已完成

投票過程有3個重要的資料:

serverstate

目前zookeeper機器所處的狀态有4種,分别是

looking:進入leader選舉狀态

following:leader選舉結束,進入follower狀态

leading:leader選舉結束,進入leader狀态

observing:處于觀察者狀态

hashmap recvset

用于收集looking、following、leading狀态下的server的投票

hashmap outofelection

用于收集following、leading狀态下的server的投票(能夠收集到這種狀态下的投票,說明leader選舉已經完成)

下面就來詳細說明這個過程:

1 servera首先将electionepoch自增,然後為自己投票

servera會首先從快照日志和事務日志中加載資料,就可以得到本機器的記憶體樹資料,以及lastprocessedzxid(這一部分後面再詳細說明)

初始投票vote的内容:

proposedleader:zookeeper server中的myid值,初始為本機器的id

proposedzxid:最大事務zxid,初始為本機器的lastprocessedzxid

proposedepoch:peerepoch值,由上述的lastprocessedzxid的高32得到

然後該servera向其他所有server發送通知,通知内容就是上述投票資訊和electionepoch資訊

2 serverb接收到上述通知,然後進行投票pk

如果serverb收到的通知中的electionepoch比自己的大,則serverb更新自己的electionepoch為servera的electionepoch

如果該serverb收到的通知中的electionepoch比自己的小,則serverb向servera發送一個通知,将serverb自己的投票以及electionepoch發送給servera,servera收到後就會更新自己的electionepoch

在electionepoch達成一緻後,就開始進行投票之間的pk,規則如下:

就是優先比較proposedepoch,然後優先比較proposedzxid,最後優先比較proposedleader

pk完畢後,如果本機器投票被pk掉,則更新投票資訊為對方投票資訊,同時重新發送該投票資訊給所有的server。

如果本機器投票沒有被pk掉,則看下面的過半判斷過程

3 根據server的狀态來判定leader

如果目前發來的投票的server的狀态是looking狀态,則隻需要判斷本機器的投票是否在recvset中過半了,如果過半了則說明leader選舉就算成功了,如果目前server的id等于上述過半投票的proposedleader,則說明自己将成為了leader,否則自己将成為了follower

如果目前發來的投票的server的狀态是following、leading狀态,則說明leader選舉過程已經完成了,則發過來的投票就是leader的資訊,這裡就需要判斷發過來的投票是否在recvset或者outofelection中過半了

同時還要檢查leader是否給自己發送過投票資訊,從投票資訊中确認該leader是不是leading狀态(這一部分還需要仔細推敲下)

一旦leader選舉完成,就開始進入恢複階段,就是follower要同步leader上的資料資訊

1 通信初始化

leader會建立一個serversocket,接收follower的連接配接,leader會為每一個連接配接會用一個learnerhandler線程來進行服務

2 重新為peerepoch選舉出一個新的peerepoch

follower會向leader發送一個leader.followerinfo資訊,包含自己的peerepoch資訊

leader的learnerhandler會擷取到上述peerepoch資訊,leader從中選出一個最大的peerepoch,然後加1作為新的peerepoch。

然後leader的所有learnerhandler會向各自的follower發送一個leader.leaderinfo資訊,包含上述新的peerepoch

follower會使用上述peerepoch來更新自己的peerepoch,同時将自己的lastprocessedzxid發給leader

leader的所有learnerhandler會記錄上述各自follower的lastprocessedzxid,然後根據這個lastprocessedzxid和leader的lastprocessedzxid之間的差異進行同步

3 已經處理的事務議案的同步

判斷learnerhandler中的lastprocessedzxid是否在mincommittedlog和maxcommittedlog之間

learnerhandler中的lastprocessedzxid和leader的lastprocessedzxid一緻,則說明已經保持同步了

如果lastprocessedzxid在mincommittedlog和maxcommittedlog之間

如果lastprocessedzxid大于maxcommittedlog

如果lastprocessedzxid小于mincommittedlog

4 未處理的事務議案的同步

learnerhandler還會從leader的tobeapplied資料中将大于該learnerhandler中的lastprocessedzxid的議案進行發送和送出(tobeapplied是已經被确認為送出的)

learnerhandler還會從leader的outstandingproposals中大于該learnerhandler中的lastprocessedzxid的議案進行發送,但是不送出(outstandingproposals是還沒被被确認為送出的)

5 将learnerhandler加入到正式follower清單中

意味着該learnerhandler正式接受請求。即此時leader可能正在處理用戶端請求,leader針對該請求發出一個議案,然後對該正式follower清單才會進行執行發送工作。這裡有一個地方就是:

上述我們在比較lastprocessedzxid和mincommittedlog和maxcommittedlog差異的時候,必須要擷取leader記憶體資料的讀鎖,即在此期間不能執行修改操作,當欠缺的資料包已經補上之後(先放置在一個隊列中,異步發送),才能加入到正式的follower清單,否則就會出現順序錯亂的問題

同時也說明了,一旦一個follower在和leader進行同步的過程(這個同步過程僅僅是确認要發送的議案,先放置到隊列中即可等待異步發送,并不是說必須要發送過去),該leader是暫時阻塞一切寫操作的。

對于快照方式的同步,則是直接同步寫入的,寫入期間對資料的改動會放在上述隊列中的,然後當同步寫入完成之後,再啟動對該隊列的異步寫入。

上述的要了解的關鍵點就是:既要不能漏掉,又要保證順序

6 learnerhandler發送leader.newleader以及leader.uptodate指令

該指令是在同步結束之後發的,follower收到該指令之後會執行一次版本快照等初始化操作,如果收到該指令的ack則說明follower都已經完成同步了并完成了初始化

leader開始進入心跳檢測過程,不斷向follower發送心跳指令,不斷檢是否有過半機器進行了心跳回複,如果沒有過半,則執行關閉操作,開始進入leader選舉狀态

learnerhandler向對應的follower發送leader.uptodate,follower接收到之後,開始和leader進入broadcast處理過程

前面其實已經說過了,參見2.1中的内容

先來看看持久化過程:

broadcast過程的持久化

leader shutdown過程的持久化

再來說說恢複:

事務快照的恢複

事務日志的恢複

由此我們可以看到,在初始化恢複的時候,是會将所有最新的事務日志作為已經commit的事務來處理的

也就是說這裡面可能會有部分事務日志還沒真實送出,而這裡全部當做已送出來處理。這個處理簡單粗暴了一些,而raft對老資料的恢複則控制的更加嚴謹一些。

一旦leader挂了,上述leader的2個集合

就無效了。他們并不在leader恢複的時候起作用,而是在系統正常執行,而某個follower挂了又恢複的時候起作用。

我們可以看到在上述2.3的恢複過程中,會首先進行快照日志和事務日志的恢複,然後再補充leader的上述2個資料中的内容。

目前leader和follower之間的同步是通過bio方式來進行的,一旦該鍊路出現異常則會關閉該鍊路,重新與leader建立連接配接,重新同步最新的資料

用戶端收到ok回複,會不會丢失資料?

用戶端沒有收到ok回複,會不會多存儲資料?

用戶端如果收到ok回複,說明已經過半複制了,則在leader選舉中肯定會包含該請求對應的事務日志,則不會丢失該資料

用戶端連接配接的leader或者follower挂了,用戶端沒有收到ok回複,目前是可能丢失也可能沒丢失,因為伺服器端的處理也很簡單粗暴,對于未來leader上的事務日志都會當做送出來處理的,即都會被應用到記憶體樹中。

同時目前zookeeper的原生用戶端也沒有進行重試,伺服器端也沒有對重試進行檢查。這一部分到下一篇再詳細探讨與raft的差別

本文有很多細節,難免可能疏漏,還請指正。

這裡留個問題供大家思考下:

raft每次執行appendentries rpc的時候,都會帶上目前leader的新term,來防止舊的leader的舊term來執行相關操作,而zookeeper的peerepoch呢?達到防止舊leader的效果了嗎?它的作用是幹什麼呢?

下一篇就來對比一下raft和zab之間的細微的差別

歡迎關注微信公衆号:乒乓狂魔

ZooKeeper的一緻性算法賞析1 ZAB介紹2 ZAB協定源碼實作3 特殊情況的注意點4 未完待續

繼續閱讀