Consul實現原理---Raft算法

 公司的註冊中心要調研選型,不再使用Eureka,我負責這件事,先調研了Consul,Raft算法真的很巧妙,可惜的是consul的一些設計架構無法在我們公司生產環境使用。

Raft將一致性問題分解成了三個獨立的部分:leader選舉、日誌複製、安全性。

 

一、Raft一致性的實現 複製日誌

        想要實現共識性算法主要有兩種方式:第一種方式稱爲對稱式或無主式,在這種方式下,所有的服務器都有相同的角色,它們有同等的權力,它們任何時候的行爲幾乎都是一樣的,客戶端可以與任何一臺服務器進行通信。第二種方式稱爲非對稱式或基於領導者(leader),服務器在任何時候都不是對等的,只有其中的一臺服務器是領導者(leader),領導者負責集羣的所有操作,其他的服務器只是簡單地服從領導者發出的指令,在這種系統下,客戶端永遠與領導者通信,只有領導者才與其他的服務器發送通信。

        Raft 就是使用上面第二種方式。它將共識性算法的問題分解成兩類不同的問題,一種是在領導者正常運行下,進行的普通操作;另一種是在領導者崩潰時,需要對領導者進行重新選舉,這種方式有其優勢,它讓普通的操作變得非常簡單,不需要關心是否有多個領導者相互發生衝突,或同時發出指令,只要有一個領導者控制全局,就可以完全按照它的指令來運行。Raft 算法的複雜之處在於領導者發生變化時,因爲當領導者崩潰時,會使系統處於不一致的狀態,後續被選舉的領導者需要對此這些不一致狀態進行清理。總體上說,基於領導者的方式要比無領導者的方式簡單,因爲無須擔心不同服務器間會出現衝突,只須關心領導者發生變化的情況。

 

Raft 算法共分成 6 個部分,首先我們要介紹的就是領導者的選舉。

  1. 如何從所有的服務器中選擇領導者?如何在當作爲領導者的服務器崩潰時能檢測到故障並挑選另一個領導者來替代它?

  2. 會介紹當領導者接收到客戶端請求時,系統是如何處理正常操作的。這是 Raft 算法中最簡單的部分。

  3. 會討論領導者發生改變的情況,這部分是 Raft 中最複雜的,也是保證整個系統行爲最重要的部分。首先,會討論什麼叫做安全,如何保證安全?其次,領導者是如何識別日誌的一致性的,從而可以將系統恢復到處於一致狀態下。

  4. 會討論領導者發生改變時的另一個問題。如何讓曾經崩潰死機的老領導者,重新迴歸到集羣后集羣的狀態仍然能保持一致。

  5. 會談論客戶端是如何與集羣交互的。關鍵點在於客戶端是如何處理服務器崩潰,如何保證客戶端發送的命令是線性的,即操作執行也僅執行一次。

  6. 最後會討論如何處理配置變更的情況,即如何對集羣增加或移除服務器。      

 

          Raft首先選出一個傑出的leader,然後給予其管理日誌複製的全部職責來實現一致性。該leader從客戶端接收日誌條目,將他們複製到其他服務器,並告訴他們從狀態機獲取日誌條目是安全的。

         在任何給定的時間,每個服務器都是三種狀態之一:leader(領導者),follower(跟隨者),candidate(候選人)

         002827_APoV_271522.png

      Raft將時間分成任意長度的term。term以一串連續的數字進行編號。每個term都以一次選舉開始,其中一個或多個candidate嘗試成爲leader,如果一個candidate競選成功,那麼它將作爲leader服務其他服務器。Raft保證任何一個給定的term內,至多隻有一個leader。每個term開始一個選舉。一次成功的選舉後,一個leader管理整個集羣直到term結束。在 Raft 系統的所有服務器都保持着一個被稱爲當前任期的值,這個信息必須存於服務器的可靠媒介中(如硬盤)。這樣就能在服務器崩潰之後得以重啓並恢復。任期這個概念十分重要,它使 Raft 可以判斷過期的信息。

     

        每臺服務器無論是領導者還是跟隨者,都各自保存一個日誌副本。日誌本身被分成了多條記錄(Entries),記錄是由下標索引的位置和term來進行唯一標識的。

  • 需要保證所有的狀態機,以相同的順序執行相同日誌記錄的命令。爲了達成總體的安全性要求,Raft 實現了一個安全屬性,一旦領導者決定某個特定記錄已提交,那麼 Raft 就需要保證該條記錄會出現在它所有未來領導者的日誌記錄中,並且也處於已提交狀態。,領導者永遠不會覆蓋日誌記錄,它只會追加,並且只有leader才能committed
  • 如果不同日誌中的兩個條目擁有相同的索引和term值,那麼他們儲存相同的命令。領導人最多在一個任期裏在指定的一個日誌索引位置創建一條日誌條目,同時日誌條目在日誌中的位置也從來不會改變。

  • 如果不同日誌中的兩個條目擁有相同的索引和term值,那麼日誌中之前的條目都相同。第二個特性由附加日誌 RPC 的一個簡單的一致性檢查所保證。在發送附加日誌 RPC 的時候,領導人會把新的日誌條目緊接着之前的條目的索引位置和任期號包含在裏面。如果跟隨者在它的日誌中找不到包含相同索引位置和任期號的條目,那麼他就會拒絕接收新的日誌條目。一致性檢查就像一個歸納步驟:一開始空的日誌狀態肯定是滿足日誌匹配特性的,然後一致性檢查保護了日誌匹配特性當日志擴展的時候。因此,每當附加日誌 RPC 返回成功時,領導人就知道跟隨者的日誌一定是和自己相同的了。

挑選最好的領導者

拒絕投票:(0lastTerm v > lastTerm c)|| ((lastTerm v == lastTerm c) && (lastIndex v > lastTerm c))

 

       

平衡舊領導者(Neutralizing Old Leader)

所以如果發送者的任期比接收者的要老,那麼就表示發送者是過時的,這時接收者會立即拒絕 RPC 請求,並將包括了接收者任期信息的響應發送回發送者,這樣當發送者接收到響應時就會意識到,它的任期號是過期的,此時它就會停下並作爲跟隨者繼續運行,同時它還會更新自己的任期號,並與其他服務器保持一致。反之,如果接收者的任期號更老,如果這時接收者不是跟隨者,那麼它也會停下,並作爲跟隨者,而且更新它自己的任期號。略微不同的是接收者不會拒絕 RPC ,它會接收 RPC 請求。

與客戶端通信

        客戶端是如何與系統進行交互的。這點並不複雜,客戶端將命令發送給領導者,並獲得響應,如果客戶端不知道哪臺服務器是領導者也沒關係,它可以與集羣的任意一臺服務器進行通信,如果這臺服務器不是領導者,那麼它會告知客戶端,並將客戶端重定向到領導者,然後客戶端會再次發送請求。只有在領導者記錄下命令,並已經將其提交,然後發送給狀態機執行之後,纔會將結果返回給客戶端。這裏比較微妙的是,如果領導者發生崩潰或請求發生超時該怎麼辦?如果發生這種情況,客戶端會隨機挑選另一臺服務器並再次發送請求,最終它會將請求發送到新的領導者,新的領導這會執行該命令。這個可以保證命令最終總能被執行。

        問題在於領導者會在執行完命令後響應客戶端之前發生崩潰,所以命令本身是無法知道自己是否被記錄或已被執行。這時客戶端就會再次發起請求,這樣命令就又被執行了一遍。這是不能被接受的,因爲我們要每條命令執行且僅被執行一次。Raft 解決這個問題的辦法是讓客戶端爲每條命令生成一個唯一的 ID ,並將其與命令一起發送給領導者,當領導者記錄該條命令時,也會包括這個唯一 ID ,但在領導者接受命令之前,它會進行檢查,看其他記錄中是否已存在相同的 ID ,如果存在相同的,那麼它就會知道該條命令請求是多餘的,所以它會找到該條記錄,並忽略這條新命令,並將老的執行結果返回給客戶端。

        所以只要客戶端不崩潰,結果最多隻會被執行一次。這也是我們希望系統應該具備的線性一致性。

 

配置變更

 

uploading.4e448015.gif正在上傳…重新上傳取消

        必須要意識到,我們無法直接從舊配置切換到新配置。我們來看個例子。假設系統集羣有三臺服務器正在運行,這時我們希望再增加兩臺服務器,所以最終集羣內會有五臺服務器。如果我們只是要求每臺服務器從舊配置切到新配置,問題是這個切換不能無法同時完成,時間上總會有先有後。而這可能會導致衝突的大多數。因爲 S1、S2 可以在某個時候形成舊集羣的大多數,並決定領導者。而與此同時,另外三臺服務器 S3、S4、S5 已經切至新的配置,它們也形成了該配置狀態下的大多數。所以它們也可以決定領導者,確認提交狀態。這樣就會與 S1、S2 發生衝突。這樣,我們就需要使用兩段協議(two-phase protocol),無法在一段內達到目的。

       

        Raft 將第一階段到中間階段稱爲多邊共識(joint consensus),在這個階段中,集羣包括所有的服務器上新舊兩種配置,但是如選舉和提交的決策,需要在新舊兩個獨立的配置狀態下達成一致。

        集羣配置以 C(old) 開始,然後客戶端向領導者發送請求,當接收者收到請求之後,會向日志裏新增一條記錄,要求記錄新配置 C(old+new) ,配置與其他普通的命令記錄一樣,領導者會用 AppendEntries RPC 請求將其發送給集羣的其他服務器,配置變更唯一的不同在於它們會立即生效,一旦服務器將新配置記錄到日誌中,那麼它就立刻生效,並不需要等待該日誌記錄變爲已提交狀態。所以此時在領導者上已經認爲 C(new) 已生效,那麼如果配置C(old+new) 要生效,就要求該配置分別在新舊配置服務器下同時都成爲大多數。又過了一會,當記錄狀態變成已提交後,也還是可能存在決策在 C(old) 與 C(old+new) 決定。例如,如果領導者在記錄新配置記錄後就發生崩潰,有可能某些其他舊配置的機器仍然處於工作狀態,被選舉成領導者管理集羣。但在某個時間點,C(old+new) 會變爲已提交的狀態,在此種狀態下,任何機器就無法只根據 C(old) 來做出決策。爲了讓領導者被成功選舉,它必須保證所有的記錄都已提交,所以一旦 C(old+new) 記錄已提交,它就能保證任意選舉的領導者都有該記錄,也就是說領導者已使用該配置。所以在這個時候,集羣是處於聯合共識下運行的,一旦聯合共識被提交確認,領導者就可以將配置變更 C(new) 寫入日誌記錄,併發送給集羣其他服務器。所以在這個時候,集羣下服務器配置可能在 C(new) 或 C(old+new) 的狀態,因爲這時服務器也可能再次出現崩潰,另一服務器會替代成爲領導者,並使用聯合共識下的 C(old+new) 配置。但最終新配置記錄 C(new) 會處於提交狀態,一旦出現這種情況,集羣所有未來的決策都將基於 C(new) 。所以關鍵在於,不存在 C(old) 或 C(new) 在不進行相互協調的前提下就能做出決策的情況。C(old) 可以獨立做出決策,C(new) 也可以獨立做出決策,但是兩者不會發生重疊。在這兩段時間之間,兩個配置需要相互協調,這就能保證,集羣不會兩個獨立的達成共識的羣體存在。

 

 

 

uploading.4e448015.gif正在上傳…重新上傳取消

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章