其實整個項目中一個最主要的看點就是選舉算法,而這部分也是邏輯最複雜最難理解的部分。不一樣的實如今不一樣的場景下的策略也不盡相同,並且場景很是之多。接下來咱們一塊兒來看一下Cocklebur的實現思路。node
一個問題擺在咱們面前:集羣啓動後,如何選舉出一個主節點,其他都是從節點?實際上用狀態描述去理解這個問題就是,集羣啓動後每一個節點都是LOOKING(選舉進行時)狀態,如何經過節點間有限次的通訊最終使得集羣中有且僅有一個節點爲LEADERING(主節點)狀態,其他都是FOLLOWING(從節點)狀態。算法
圖1 經過一種方法是集羣的每一個節點達成一致網絡
咱們暫且拋開Paxos算法,咱們只考慮如何讓進羣全部節點達成一致,確切的說是活着的節點。你可能會回憶小時候小夥伴們一塊兒愉快的玩耍時,你們都會一致的推薦某個小夥伴爲頭兒當領隊,緣由就是他最高。其實這個選舉過程當中默認了一些條件:選舉過程不會失敗(選舉一次性成功)、每一個節點彼此都快速通訊(不會有人上廁所)、沒有節點會忽然掛掉(不要往太壞了想~多是他媽叫他回家吃飯)。也就是說,一次選舉過程很是簡單,你們彼此看看,誰個子高天然就會當領隊了。分佈式
可是分佈式環境中遠沒有這麼簡單,資源有限(節點不是隨便相互看幾遍就OK的,看一眼就消耗很多資源)、網絡延遲(至關於在選舉的時候有人上廁所了)、節點崩潰(他媽叫他回家吃飯了),任何狀況都有可能發生。咱們的目標是儘可能快速的讓集羣中每一個可對外服務的節點(通訊正常的節點)都有最新的數據,從而總體對外提供服務。spa
再來看看Paxos的角色。目前在集羣中每一個節點均可能會成爲Leader,因此每一個節點都既是提案者(proposeor)也是接受者(acceptor)。固然他們也是Learner,因爲acceptor自己是知道選舉結果的,那麼就不須要再通知Learner了。設計
名詞解釋code
問題已經引出,那麼咱們來看看Cocklebur的類Paxos是如何實現的。首先咱們須要來了解一下算法中涉及的一些概念:blog
名稱遞歸 |
變量名seo |
描述 |
數據版本號 |
Xid |
這是該節點所持有數據的版本號,版本號越高,那麼數據越新。對於選舉過程來講,它是該節點的固有屬性。就像上面例子中的身高,是評判標準之一。 |
主機名稱 |
my_host_name |
節點的主機名或者ip,網絡中的惟一標識。 |
當前狀態 |
cur_mode |
LOOKING(正在選舉,尚未找到Leader)、FOLLOWING(已經找到Leader的從節點)、LEADERING(已經找到Leader的主節點,就是它本身)。 |
當前推薦節點 |
cur_rec_host |
對於LOOKING階段,是該節點當前所知道xid最大的那個主機名,對於其餘狀態下的節點就是Leader主機名。 |
已交換表決器節點列表 |
heard_from |
通訊過的主機名稱集合。 |
已知節點列表 |
known_hosts |
與heard_from不一樣,known_hosts每每要更大一點,它存放了heard_from的遞歸列表。好比,1跟2交換表決器,此時2跟4交換過,1跟3交換過,那麼1的known_hosts即爲2、4,而2的known_hosts爲1、3。這種機制也是節點選舉加速的關鍵點之一。 |
表決器 |
Voter |
實際上就是節點間通訊所傳遞的內容:rec_host、my_host_name、known_hosts、cur_mode、xid、logical_clock。 |
邏輯時鐘 |
logical_clock |
每一次選舉都會有一個邏輯時鐘,若是某個節點得知本次選舉失敗,則邏輯時鐘加1。邏輯時鐘的本質是標識了該節點狀態信息的版本,若是一個節點因爲延遲發現當前的選舉早已經不是以前那次選舉了,那麼它當前的狀態信息也就做廢了,因此要清空狀態以後提升邏輯時鐘,直到與當前選舉的時鐘匹配。 |
狀態信息 |
host_stat |
上面除了表決器以外全部信息都屬於狀態信息。 |
多數派 |
majority |
超過集羣節點總數一半的最小整數個節點,好比3的多數派數目是2,4的多數派數目是3。 |
ACK鎖 |
Ack_lock |
在算法第二階段,知足Leader條件的節點會爭取其餘節點的承諾,保證其餘節點再也不接受表決器交換,以便順利成爲Leader,那麼這些「準Follower」若是以爲沒問題就會把本身鎖住,保證再也不交換表決器。不熟悉的同窗參看該系列博客的第一篇:理解Paxos |
注:有人爲集羣若是某些節點死掉了多數派怎麼保證?答:永遠用集羣鎖配置的節點的個數來決定。也就是說配置一旦生效,多數派數目就已經肯定,若是一個集羣中存活的數目不超過半數,那麼該集羣永遠不會完成選舉。其實這樣作的好處就是最大限度的保證集羣數據的可靠性,雖然這是算法所要求的,可是咱們只要保證一個多數派可服務就能最大限度的保證這其中存在最新數據,由於Cocklebur在寫數據時也是超過一半纔算成功的。
算法實現
對於實現過程可能會有一些疑問,對於交換表決器這種行爲必定是要作rpc調用的。那麼Client和Server應該分別怎麼去實現呢?其實對於選舉算法咱們更關注Client端的實現邏輯,Server只是去接收一下表決器而已,在更新本身的狀態信息(host_stat)時注意同步問題便可。那麼咱們主要來看看Client端的選舉邏輯是怎麼實現的。
源碼中,主要算法邏輯在CockLeaderElection::lookForLeader() 中。
過程1 選舉初始化 獲取多數派個數; 獲取集羣主機列表; 釋放ACK鎖; 重置host_stat; 初始化交換表決器的通訊客戶端; 邏輯時鐘++;
過程2 選舉過程 While (自身狀態爲LOOKING && 主機列表不爲空) Do 遍歷主機列表(不包含該節點) 當前的目標節點爲cur_task_node // 第二階段--被ACK鎖定的邏輯 If 自身被ACK_LOCK鎖定 Then
等待cur_rec_host 發送成爲Leader的消息; if 在超時時間內發現cur_rec_host成爲Leader Then break;//此時cur_mode已經爲Following,因此while直接結束 Else if 等待超時 Then 重置host_stat//由於以前的候選人不知道幹嗎去了 解ACK_LOCK; break; //繼續跟其餘節點交換,由於此時依然爲Looking,因此while將繼續 End if End if 生成表決器;//實際上就是讀取自當前的host_stat // 交換表決器的邏輯 If 該節點沒有對目標節點(cur_task_node)加鎖成功 then 與cur_task_node交換表決器;// 主要沒加鎖成功,那就說明沒有贏得該節點,就須要換票。 If 拿到目標主機的表決器發現rev_host爲空 Then continue;//交換失敗,多是宕機或目標主機被別的節點加鎖了。 End if If 目標主機的邏輯時鐘比本身的要大 Then 本次選舉失敗;break; End if 更新host_stat; 生成表決器; If 在交換表決器以後發現目標主機是Leadering或者Following Then 該節點則會設置自身狀態爲Following,而且把rec_host設置爲它所讀取到的Leader。 Break; //完成選舉 End if End if // 得知一個多數派信息以後的邏輯 // 即該節點已經獲知了其中一個多數派全部主機信息,也就是說該節點有條件決定誰是Leader了。(注:此時有人會問,瞭解一個多數派並不能全面瞭解整個集羣,萬一該多數派以外有節點還有更新的信息怎麼辦?緣由見「問題一」。) If known_hosts.size() >= num_major // 蒐集信息達到了多數派,若是沒有達到怎麼辦?見「問題二」 Then If cur_rec_host == my_host_name //說明該節點是這個多數派的Leader候選者 Then 開始逐個遍歷known_hosts去詢問本身是否能夠當Leader。同時計數機去記錄確定答覆的數量; If 收到的ack許諾達到了多數派的要求 then 修改狀態爲Leadering,並向全部多數派成員發送本身當Leader的消息; Break;//選舉結束; Else then Continue;//繼續去和其餘的成員交換表決器(爲什麼不停下來呢?問題三) End if End if End if
End while
另外須要補充更新host_stat時,咱們的rec_host選擇算法是先比較Xid較大的,若是Xid同樣大則選擇字典序較大的。
問題一:緣由就是,咱們也不能肯定該多數派以外的其餘主機必定就有更(四聲)新數據,並且也不能肯定其餘主機通訊是否正常。若是你放不下其餘主機,意味着要去重試屢次其餘主機,這樣的策略並不適合在線應用,何況這種狀況不容易發生,由於上面咱們闡述爲什麼任意多數派都能保證擁有一份最新數據。其實總結一點就是見好就收,達到了多數派立刻就開始張羅選出Leader。另外,即便這個多數派沒有最新數據,那麼在集羣穩定後,後續加入的Follower若是有最新咱們也會加以數據同步處理。
問題二:若是收集了一個多數派信息以後,該主機發現本身不是候選者,它的行爲以後是如何的呢?答案是他依然會與其餘人交換表決器。由於發現本身不是候選人不表明以後的某個時刻就沒機會了,極可能那個候選者宕機了,那麼其他的主機中說不定那臺就有機會了。因此觀機待變。
問題三:從算法上看,一個候選者是十分激進的,當他發現本身沒有獲得預期的ACK鎖許諾,那麼他會直接去與其餘節點去交換表決器。之因此這樣,是爲了把以前鎖住的主機代價儘可能減小到最小。由於若是該候選者失敗了,那麼以前被ACK_LOCK鎖住的主機在一段時間內不能接受任何表決器。只能等待超時才能解鎖。
總結
整個Cocklebur的選舉流程就結束了。有些行爲須要特別指出:1、選舉過程當中只要知足多數派,候選者就開始提議作Leader,這個過程不會等待。因此這就會致使選舉出來的那個Leader可能不是擁有最新數據且最大字典序的那一個。爲了保證數據一致性,數據同步工做在Leader集羣穩定以後進行(這個在後續章節講述)。2、若是一個LOOKING狀態的主機獲得的表決器狀態爲LEADERING或者FOLLOWING,那麼他將嘗試加入到這個穩定集羣之中。這有利於爲Cocklebur集羣動態加入節點,也保證了多數派以後能夠歸入那些延遲的節點。3、知足多數派以後,候選者第一個給本身加ACK_LOCK,可是在候選者爲多數派其餘成員加鎖這麼短暫的過程當中,可能某些多數派成員的cur_rec_host被改變,那麼候選者極可能功虧一簣。實際上可能性最大的就是真的存在一個字典序更大的主機在候選者加鎖以前與多數派成員交換了表決器。因此這裏將作一些小小的改進,那就是:若是一個主機成爲了多數派成員,那麼它更新host_stat時將不受字典序影響,除非有更大的Xid。4、再次追問:若是多數派成員此時接到了更大的Xid怎麼辦?那麼候選者在向其加ACK_LOCK時必定會失敗,它極可能會加入了一個新的多數派,而其他已經被加鎖的成員則會等待鎖超時。其實這裏存在一個更加嚴重的問題,那就是若是集羣中剛好已經掛掉幾臺機器,而擁有最大Xid的那臺沒法造成本身的多數派,(咳咳,這裏說不清楚,我立刻舉個例子說明此問題!):1已經被加ACK_LOCK,2已經被4改變了主意,3 是候選者,4是擁有最大xid那個 5已經掛掉。OK,咱們來看看這樣一種窘態,3準備Lead1,2時被4阻撓,由於4有最大的Xid,而4只能與2交換表決器,1,3都被加鎖。那麼3若是發現本身候選失敗時就會去找4,那麼4就得知2,3能夠加鎖。因此4,2,3將會組成集羣,因此1就會很遲的加入集羣。若是節點更多,那麼意味着更多像1這樣的節點不能快速加入,因此ACK鎖的超時時間的設定就是個關鍵,過短了對於候選者延遲是個挑戰,太長了對於上面狀況是個挑戰。
其實,面對分佈式問題時,咱們的策略應該更多的去考慮你所在的具體的應用場景。在該場景下,去綜合考慮全部問題發生的機率。而程序的實現也可以更加的靈活,經過參數的配置去適應這些事件發生的機率。Cocklebur的某些地方設計的並很差,可是經過剖析整個流程咱們能夠很清楚的摸清楚Paxos在實際場景中的面貌。若是實踐與理解相結合,咱們能夠打磨出更加好用的服務組件。