分布式系統(tǒng)的一致性協(xié)議,Raft協(xié)議雖然說比paxos協(xié)議要簡單,但是要理解也是有困難的。經(jīng)過幾天反復(fù)看raft協(xié)議的論文,然后仔細(xì)研讀raft的代碼感覺自己掌握了raft協(xié)議,其中理解最困難的是日志復(fù)制安全的那個限制吧。
1.raft要解決的問題。raft要解決的是分布式集群的一致性問題,而不是一個完善的存儲系統(tǒng),比如客戶端當(dāng)時沒受到服務(wù)器返回,網(wǎng)絡(luò)通路就掛了,過了幾天還要服務(wù)器能自動給你返回,不是raft要解決的問題,這是由一個完善的系統(tǒng)的開發(fā)者要來解決的問題,不是raft的范圍,raft只能確認(rèn)你的請求被集群是否一致的接受,并且只要被提交確認(rèn)接受就不可在相同位置被更改,只解決這些問題,我們看raft自己的客戶端例子,他不解決返回異常的問題,所以讀者不要擴(kuò)大raft的解決范圍
2. 兩個rpc請求響應(yīng)對 都有個current term成員,這樣只要收到別人的current term比自己的current term大就更新自己的current term,比自己小拒絕這個投票,在下次自己發(fā)起投票的時候+1
3.選舉限制5.4.1這節(jié)說的比較的term號是日志的term號不是節(jié)點(diǎn)的current term號,并且比較的日志并不一定是提交過的日志,是自己和對方所有的日志比較,包括提交和未提交的
4.提交的限制5.4.2這個圖配上說明非常迷惑,其實(shí)他就是說如果leader的current term是4,你不能直接去提交自己的最大日志是term=2的那條日志,就算這個日志leader知道已經(jīng)被所有機(jī)器接受也不能提交這個日志。如果你提交該日志就可能被s5覆蓋,破壞raft的安全,當(dāng)然不提交也會被s5覆蓋,但raft只保證被提交日志的安全性;比current term小的日志term ?,Leader就不會去統(tǒng)計(jì)該日志是否被大多數(shù)節(jié)點(diǎn)接受。只有當(dāng)有個新日志并且其必須term=4被大多數(shù)節(jié)點(diǎn)接受,然后可以直接提交這個term=4的新日志;這樣可以導(dǎo)致之前的term=2的日志也被默認(rèn)提交了,這點(diǎn)用數(shù)學(xué)歸納法證明。
5.leader提交了某日志在下次心跳的時候會通知其他節(jié)點(diǎn)也去提交這個日志,然后緩慢的通知節(jié)點(diǎn)一條一條的去應(yīng)用日志
6.提交的日志,和應(yīng)用的日志都是不一定存盤的,也就是節(jié)點(diǎn)掛了,再啟動是有可能不知道自己提交了哪些日志,應(yīng)用了哪些日志,至少raft協(xié)議和我看到的實(shí)現(xiàn)是沒規(guī)定必須存盤
7.臟讀問題,也就是讀到了老版本的問題。如果集群出現(xiàn)了分區(qū),由于老leader被廢黜了自己卻不知道,是會出現(xiàn)該問題的,因?yàn)樽x是沒有日志的。解決這個問題,分為兩步,一是新leader上任要提交空白日志去確認(rèn)自己到底曾經(jīng)提交了哪些日志(前面說過提交狀態(tài)是不存盤的,還有可能是新leader以前是folloewer 還沒來的及提交日志)。二是需要走一次心跳rpc,看自己的leader權(quán)限是否被廢黜了。以上是一種分兩步的方法。
還有一種方法是依賴時序的lease機(jī)制,這個tikv里有對腦裂的臟讀問題詳細(xì)描述。
8.日志rpc的prevlogindex當(dāng)leader剛發(fā)出日志心跳的時候,就是等于自己的最大的日志索引,走過一次心跳后就為follower們的當(dāng)前日志索引或當(dāng)前日志索引之前的索引。本節(jié)點(diǎn)狀態(tài)的nextindex就是follower們的想獲得的日志索引,也就是follow的當(dāng)前日志索引+1。投票rpc中的lastlogindex就是candidater自己的最大索引。這樣索引的開始都是1而不是c語言用的0; leader發(fā)送的log entries是從nextindex發(fā)的,前面的日志是否存在只用比較prevlogindex prevlogterm一致就行了
9.客戶端判斷是否請求成功只用判斷l(xiāng)eader的該請求是否被提交就行了,不用去查詢大多數(shù)節(jié)點(diǎn),也不用判斷是否被應(yīng)用
10.應(yīng)用狀態(tài)機(jī)只是告訴用戶可以把commit index存盤,只是可以,不一定非要存盤。應(yīng)用狀態(tài)機(jī)還有個工作就是將配置正在更改的狀態(tài)清理。這個要及時,因?yàn)槲铱吹降膔aft是不會處于配置更改狀態(tài),然后繼續(xù)接受配置更改請求的
11.快照壓縮: ? ?1)各個節(jié)點(diǎn)自己獨(dú)立打快照,2)打的快照只能包含已經(jīng)提交的日志,3)快照中也要包含最后一次的配置,4)打完快照就可以丟棄之前的日志了,5)收到leader的快照丟棄自己的快照,6)follower收到leader的快照跟自己的commit index沖突也不要緊:因?yàn)閘eader最新,最牛逼,并且都是已經(jīng)提交了的日志打快照
12.配置更改,論文花了很大篇幅介紹配置更改的時候并不阻塞客戶端請求的方法。注意: 只要收到新配置該節(jié)點(diǎn)就立馬采用新配置,不管是否被提交
? ?(1)一次添加多個節(jié)點(diǎn)比較麻煩,先說一次添加一個節(jié)點(diǎn),我看的代碼就是這樣實(shí)現(xiàn)的,不會發(fā)生多個leader的情況,比如abcde 集群,e作為leader添加f 節(jié)點(diǎn),e立馬采用新配置感知了f的加入,這時候a想競爭leader: ?(ab(c)def)如果想產(chǎn)生兩個leader節(jié)點(diǎn)c 就要投兩票,集合有交叉,所以不能產(chǎn)生兩個leader。
? (2) 一次添加多個節(jié)點(diǎn)就比較麻煩了,開始是abc集群,現(xiàn)在添加de兩個節(jié)點(diǎn),就會發(fā)生ab可以投a作為leader,cde可以投c作為leader,解決這個問題分兩階段,第一階段有個共同一致性狀態(tài)Cold-new日志,提交這個日志前,集群想選leader必須獲得新老配置兩個各自的集群的大多數(shù)才能成功,提交日志也是必須獲得雙方的大多數(shù),這樣就不會產(chǎn)生兩個leader,提交后再全部所有都切換為新配置。注意的是新老集群的任一方不得單獨(dú)做決定,只有雙方都確認(rèn)了才能做決定。
在共同一致態(tài),raft的規(guī)則是于之前說的不同的,
? 1)日志被復(fù)制到所有節(jié)點(diǎn)
? 2)新老都可以成為leader
? ?3)共同一致態(tài)要同時獲得新老集群的大多數(shù)才能達(dá)成一致。
3個問題:
1)為了可用性,不要拖慢集群響應(yīng)速度,新節(jié)點(diǎn)日志比較老,就先不給其投票權(quán),但給其復(fù)制日志,等日志追上來了再給其投票權(quán)
2)leader是老集群的成員,leader自己被刪除了或則說leader自己不是新集群一份子,這樣leader只有在提交了Cnew后才下臺,這是種奇怪的狀態(tài):他的復(fù)制日志,自己也不把自己計(jì)算大多數(shù),只是復(fù)制提交日志。他提交完日志使命就結(jié)束了
3)被移除的pld集群的節(jié)點(diǎn)要競爭領(lǐng)導(dǎo),導(dǎo)致正常leader被退位,使用最小超時時間,在這個最小超時時間內(nèi)發(fā)起的投票直接拒絕,也不更新自己的current term;一個沒有節(jié)點(diǎn)記錄的選舉,直接拒絕它的投票,并提示其下機(jī)
13.分區(qū)后的節(jié)點(diǎn)重連進(jìn)集群,因?yàn)閠erm比集群的大會干擾集群導(dǎo)致重新選舉,而導(dǎo)致業(yè)務(wù)中斷,這種情況采用zk的pre-vote機(jī)制。
>源碼注釋