0. 寫在前面的話
一直從事分布式對象存儲工作,在分布式對象存儲的運營,開發等工作中,數據一致性是至關重要的。因此想寫一篇關於分布式一致性的文章。一來,可以和大家分享。二來,可以提高自己的文字提煉能力也可以當作備忘。
本篇文章並不是raft的一篇科普文,不着重介紹raft的具體過程,這些具體過程raft論文中都詳細闡述,在此不再贅述,而是着重於raft中選舉以及日志復制過程如何保證數據的一致性的闡述。
#如果對raft不了解的同學,建議搜索raft論文譯文+原文對照看
#分享的都是個人體會和思考的過程可能不是很嚴謹
#不求全,可能有些細節會被省略,但求能夠把raft最主要的東西說清楚
1. 什么是分布式一致性協議
#分布式:多個節點互為副本,副本之間可以是平等關系也可以是主從關系
#一致性:保證各個節點上的數據,只要是提交的數據一定是保證一致的
2. 為什么需要分布式一致性協議
在分布式對象存儲系統中,數據的安全性是通過多份冗余副本的保證的,這就要求系統能夠保證多份副本上的數據一致,比如對於對象存儲系統的master管理着整個集群的副本關系,block狀態等元數據信息。為了防止master單點,一般會采取一主多從的方式,而在存儲系統中由於故障導致的數據遷移,空間回收等場景都會導致元數據發生改變,這些元數據的改變如何同步到其他備機上對提升整個分布式的數據安全性有着至關重要的作用。
3. raft是如何做到保證數據一致性
raft作為一種比較好理解的分布式一致性算法(相對於paxos來說啦,其實要理解還是有點難度的!),是通過選舉機制,日志復制來保證系統數據的一致性
筆者認為,raft協議理解的難度並不在於raft定義的那些操作,而是在於raft定義的操作和raft系統遵守的原則之間的關系,以及為什么系統遵循這些原則就能保證數據的一致性呢?所以剛開始了解raft協議時給人的感覺就是比較散亂而不清晰。
因此下面將會着重試着從raft操作是如何保證系統始終遵守這些原則之間,以及這些原則為什么能保證數據的一致性來闡述
#raft基本概念:
提交的日志索引(commitIdx):已經同步至大部分節點的log的下標,表明數據是安全狀態,是不會再被修改的日志數據。
任期(term):相當於系統的邏輯時鍾,每一個任期中只能有一個leader,可以理解為系統每進入一輪選舉時任期+1。可以把任期想象成一個朝代,一朝天子一朝臣,leader就是這朝的皇帝,從節點就是這朝的臣子。term是理解raft的一個關鍵點。
日志索引(logIdx):raft日志索引是一個的下標連續&單調遞增的,例如(1,2,3,4,5,..,n,n+1)。
狀態機執行日志索引(applyIdx):發送給狀態機(是個抽象的概念可以是對象存儲系統,也可以是其他的系統)執行的日志下標。
#raft基本操作
#選舉:
功能描述:
請求其他節點為自己競選leader投票,超過半數節點投票給自己就會轉化成leader節點
發起方:
角色:候選者
操作名稱:RequestVote
操作參數:
#當前最新的日志條目索引(當前日志的term+logIdx)【規則b會用到】
#當前的term號 【規則a會用到】
#自身的ID,用於告訴選民我是誰
響應方:
角色:所有
響應規則:
a.請求方term大於自身的term。(小於的話,就好比生在新中國的我們來了個古代人來參選主席,你說你會答應嗎?)
b.請求方的日志條目包含自身的日志條目。(通過筆記兩方的日志條目索引來判斷,后面會介紹為什么這種判斷可以保證)。對方知道的東西還不如你多,說要當你領導你干嗎?)
c.在同一個任期內只能投一次票,多個競選者按照先來先得的投票原則。
在上面a,b,c條件都滿足的情況下,會投出神聖的一票給對方,否則不投。
#日志同步:
發起方:
功能描述:
#leader把客戶端寫到leader的日志的條目復制給從節點
· #在leader上任時會進行一次主從數據的同步(可以理解為當權者掌權后清除異己,主把從上和自己不同的記錄都給刪掉,直到保持一致!)
#充當心跳報文,維持leader的存在,抑制從節點進入競選。(leader刷存在感的方式)
發起方:
角色:leader
操作名稱:appendEntrtries
操作參數:
#當前任期:term
#entries[]:日志數據數組,記錄將要復制給從節點的日志條目
#prevLogIndex / prevLogTerm:entries[0]日志的前一個日志對應的logIdx / prevLogIndex對應日志條目的任期號(很多raft介紹文章是:最新日志之前的日志的索引值,但是本人參考raft原文以及邏輯推理覺的並不是最新日志之前)
#leaderId
#CommitIdx:leader上已經提交的日志索引
內部維護的數據結構:
#nextIndex[]:維護每一個從節點下一次需要復制的日志條目索引數組
響應方:
角色:非leader角色
響應規則:
a.進行term檢查,如果term小於自身,拒絕更新日志,直接返回False。(上一屆領導的命令肯定不能聽)
b.進行一致性檢測:如果在prevLogIndex
處的日志的任期號與prevLogTerm
不匹配時,返回 false
c.如果一條已經存在的日志與新的沖突(index 相同但是任期號 term 不同),則刪除已經存在的日志和它之后所有的日志。
d.添加任何在已有的日志中不存在的條目
e.更新commitidx,如果主的commitidx>自身的commitidx,則 自身的commitidx = 主的commitidx。(本人認為在實際raft系統中由於滿足領導人完全原則所以不會存在從的commitidx>主的commitidx情況)
#raft遵循的原則
#領導人只增加原則:領導人永遠不會覆蓋或者刪除自己的日志,它只會增加條目
#領導人完全原則:再一個任期提交的日志一定,出現在任期更大的領導人日志中。
#選舉安全原則:一個任期內最多只能有一個leader
#日志匹配原則:如果兩個日志在相同的索引位置上的日志條目的任期號相同,那么我們就認為這個日志從頭到這個索引位置之間的條目完全相同
#狀態機安全原則:如果一個服務器已經將給定索引位置的日志條目應用到狀態機中,則所有其他服務器不會在該索引位置應用不同的條目
#raft和遵循原則的之間的因果關系
#領導人只增加原則:
raft系統中日志的修改來自兩方面,1.appendEntrtries,根據appendEntrtries的響應描述可知,日志可以append,刪除修改。2,客戶端日志寫入,是append操作
在raft的appendEntrtries操作定義中響應方是非leader,所以leader只能介紹客戶段的append日志操作-->原則得證
#選舉安全原則:
在選舉操作響應規則c中規定在同一個任期內只能投一次票
證明:同一個任期內只能投一次票:則這個任期中的投票次數和節點個數相等的(2N+1),其中大多數票投個A,成為leader,B就不能得到大多數的投票。
#領導人完全原則:
在選舉的操作中,響應規則b,要求候選者的日志條目包含自身的日志條目,才可以投票給該候選者。
一個候選者得到大多數的節點的投票才能成為leader -->leader的日志能夠包含大多數節點(Node_Majority_set)的日志條目,並且當前leader的任期一定大於Node_Majority_set日志中的term。
證明:
反證法:假設存在一條日志已經提交了但是在Node_Majority_set_1中不存在節點包含這條記錄
由於已經提交的日志是已經存在於大多數節點(Node_Majority_set_2)中的日志,Node_Majority_set_1&Node_Majority_set_2 != NULL,因此必定存在節點包含這條記錄,所以得出矛盾,結論正確
因此已經提交的日志條目一定包含在Node_Majority_set_1的節點中(不一定全包含,可能是部分節點),而前面的論證leader的日志能夠包含大多數節點的日志條目(節點已提交的日志條目是所有日志條目的子集),所以新leader的日志一定包含所有已經提交的日志條目。
#日志匹配原則:
命題:如果兩個日志在相同的索引位置上的日志條目的任期號相同-->該日志索引處前面的索引上對應的日志條目完全相同
根據appendEntrtries響應規則中b的描述:在在prevLogIndex
處的日志的任期號與prevLogTerm
不匹配時,返回 false
歸納證明:初始化時所有的節點的在LogIdx=0處的任期號自然相同。
當LogIdx=N處的任期號相同時,appendEntrtries leader同步復制 LogIdx = N+1的日志時,會比對LogIdx=N的進行任期相同的比較,如果相同會寫入 LogIdx = N+1的日志條目,由於所有的從節點復制來自同一個主節點所以任期相同
因此歸納證明可得結論
#狀態機安全原則:
首先發送給狀態機的日志必須是已經提交的日志,如果一個日志log1已經提交,那么在該日志的索引位置處不會存在另一條log2已經提交但是和該日志條目不一樣的日志
假設存在log2已經被提交,說明在log2處的索引的日志在大多數節點保持一致,同理log1在log1索引處的日志條目在大多多數節點保持一致。
(log1_idx == log2_idx) 所以必然得到 log2 == log1
#這些原則為什么能保證數據的一致性
# 領導人只增加原則+選舉安全原則:保證raft系統中最多只有一個leader,並且日志復制只從leader單向流動到從節點(個人任務系統命名為raft中文漂流,是不是就是因為日志復制是單向的流動的呢)。
# 領導人完全原則:能夠保證新的leader中包含所有已經提交的日志,已經提交的日志是不會再修改的,從而保證新的leader產生也不會對已經提交的日志產生修改操作。
#根據日志匹配原則可以保證如果兩個日志在相同的索引位置上的日志條目的任期號相同-->該日志索引處前面的索引上對應的日志條目完全相同:
在appendEntrtries操作的響應規則c規定如果一條已經存在的日志與新的沖突(index 相同但是任期號 term 不同),則刪除已經存在的日志和它之后所有的日志。然后復制eader的同步的日志條目,和leader保持一致。
如果不沖突:根據日志匹配原則可以判斷前面的日志一定也是和leader保持一致的,把新的日志條目添加后,和leader保持一致。
綜上所述:這些原則能夠保證系統中最多只存在一個leader,而且leader包含之前任期的所有已提交的日志條目,日志條目只從leader流向從節點,在主從日志同步階段能夠保證日志的一致。