Raft協議詳解(一)前言:子問題分解
分散式一致性,一直是分散式系統中非常重要的部分。在多機叢集中,如何保證不同伺服器上同一副本的資料是一致正確的,如何保證在故障頻發的叢集中資料能夠安全儲存等問題,都需要靠分散式一致性來解決。在對分散式一致性演算法進行設計的時候,存在如下幾點需要注意的地方:
1. 演算法的正確性保證,不論在丟包、宕機、延遲等情況,都不能返回錯誤的結果
2. 只要大部分機器可以用,那麼演算法就可以正常執行
3. 不依靠時序來保證一致性
4. 收斂速度應該比較快,能儘快在大部分上機器完成,少部分沒有完成的不影響演算法使用
(我知道上面這幾句有點不是很好理解,後面我儘量只講好理解的)
很多一致性演算法來進行保證分散式一致性問題。比如很出名的Paxos協議,Google的分散式鎖Chubby就是基於Paxos實現的,還有Zookeeper等。但是Paxos的問題在於比較難以理解,實現起來非常困難。即使基於Paxos的Zookeeper和Chubby,其實現和Paxos仍然有一定的出入。這就導致了一個非常嚴重的問題,就是你的實現沒有按照完整的Paxos實現的話,那麼你的方法就沒有被理論所很好的支撐,可能你的實現就會出現很多問題。
那麼,基於這個問題呢,斯坦福的大佬們就提出了Raft演算法。這個演算法和Paxos其實在功能上,和效能上差不多。好處就是便於理解以及工程實現。當前也有挺多公司採用Raft演算法,但是其實這個演算法在實際的應用場景下,還存在一定的問題。比如阿里也在使用Gossiping演算法。這裡我們就不在多加討論。我們這個文章系列還是對Raft進行學習。文章內容基於該演算法發表的論文,小夥伴們也可以去讀一下論文原文。
Raft
上面我們講到,Raft的主要貢獻就是設計一個簡單易於理解和實現的演算法。那麼他如何達到這個目的的呢?從兩方面入手的,首先是將複雜的問題進行分解,分成了幾個小的問題;第二是減少系統中的變數狀態,使問題儘量簡單一點。這其實也能對我們以後進行系統設計提供借鑑。那麼接下來我們開始詳細介紹Raft。
剛才講到Raft進行了問題分解,分成了四個小問題,小問題分別是:如何選主(leader election)、如何進行日誌複製(log replication)、安全性保證(safety)以及維護成員變化(membership changes)。可能小夥伴還不太知道為啥要分為這幾個問題,我這裡先講一下。要講這個,咱們首先得看一個圖。
圖1 Raft演算法元件組成圖
我們看一下Raft的組成裡面有哪些東西。上圖有三臺伺服器(Server),假設是一個叢集(叢集我們要求是奇數個)。每臺伺服器上分別執行著Raft演算法。其中有三個基本組成,分佈是一致性模組(Consensus Module)、日誌(log)、狀態機(State Machine)。下面先簡單大概說一下叢集是如何執行的,咱們先有個大概印象。
首先第一個問題是,這幾臺機器中都有資料,我們的目標是保證裡面儲存的資料都是一樣的,那假如其中有一臺資料不一樣,那麼到底應該以誰的為準?這就是選主問題,在Raft裡面,有一個Leader的伺服器,一切以Leader的資料為準。那麼誰當Leader呢?那就靠Raft中的選主演算法(Leader election)決定。
第二,為了保證客戶端對資料的每一個操作,都能真正地保證操作成功(或者告知不能操作成功),Raft依靠的還是兩階段提交方法。學過兩階段提交協議的同學可能會比較熟悉。其實很簡單。就像很多人去交電費。由於交電費的地方比較落後,因此那邊的工作人員對於來排隊繳費的人,操作流程是:首先對每一個來交電費的人進行登記,登記你要買多少電費,先記在本子上。然後到了晚上再統一將登記本上登記的電劃給使用者。在本文場景中,Raft首先將使用者對資料的操作,儲存在Log中。比如“將X賦值為3”這個操作先儲存起來,然後在統一將這個操作真正去執行,持久化在State Machine中。因此日誌是對於資料一致的關鍵保證。因此我們做資料副本,其實就是做日誌副本,需要考慮日誌怎麼樣才能安全複製到其他機器上去。這就是日誌複製(log replication)
而要對這一系列流程進行安全性保障,就需要一致性模組進行演算法上的控制。安全性保證(safety)
當然,由於叢集一般會不斷變化,比如機器壞了,比如機器配置需要升級等等,導致叢集的數量、配置等會發生變化。那麼如何保證在叢集變化的時候,也能使得一致性能夠得到保證呢?那麼就需要進行維護成員變化(membership changes)。
上面就是幾個小問題之間的聯絡。只要把上面幾個問題搞清楚了,那麼Raft也就搞清楚了。我們後面就分別對這幾個問題進行詳細說明。