Raft論文讀書筆記

本文以問答的方式,總結Raft相關知識。
所有信息來源於Raft論文

第一章

1.爲什麼要尋找一個新的一致性算法?
Unfortunately, Paxos has two significant drawbacks.
The first drawback is that Paxos is exceptionally difficult to understand.
The second problem with Paxos is that it does not provide a good foundation for building practical implementations.
(1)paxos算法比較難理解,即便有很多人嘗試着用更容易理解的方式去解釋它。education
(2)paxos沒有爲實際實現打好基礎,有許多細節問題沒有公佈或者形成一致意見,以致於在實際構建和部署時,需要做複雜的變更。practice
Because of these problems, we concluded that Paxos does not provide a good foundation either
for system building or for education. Given the importance of consensus in large-scale software systems, we decided to see if we could design an alternative consensus algorithm with better properties than Paxos. Raft is the result of that experiment.

第二章

2.通常使用什麼方法來解決分佈式系統的容錯問題?舉例?
複製狀態機
Replicated state machines are used to solve a variety of fault tolerance problems in distributed systems, as described in Section 2.2.
Many large-scale storage systems that have a single cluster leader, such as GFS [30], HDFS [105], and RAMCloud [90], use this approach.

3.複製狀態機如何實現?架構?
使用replicated log實現
Replicated state machines are typically implemented using a replicated log, as shown in Figure 2.1.

這裏寫圖片描述
集羣中的每臺服務器上都有複製狀態機
每個狀態機按同樣的順序執行一串相同的命令,每個狀態機的狀態也是一致的。
一致性模塊接收客戶端命令並將其寫入到日誌中,每個服務器中的一致性模塊互相通信,保證每臺服務器上寫入日誌的命令內容和順序相同

4.paxos有什麼問題?
與第一個問題重複

第三章 Basic Raft algorithm

5.如何實現易懂性?
(1)將單個複雜的問題分解爲若干可以獨立解決,解釋,理解的問題。–問題的分解
we divided problems into separate pieces that could be solved, explained, and understood relatively independently.
(2)第二種方法是通過減少需要考慮的情況的數量來簡化狀態空間,使系統更加連貫並儘可能消除不確定性。–狀態簡化
Our second approach was to simplify the state space by reducing the number of states to consider, making the system more coherent and eliminating nondeterminism where possible.

6.簡單說一下Raft。概述。
Raft實現一致性的方法是,首先選出一個server作爲leader,讓leader來全權負責replicate log的管理(利用leader來簡化replicated log的管理)。leader從客戶端接收日誌條目,複製到其他服務器,並且告訴其他服務器什麼時候將日誌應用到狀態機上纔是安全的。
leader在決定將日誌條目放在日誌的哪個位置的時候,不需要和其他服務器商議。
數據流動的方向只會是從leader流向其他服務器。

有了在系統中使用leader的這個前提,Raft將一致性問題分解爲三個相對獨立的子問題:Leader election,Log replication,Safety。
(1)leader election
At any given time each server is in one of three states: leader, follower, or candidate.

通常只有一個leader,其餘的都是follower。
leader處理所有客戶端請求,follower將來自客戶端的請求轉發到leader。
follwoer只響應來自leader和candidate的請求。passive
candidate用來選舉新的leader

raft將時間分成任意長度的任期(term,連續的整數編號),每個term開始的時候都要進行選舉elect,一個或多個候選者試圖成爲leader,贏得選舉的server在這個任期的剩餘時間內作爲leader。某些情況下可能無法成功選出一個leader,所以一個term內最多隻有一個leader。
每個server都保存了當前term的數值,server之間通信時,會帶上term的信息。

我們選擇在Raft中將通信結構化爲RPC,以簡化其通信模式。
server使用RPC進行通信,基本的一致性算法只需要兩種RPC(RequestVote RPC和AppendEntries RPC),後續將要介紹的leader的資格轉換會使用其他的RPC。
RequestVote RPC:候選者在elect期間發出
AppendEntries RPC:leader發出的,用於複製日誌條目,和提供心跳機制。

Raft uses a heartbeat mechanism to trigger leader election.
Raft使用心跳機制觸發leader選舉:
server剛啓動時,先以follower作爲開始。leader會週期性的發送心跳信息到各個follower,維護自己作爲leader的地位。如果follower從leader或者candidate收到RPC,則保持follower狀態。如果follower在election timeout時間內沒有收到心跳信息,則認爲沒有leader了,follower發起新的選舉。

follower發起選舉的過程:
增加當前term –> 轉換爲candidate狀態 –> 投票給自己 –> 發送RequestVote RPC給集羣中其他服務器,會有三種結果:
(a)贏得選舉,成爲leader。(相同的term內獲得集羣中多數選票)(在同一個term中,每個服務器最多隻會投票給一個candidate,先收到誰的投票請求,就投票給誰。這保證了在同一個term中最多隻有一個leader。當candidate成爲leader之後會向集羣中其他server發送信息,確保自己的leader地位,避免其他server發起新的選舉。)safety
(b)收到其他server以leader身份發來的信息,轉變爲follower。如果收到其他server作爲leader發來的AppendEntries RPC信息,該信息中包含的term不小於自己的term,則承認其他server的leader地位,並轉換爲follower狀態。如果收到的RPC信息中的term小於自己的term,則拒絕該RPC信息,保持candidate狀態。(收到其他candidate發來的term大於自己的RequestVote RPC信息應該也會轉變爲follower吧???否則新一輪競選的時候,還是無法選出leader)
(c)在election timeout時間內,集羣中沒有選出新的leader。多個server同時轉變爲candidate發起競選,可能沒有一個candidate能贏得多數選票,則沒有新的leader選出來。這種情況下,每個candidate在elect timeout之後增加自己的term,發起新一輪的競選。(如果沒有額外的機制,這種分裂選票導致的無法選出leader的情況可能會不斷重複,解決辦法是,每個candidate的elect timeout不相同,取150-300ms中的隨機數,這樣每個candidate超時的時間不相同,發起競選的時間就會不同,最先發起競選的很可能成爲leader,在其他candidate超時之前,他就已經贏得選舉並且發送心跳信息了。網絡通信耗時一般在1ms以內,隨機數之間的時間差距,提供了充足的選舉時間。分裂選票的情況很少出現,並且能快速解決。)(曾經嘗試使用給每個server指定一個rank的方式來減少split vote,但是遇到了很多問題,沒成功。)We used randomization to simplify the Raft leader election algorithm.

(2)Log replication
leader選出來之後,就開始處理客戶端請求,客戶端請求中包含需要執行的命令,leader將命令作爲日誌條目追加到日誌中,然後並行發送給集羣中的其他服務器(AppendEntries RPC),日誌被安全的複製到其他服務器之後(判斷條件?committed!),leader將日誌應用到狀態機上,然後返回結果給客戶端。如果有follower crash,或者運行慢,或者網絡丟包的情況,leader會不斷髮送日誌條目直到所有follower都存儲了所有的日誌條目。
每個日誌條目都包含客戶端命令command,term,index。
index用來確定日誌條目在日誌中的位置。
log中的term用來檢測不同server上的日誌是否一致,以及確保一些算法的特性。

committed:A log entry is committed once the leader that created the entry has replicated it on a majority of the servers (e.g., entry 7 in Figure 3.5).多數服務器接收到日誌之後,日誌被提交,之前的所有日誌也都提交。(safety)
leader會記錄已經提交了的日誌條目的highest index,這個index也會包含在後續將要發送給follower的AppendEntries RPC(包括心跳)中,follower得知該日誌條目被提交之後,將該日誌條目應用到狀態機。

Raft的日誌機制:
(1)不同server上的日誌條目,如果term和index相同,則該條目中保存的命令也肯定相同。(leader在給定的term和index的位置,最多創建一個條目,並且日誌條目的位置(term&index)是不會改變的)—-當前的
(2)不同server上的日誌條目,如果term和index相同,則該日誌條目之前的所有的日誌條目都相同。(AppendEntries RPC一致性檢測,leader在發送AppendEntries RPC時,會將前一個entry的term和index也包含在其中。如果follower在自己的日誌中沒有找到對應的前一個entry,則拒絕新的entry。通過歸納法得知,如果leader發送的AppendEntries RPC返回成功的結果,則leader可以知道,截止到最新的entry,follower的日誌和自己的日誌是相同的。)—之前的

leader的crash可能導致leader和follower之間日誌的不一致,舊的leader可能有一些日誌條目沒有複製出去。follower的日誌可能比leader少,也可能比leader多。
(1)follower的日誌比leader少:
(2)follower的日誌比leader多:
(3)以上兩種都有

Raft如何讓follower的日誌和leader的相同?引入match index的概念。
Raft強制讓follower的日誌與leader相同,leader以此來解決日誌不一致的問題。這就意味着follower中與leader不一致的日誌會被覆蓋。通過在leader的競選過程中添加一些限制,可以保證這是安全的。(什麼限制?後面safety部分詳細描述。)
leader首先在follower的日誌中找到一個位置點match index,在這個點之前,leader和follower的日誌相同,然後在follower中刪除該點之後的所有的日誌條目,然後將leader中該點之後的所有日誌條目發送給follower,在此過程中AppendEntries RPC的一致性檢查也會起作用,以此來保證leader和follower的日誌相同。
leader中會維護每個follower的nextindex,nextindex是leader將要發送給follower的下個日誌條目的index。leader剛被選舉出來的時候,會將它自己的日誌中最後一個index的下一個index初始化爲所有follower的nextindex(nextindex=latest index+1),然後對於每一個follower,leader向其發送AppendEntries時,根據日誌一致性檢測機制,如果follower的日誌和leader的日誌不同(檢測term和index,follower.latest index=leader.follower.nextindex-1),則consistency check會失敗,此時leader將該follower的nextindex減小,並且重新發送AppendEntries,一直重複這個過程,直到consistency check成功。當follower的日誌和leader中記錄的nextindex能夠相匹配(nextindex=matchindex+1),然後follower中該index(matchindex)之後的日誌被刪除,leader發送follower缺少的日誌,follower和leader中的日誌最終達到一致。
一些優化手段,可能沒有必要,因爲failure不會經常發生,也不會有那麼多不一致的日誌:
(1)節省帶寬:在leader找到與follower的matchindex之前,可以不發送包含真正log entry的RPC,只發送包含心跳信息的RPC(應該包含term和index),節省帶寬。找到了matchindex之後,再發送log entry。
(2)減少尋找matchindex時AppendEntries RPC被拒絕的次數:
a.leader使用二分查找的方式,尋找follower日誌中第一個與之不同的index
b.follower在拒絕AppendEntries RPC時,將其日誌中當前term的第一個index返回給leader。當有多個term的日誌不一致時,每個term只需要一次比較,而不是將term中的每個log entry都進行比較。
leader不需要執行特別的動作去保持日誌的一致性,它永遠不會刪除或者覆蓋自己日誌中的條目。

Raft的日誌機制保持了不同server之間日誌的連貫性,這個機制不僅簡化了系統的行爲,使系統的行爲可預測,同時它也是保證安全性的一個重要組成部分。
在多數服務器正常運行的條件下,Raft就可以接受,複製,應用新的log entry。每個entry只需要一輪RPC即可複製到多數server上,單個慢的follower不影響整體性能。
日誌複製算法比較容易實現,因爲leader不會爲了趕進度而在單個AppendEntries request中發送超過一個entry。有的算法需要通過網絡發送整個日誌,真正實現需要很多優化,負擔比較大。

(3)Safety
可能會出現什麼問題?
之前的部分介紹了Raft如何選擇leader和複製日誌,但是沒有充分的保證每個狀態機按照同樣的順序執行同樣的命令。比如,一個follower落後leader很多,leader很多提交了的日誌並沒有複製到該follower上,但是該follower可能會被選爲leader,然後覆蓋掉原來的leader上面那些已經被提交的日誌,這可能導致不同的狀態機執行了不同的命令。新的leader缺少部分已提交的日誌。

如何解決這個問題?思路。
Raft算法對於leader的選舉添加了一些限制,以保證新的leader的日誌中包含所有之前已經提交的日誌。每次選新的leader,term的值都會增加,也就是說,新的leader的日誌中包含所有以前的term中已經提交了的日誌。

如何實現?
其他算法的實現:不包含所有已提交的日誌的candidate也能被選爲leader,在選舉的過程中或者稍後,通過額外的機制確定缺失的日誌,並傳輸到新選出的leader上面。增加了需要考慮的機制及複雜性。
Raft的實現:使用一個簡單的方法,保證被選出來的leader,包含之前term中已經提交的所有entry。由此,這意味着日誌流動的方向只有一個,即從leader流向follower。leader不需要刪除或覆蓋自己的日誌。
Raft在選舉的過程中避免了不包含全部已提交日誌的candidate成爲leader。Candidate只有獲得集羣中的多數voter的選票才能成爲leader,在這些voter至少會有一個包含所有已提交的entry。如果Candidate的日誌,至少和集羣中多數voter的日誌一樣新(與集羣中多數server中的日誌相比,相同或者更新),則其必然包含所有已提交了的日誌信息。

具體實現方式:
Candidate發送的RequestVote RPC中包含其log的信息(已提交日誌的term & index ,不是現有日誌的term & index),如果voter中的日誌比candidate中的更新,則voter不會投票給該candidate。——-一定要注意,是比較已提交日誌的term和index,比較未提交的日誌沒什麼意義。
如何比較兩個日誌誰更新?最後一個日誌條目的term和index。先比較term,再比較index。
Raft determines which of two logs is more up-to-date by comparing the index and term of the
last entries in the logs.

複製到多數server上的entry就一定提交了?
一個entry,即便被複制到了集羣中的多數機器上,但是如果leader在提交之前宕機,該entry仍有可能被刪除或覆蓋。
換句話說,新term中選出來的leader,無法判斷出上一個term中的entry是否已經提交,即便該entry已經複製到了集羣中的多數機器上。
圖3.7.舉例說明。(此情況比較複雜,但是解釋的很清楚,是必須要考慮到的情況。)

如何解決?
Raft不會使用計算副本數量的方式來提交前面term中的日誌。只有當前term中的日誌,才採用計算副本數量的方式來提交。—-不會直接提交
一旦當前term中的entry提交了,由於日誌匹配屬性(Log Matching Property)之前term中的entry也就被間接提交了。
有時候,leader可以安全的判斷出entry已經被提交了,但是爲了簡單起見,Raft採取更保守的方法。

安全性證明過程:
根據上面給出的完整的Raft算法,可以更詳細的證明Leader Completeness Property成立。(好麻煩)
先假設Leader Completeness Property不成立,然後證明這是矛盾的。
假設在term T中,leader提交了一個entry,但是這個entry沒有被複制到後來的Term U選出的leader中。
1.由於leader不會刪除或者覆蓋自己的日誌,故在leaderU競選leader時,其日誌中沒有該entry。
2.leaderT將該entry複製到了集羣中的多數server上。leaderU獲得了集羣中多數server的投票。因此,至少有一個server,既保存了leaderT提交的entry,又投票給了leaderU。—-這個voter是造成矛盾的關鍵點。
3.這個voter在投票給leaderU之前,必然已經接收了leaderT發來的提交了的entry。否則就是拒絕了leaderT發來的AppendEntries request(此時它的term大於T)。
4.voter依舊存儲了這個entry,因爲介於U和T之間的每一個leader都存儲了該entry。leader不會刪除或覆蓋自己的日誌。follower只刪除與leader相沖突的日誌。
5.voter投票給了leaderU ,所以leaderU的日誌和voter一樣內保持最新。這導致兩個矛盾。
6.首先,如果voter和leaderU有相同的term,那麼leaderU的日誌至少和voter的一樣新,所以leaderU的日誌包含了voter的log中的每個entry。這是矛盾的,因爲我們假設voter包含了已經提交的entry,但是leaderU不包含。
7.Otherwise, leaderU’s last log term must have been larger than the voter’s. Moreover, it was
larger than T, since the voter’s last log term was at least T (it contains the committed entry
from term T). The earlier leader that created leaderU’s last log entry must have contained the
committed entry in its log (by assumption). Then, by the Log Matching Property, leaderU’s
log must also contain the committed entry, which is a contradiction.
否則,leaderU的最後一個日誌term必須大於voter的日誌term。 此外,leaderU的term大於T,因爲voter的最後一個日誌term至少是T(它包含了來自termT的承諾條目)。 創建leaderU最後一個日誌條目的之前的leader必須在其日誌中包含這個已提交的條目(通過假設)。 然後,通過日誌匹配屬性,leaderU的日誌也必須包含這個已提交的條目,這是一個矛盾。
8.這就形成了矛盾。 因此,term大於T的leader,必然包含了termT期間所有提交的entry。
9.日誌匹配屬性保證了後來的leader必然包含之前間接提交的日誌條目。

這就證明了leader完整屬性,這也證明了狀態機安全屬性(如果一個server按照給的順序應用了log正的entry,則其他server會以同樣的順序執行日誌。每個server上的日誌是相同的。)
========================回頭再仔細看證明過程===============================

3.7 Follower and candidate crashes
follower和candidate的crash比leader的crash處理起來要簡單很多,它倆採用相同的處理方式。
follower和candidate crash之後,發送給他們的RequestVote和AppendEntries RPC信息會返回失敗的結果,Raft會不定期的重試發送這些信息,直到follower/candidate重啓,RPC返回成功的結果。
如果server在接收到RPC之後,還沒來得及響應就crash了,則在server重啓之後,會再次收到相同的RPC,但是這不會產生什麼危害。Raft RPCs have the same effect if repeated, so this causes no harm.如果follower收到與日誌中已經存在的entry相同的AppendEntries request,會忽略這些request中的entry。

3.8 Persisted state and server restarts
Raft服務器必須將足夠的信息持久化到穩定的存儲器中,以便安全地重新啓動服務器。(舉例:term,vote, entry)
特別是term和vote,這是爲了防止在一個term內投票兩次(vote),或者新的leader產生的日誌entry將以前的leader產生的log entry替換掉。
每個server中的log entry在提交之前必須持久化,避免server重啓後,提交了的entry丟失或者變爲未提交的狀態。

其他的信息可以重新創建,重啓不會引起問題。
舉例:commit index,即便所有server同時重啓也不會有問題。服務器重啓先將commit index初始化爲0,在leader選出之後提交新的log entry時,commit index會變成真實的值,並且將該值傳播到所有follower。(應該可以讀取自己的日誌,獲得真正的commit index)

狀態機可以是易失性的或持久性的。
易失性狀態機:最新快照+重新應用log entry
持久性狀態機:記錄last applied index,重啓之後應用該index之後的entry

如果服務器所有的持久化狀態都丟失了,則無法以其先前的身份安全地重新加入羣集。 這種服務器通常可以通過調用集羣成員資格更改的方式,以新的身份重新添加到集羣中。 但是,集羣中的多數服務器丟失了持久化狀態,則已經提交的日誌條目可能會丟失,集羣成員資格更改的也無法進行。如果要系統繼續運行,則系統管理員需要承認數據丟失的可能性。

3.9 Timing and availability
不管系統中的事件發生的是快還是慢,系統都不能產生不正確的結果。(安全性與時間無關)
但是可用性與時間有關。舉例:server間消息傳遞需要的時間,如果大於server crash的時間間隔,則無法選出可用的leader,沒有leader,Raft無法正常工作。

在Raft的leader選舉中,時間很重要。Raft能夠選出並且保持一個穩定的leader的時間要求:
broadcastTime << electionTimeout << MTBF
broadcastTime:Server發送RPC並接受響應的時間,要比election timeout至少小一個數量級,以便leader能夠可靠的發送心跳信息,防止follower開始新的選舉。結合election timeout的隨機方法,可以儘量避免split vote。
electionTimeout :超過該時間,如果follower沒有收到心跳信息的話,則發起新的elect。當leader crash時,系統大概有electionTimeout的時間是不可用的。
MTBF:單個server平均故障間隔時間

broadcastTime和MTBF都是與系統屬性,只有election timeout是我們必須設定的。
接收者需要將RPC中的信息持久化到存儲上,所以broadcastTime大概在0.5-2ms,根據系統存儲的性能而定。因此election timeout可以選擇在10–500ms之間。MTBF一般爲幾個月或更長時間。

3.10 Leadership transfer extension (可選的擴展)
兩種情況下需要用到leader轉換:
1.leader必須下臺(重啓維護或從集羣中移除)。leader下臺,會有election timeout的時間系統是不可用的,直到其他的server超時,發起新的選舉並贏得選舉。
在下臺之前,leader將其角色轉移到其他server,可以儘量避免這段系統不可用的時間。
2.其他server更適合作爲leader。舉例:負載高的服務器不適合作爲leader。廣域網中,主數據中心的服務區更適合作爲leader以儘可能減少客戶端和leader之間的網絡延遲。(其他算法可能在選舉階段就選出了更合適的leader,但是Raft由於要選舉包含最完整日誌的server作爲leader,被選舉出來的可能不是最優的server。Raft的leader會不時的去檢測follower中有沒有更適合作爲leader的server,並且將leader的角色轉給該server,人工操作。)

詳細的leadership transfer步驟:
1.原leader停止接受客戶端的新請求。
2.原leader將所有已提交的log entry傳輸到目標server,保證原leader和目標server日誌一致(正常的日誌複製機制,沒有其他特殊操作)。
3.原leader向目標server發送一個TimeoutNow的請求,這個請求可以使目標server立即發起一輪新的選舉,而不需要等待election timeout的時間。(目標server增加term的值,轉換爲candidate狀態)
這樣基本能保證目標server在其他server election timeout之前就發起新的election並且成爲leader。目標server發送給原leader的信息中也會包含新的term,使原leader下臺。此時,leadership transfer完成。

如何保證transfer異常的情況下儘快恢復響應客戶端請求?–只提供了思路,還沒有真正去實現這個功能。
如果leadership transfer這個操作在一個election timeout的時間內沒有完成,則原leader終止此操作,並重新接受客戶端請求,防止客戶端請求被長期阻塞。如果目標server實際上已經成爲leader,但是原leader依舊錯誤的終止了transfer的操作,則最壞的情況也就是多進行一次選舉,然後就又可以響應客戶端請求了。

3.11 Conclusion
本章解決了一致性相關的所有問題。Raft超越了single-decree Paxos中對於單一值實現一致性的機制,實現了不斷增長的日誌的一致性,這是建立複製狀態機所必須的。它將所有節點達成一致的消息發送到其他服務器,讓其他服務器知道log entry已經提交。Raft通過選擇一個集羣leader,由該leader單獨做決定,並由leader向集羣其他成員發送日誌信息的方式,高效的,可行的實現了一致性。
Raft僅使用少量機制來解決完全一致的問題。 例如,它只使用兩個RPC(RequestVote和AppendEntries)。 創建一個緊湊的算法/實現並不是Raft的最直接的目標,相反,這是設計易懂性的結果,每一個機制都必須充分激活和解釋。 我們發現冗餘或曲折機制很難激發,所以它在設計過程中自然會被清除。
對於不會影響大部分Raft部署的問題,我們沒有在Raft中解決它,所以Raft中有的部分可能不夠成熟(幼稚,比如leader選舉還可以改進,但是在增加複雜性的同時並沒有帶來多少實際的好處),有的又比較保守(比如leader只能提交當前term下生成的日誌)。爲了讓讀者更容易理解,放棄了一些不成熟的優化手段。
本章不可避免的會遺漏一些功能或優化,這些功能或優化在實踐中非常有用。 隨着實施者獲得更多的Raft經驗,他們將瞭解何時以及爲什麼某些附加功能可能會有用,並且他們可能需要在實際部署中實施這些功能。 在整個章節中,我們勾勒了一些我們認爲不必要的可選擴展,但如果需要的話,這可能有助於指導實施者。 通過關注可理解性,我們希望爲實施者根據他們的經驗調整Raft提供堅實的基礎。 由於Raft在我們的測試環境中工作,我們期望這些是簡單直接的擴展,而不是根本性的變化。

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