什么是Raft
Raft是由Diego Ongaro與John Ousterhout的一篇論文中所提出的分布式存儲一致性算法。在分布式系統中,為了防止服務器數據由于只存一份而導致在服務時由于一個存儲節點故障就產生服務完全不可用或數據丟失的嚴重后果,數據的存儲會有多個備份副本,分別存儲于不同的存儲服務器上,都可以提供服務。這樣一來,如果有合適的算法能保障各服務器對同一份數據存儲的內容一致,并且在一臺服務的存儲服務器故障時,這個集群能以適當的邏輯切換到其他正常服務器提供服務,那么就可以實實在在地保障分布式存儲服務的質量。Raft就是為這樣的系統服務的。
Raft的特點
- 簡單易學。Paxos算法由Leslie Lamport通過論文發表于1990年,是當時最實用的分布式存儲一致性算法。有許多上了年頭的分布式系統底層采用的是Paxos。而Raft是由Paxos簡化得來。Diego Ongaro與John Ousterhout在自己的論文中指出:經過對比,學生學習Paxos所需時間明顯長于學習Raft所需時間。
- 最終一致性。存儲一致性根據對于各個服務器的同一份數據之間允許差異的嚴格程度不同,可以分為強一致性、最終一致性、弱一致性。根據CAP定理,一個分布式系統不能同時保障一致性(Consistency)、可用性(Availability)與分區容錯性(Partition tolerence)。但是經過權衡,系統可以達到“BASE”效果:基本可用(Basically available)、軟狀態(Soft state)、最終一致性(Eventually consistent)。Raft所達到的最終一致性,是指各節點上的數據在經過足夠的時間之后,最終會達到一致的狀態。
Raft基本思想
- 選舉與邏輯主從管理。一個典型的Raft集群由3臺或5臺服務器組成,各節點最終都會保存相同的數據。在運行開始時,集群會通過自動選舉產生一個主節點(Raft中稱為leader),其他節點為從節點(稱為follower)。所有來自客戶端的讀寫請求都要由leader處理;follower若收到了客戶端請求,則要回復leader的地址,從而使請求重定向到leader。當探測到leader失效(leader通過心跳機制通知follower自己的存活,超時則認為leader故障),其他節點將會出發重新選舉,得到新的leader。
- 用日志存儲寫操作。顯然,只讀操作不影響存儲一致。所以,如果能保證所有節點對于所有寫操作都能按照相同的順序沒有遺漏也沒有多余地保存下來,那么自然能夠保證最終存儲一致。Raft使用日志(log)存儲寫操作。Leader收到寫操作請求后,在自己的日志條目存儲中生成對應的日志條目(log entry,簡稱entry)。之后就只需要把自己的日志記錄復制(replicate)到各follower即可。
從上面可以看出,Raft的核心問題就是保障:可靠的選舉與日志復制操作。實際上這兩點在許多分布式系統中都有應用。
Raft的選舉
Raft集群各機之間的RPC報文可分為兩種:添加條目RPC(AppendEntries RPC,以下簡稱AE)與請求投票RPC(RequestVote RPC,以下簡稱RV)。AE是leader用來向follower加entry使用的。RV是“候選人"(candidate,是除了leader、follower以外的第三種狀態,只出現于選舉時)用來向其他follower要求給自己投票的。
如果超過一定時間,follower檢測不到來自leader的周期性心跳消息(leader將不含實際entry的AE作為心跳使用),就會變為candidate狀態。此時,Raft集群就會開始選舉leader。在Raft中,時間上有著term的概念,表示一個leader的統治期;每個term有著獨特的遞增的序號,稱為term ID。leader會在自己發送的AE中都附上自己的term ID。當新的candidate產生,它會在上一個leader的term ID基礎上加1,作為自己的term ID,并在廣播給所有其他機的RV中也附上新的term ID。任何非candidate的節點收到了帶有比自己已經見過的任何ID更大的term ID的RV時,就會回復這個RV,并更新“自己已經見過的最大ID”。如果同時這個RV中說明的candidate含有的日志足夠新(詳細說明見后文),follower就會為這個candidate投票。這樣一來,任何節點就都不會為同一個term ID投兩票。如果一個candidate收到了足夠的票數(票數加上自己的一票能夠占集群的多數),就會開始發送心跳,宣告自己在這個term內的leader地位,開始服務。
由于在一個leader失效時,可能有多個follower超時的時刻相同,發出RV廣播的時刻也大致相同,結果在新term都得不到足夠的票數,所以candidate存在等票超時與隨機等待機制,避免一致沖突,選不出leader。candidate在等足夠的票時當然也會看有沒有其他candidate宣布勝利。如果都沒有發生的話,在一段超時后,candidate們會再開啟新term ID,但會隨機等待一段時間,然后才廣播RV請求投票(廣播RV前相當于follower,可以投票)。由于隨機等待的時間有長有短,最終一定會由率先結束等待的candidate獲勝。
Raft的日志復制
前面提到,Raft集群由leader統一處理所有客戶端請求,會將寫請求轉換為log entry,然后用AE向follower發送來復制log。Leader創建日志條目時,會給它附上兩個屬性:term ID與log index。term ID即為自己統治期的ID,在當前term的日志條目都用這個ID;而log index也是一種遞增序號,但它是在集群的整個運行期間連續的。跨term時,log index會在前面的的基礎上遞增1,而非歸零重計。如此一來,考慮到選舉機制保證了一個term ID一定對應確定的一個leader節點,我們由term ID+log index這個組合就一定可以確定唯一的一條日志。
Leader生成了寫操作的日志之后,就通過AE將日志條目發送到各個follower處,讓它們把日志按早晚順序加入自己的日志存儲中。如果有集群多數的節點(包含leader自己)都成功存儲了一條日志(follower會回復自己的存儲情況),那么leader就認為這條日志及更早的日志的存儲都是安全的,會回復客戶端寫操作成功,并會告知集群已經可以將到此條的所有日志中的寫操作實際進行,修改自己的存儲數據。
當leader上臺時,會以自己自己的日志存儲為準,使其他節點與自己對齊。如果比自己快,就截掉多的;比自己慢,就用自己的日志存儲給它慢慢補足。但是在復制日志的過程中,各個follower可能因各種原因速度差異較大。那么如果leader突然故障,而一個復制的特別慢的follower選舉為leader,那么不就會導致大量寫操作失效嗎?之所以要保證日志已經復制到了多數機上,才可認為寫操作成功,就是為了避免這種情況。之前在講解選舉機制時提到:RV要包含candidate的日志存儲版本消息,也就就是最后一條日志的term ID+log index。如果投票的follower發現這candidate的版本消息比自己的還要舊,就會拒絕給它投票。由于只有復制到了多數節點的日志的寫操作才被回報為“成功”,而選舉時必須要獲得多數票才能當選,所以最終當選的leader一定擁有上一個leader所回報為成功的操作的所有日志,不會缺失。
后記
由于在實際運行中,不論服務器還是網絡信道,都是可能發生故障的。所以Raft其實還有一些額外的措施來保障數據的最終一致性。限于篇幅,本文不再詳述。如有興趣,請研讀Raft原論文。相關學習資源,包括系統可視化模擬、視頻講解、比論文更詳細的講解Raft的書籍,都可以在專題網站上面找到。