Raft成员变更的工程实践
簡(jiǎn)介: 成員變更是一致性系統(tǒng)實(shí)現(xiàn)繞不開(kāi)的難題,對(duì)于提升運(yùn)維能力以及服務(wù)可用性都有很大的幫助。 本文從Raft成員變更理論出發(fā),介紹了Raft成員變更和單步成員變更的問(wèn)題,其中包括Raft著名的Bug。 對(duì)于Raft成員變更的工程實(shí)現(xiàn)上需要考慮的問(wèn)題,本文給出了一些工程實(shí)踐經(jīng)驗(yàn)。
?
一 ?引言
?
成員變更是一致性系統(tǒng)實(shí)現(xiàn)繞不開(kāi)的難題,對(duì)于提升運(yùn)維能力以及服務(wù)可用性都有很大的幫助。
?
本文從Raft成員變更理論出發(fā),介紹了Raft成員變更和單步成員變更的問(wèn)題,其中包括Raft著名的Bug。
?
對(duì)于Raft成員變更的工程實(shí)現(xiàn)上需要考慮的問(wèn)題,本文給出了一些工程實(shí)踐經(jīng)驗(yàn)。
?
二 ?Raft成員變更簡(jiǎn)介
?
分布式系統(tǒng)運(yùn)行過(guò)程中節(jié)點(diǎn)經(jīng)常會(huì)出現(xiàn)故障,需要支持節(jié)點(diǎn)的動(dòng)態(tài)增加和刪除。
?
成員變更是在集群運(yùn)行過(guò)程中改變運(yùn)行一致性協(xié)議的節(jié)點(diǎn),如增加、減少節(jié)點(diǎn)、節(jié)點(diǎn)替換等。成員變更過(guò)程不能影響系統(tǒng)的可用性。
?
成員變更也是一個(gè)一致性問(wèn)題,即所有節(jié)點(diǎn)對(duì)新成員達(dá)成一致。但是成員變更又有其特殊性,因?yàn)樵诔蓡T變更的過(guò)程中,參與投票的成員會(huì)發(fā)生變化。
?
如果將成員變更當(dāng)成一般的一致性問(wèn)題,直接向Leader節(jié)點(diǎn)發(fā)送成員變更請(qǐng)求,Leader同步成員變更日志,達(dá)成多數(shù)派之后提交,各節(jié)點(diǎn)提交成員變更日志后從舊成員配置(Cold)切換到新成員配置(Cnew)。
?
因?yàn)楦鱾€(gè)節(jié)點(diǎn)提交成員變更日志的時(shí)刻可能不同,造成各個(gè)節(jié)點(diǎn)從舊成員配置(Cold)切換到新成員配置(Cnew)的時(shí)刻不同。可能在某一時(shí)刻出現(xiàn)Cold和Cnew中同時(shí)存在兩個(gè)不相交的多數(shù)派,進(jìn)而可能選出兩個(gè)Leader,形成不同的決議,破壞安全性。
?
圖1 成員變更的某一時(shí)刻Cold和Cnew中同時(shí)存在兩個(gè)不相交的多數(shù)派
?
如圖1是3個(gè)節(jié)點(diǎn)的集群擴(kuò)展到5個(gè)節(jié)點(diǎn)的集群,直接擴(kuò)展可能會(huì)造成Server1和Server2構(gòu)成老成員配置的多數(shù)派,Server3、Server4和Server5構(gòu)成新成員配置的多數(shù)派,兩者不相交從而可能導(dǎo)致決議沖突。
由于成員變更的這一特殊性,成員變更不能當(dāng)成一般的一致性問(wèn)題去解決。為了解決這個(gè)問(wèn)題,Raft提出了兩階段的成員變更方法Joint Consensus。
?
1 ?Joint Consensus成員變更
?
Joint Consensus成員變更讓集群先從舊成員配置Cold切換到一個(gè)過(guò)渡成員配置,稱為聯(lián)合一致成員配置(Joint Consensus),聯(lián)合一致成員配置是舊成員配置Cold和新成員配置Cnew ?的組合Cold,new,一旦聯(lián)合一致成員配置Cold,new提交,再切換到新成員配置Cnew??。
?
圖2 Joint Consensus成員變更
?
Leader收到成員變更請(qǐng)求后,先向Cold和Cnew同步一條Cold,new日志,此后所有日志都需要Cold和Cnew兩個(gè)多數(shù)派的確認(rèn)。Cold,new日志在Cold和Cnew都達(dá)成多數(shù)派之后才能提交,此后Leader再向Cold和Cnew同步一條只包含Cnew的日志,此后日志只需要Cnew的多數(shù)派確認(rèn)。Cnew日志只需要在Cnew達(dá)成多數(shù)派即可提交,此時(shí)成員變更完成,不在Cnew中的成員自動(dòng)下線。
?
成員變更過(guò)程中如果發(fā)生Failover,老Leader宕機(jī),Cold,new中任意一個(gè)節(jié)點(diǎn)都可能成為新Leader,如果新Leader上沒(méi)有Cold,new日志,則繼續(xù)使用Cold,Follower上如果有Cold,new日志會(huì)被新Leader截?cái)?#xff0c;回退到Cold,成員變更失敗;如果新Leader上有Cold,new日志,則繼續(xù)將未完成的成員變更流程走完。
?
Joint Consensus成員變更比較通用且容易理解,但是實(shí)現(xiàn)比較復(fù)雜,之所以分為兩個(gè)階段,是因?yàn)閷?duì)??與??的關(guān)系沒(méi)有做任何假設(shè),為了避免??和??各自形成不相交的多數(shù)派而選出兩個(gè)Leader,才引入了兩階段方案。
?
如果增強(qiáng)成員變更的限制,假設(shè)Cold與Cnew任意的多數(shù)派交集不為空,Cold與Cnew就無(wú)法各自形成多數(shù)派,則成員變更就可以簡(jiǎn)化為一階段。
?
2 ?單步成員變更
實(shí)現(xiàn)單步的成員變更,關(guān)鍵在于限制Cold與Cnew,使之任意的多數(shù)派交集不為空。方法就是每次成員變更只允許增加或刪除一個(gè)成員。
?
圖3 增加或刪除一個(gè)成員
?
增加或刪除一個(gè)成員時(shí)的情形,如圖3所示,可以從數(shù)學(xué)上嚴(yán)格證明,只要每次只允許增加或刪除一個(gè)成員,Cold與Cnew不可能形成兩個(gè)不相交的多數(shù)派。因此只要每次只增加或刪除一個(gè)成員,從Cold可直接切換到Cnew,無(wú)需過(guò)渡成員配置,實(shí)現(xiàn)單步成員變更。
?
單步成員變更一次只能變更一個(gè)成員,如果需要變更多個(gè)成員,可以通過(guò)執(zhí)行多次單步成員變更來(lái)實(shí)現(xiàn)。
?
單步成員變更理論雖然簡(jiǎn)單,但卻埋了很多坑,實(shí)際用起來(lái)并不是那么簡(jiǎn)單。
?
三 ?Raft單步成員變更的問(wèn)題
?
Raft單步成員變更的問(wèn)題,最著名的莫過(guò)于Raft著名的正確性問(wèn)題,另外單步成員變更還有潛在的可用性問(wèn)題。
?
1 ?Raft單步成員變更的正確性問(wèn)題
?
Raft單步變更過(guò)程中如果發(fā)生Leader切換會(huì)出現(xiàn)正確性問(wèn)題,可能導(dǎo)致已經(jīng)提交的日志又被覆蓋。Raft作者(Diego Ongaro)早在2015年就發(fā)現(xiàn)了這個(gè)問(wèn)題,并且在Raft-dev詳細(xì)的說(shuō)明了這個(gè)問(wèn)題[1]。
?
下面是一個(gè)Raft單步變更出問(wèn)題的例子, 初始成員配置是abcd這4節(jié)點(diǎn),節(jié)點(diǎn)u和V要加入集群, 如果中間出現(xiàn)Leader切換, 就會(huì)丟失已提交的日志:
?
圖4 Raft單步成員變更的正確性問(wèn)題
?
- t0:節(jié)點(diǎn)abcd的成員配置為C0;
- t1 :節(jié)點(diǎn)abcd在Term 0選出a為L(zhǎng)eader,b和c為Follower;
- t2:節(jié)點(diǎn)a同步成員變更日志Cu,只同步到a和u,未成功提交;
- t3:節(jié)點(diǎn)a宕機(jī);
- t4:節(jié)點(diǎn)d在Term 1被選為L(zhǎng)eader,b和c為Follower;
- t5:節(jié)點(diǎn)d同步成員變更日志Cv,同步到c、d、V,成功提交;
- t6:節(jié)點(diǎn)d同步普通日志E,同步到c、d、V,成功提交;
- t7:節(jié)點(diǎn)d宕機(jī);
- t8:節(jié)點(diǎn)a在Term 2重新選為L(zhǎng)eader,u和b為Follower;
- t9:節(jié)點(diǎn)a同步本地的日志Cu給所有人,造成已提交的Cv和E丟失。
?
為什么會(huì)出現(xiàn)這樣的問(wèn)題呢?根本原因是上一任Leader的成員變更日志還沒(méi)有同步到多數(shù)派就宕機(jī)了,新Leader一上任就進(jìn)行成員變更,使用新的成員配置提交日志,之前上一任Leader重新上任之后可能形成另外一個(gè)多數(shù)派集合,產(chǎn)生腦裂,將已提交的日志覆蓋,造成數(shù)據(jù)丟失。
?
Raft作者在發(fā)現(xiàn)這個(gè)問(wèn)題之后,也給出了修復(fù)方法。修復(fù)方法很簡(jiǎn)單, 跟Raft的日志Commit條件類(lèi)似:新任Leader必須在當(dāng)前Term提交一條日志之后,才允許同步成員變更日志。也即Leader在當(dāng)前Term還未提交日志之前,不允許同步成員變更日志。
?
按照這個(gè)修復(fù)方法,最簡(jiǎn)單的實(shí)現(xiàn)就是Leader上任后先提交一條no-op日志,然后再同步成員變更日志。這條no-op日志可以保證跟上一任Leader未提交的成員變更日志至少有一個(gè)節(jié)點(diǎn)交集,這樣可以發(fā)現(xiàn)上一任Leader的日志是舊的,從而阻止上一任Leader重新選為L(zhǎng)eader,進(jìn)而阻止了腦裂的產(chǎn)生。
?
對(duì)應(yīng)上面這個(gè)例子,就是L1當(dāng)選Leader后必須先提交一條no-op日志,然后才能開(kāi)始同步Cv和E,以便能發(fā)現(xiàn)L2的日志是舊的,從而阻止L2當(dāng)選Leader。
?
另一種方法是使用Joint Consensus成員變更,沒(méi)有這樣的正確性問(wèn)題。
?
2 ?Raft單步成員變更的可用性問(wèn)題
?
單步成員變更每次只能增加或者減少一個(gè)成員,在做成員替換的時(shí)候需要分兩次變更,第一次變更先將新成員加入進(jìn)來(lái),第二次變更再將老成員刪除,中間如果如果網(wǎng)絡(luò)分區(qū),有可能會(huì)導(dǎo)致服務(wù)不可用。
考慮a、b、c三個(gè)成員部署在三個(gè)機(jī)房,現(xiàn)在因?yàn)閍發(fā)生故障要將a替換為同機(jī)房的d。按照單步成員變更,abc要先變?yōu)閍bcd,再變?yōu)閎cd。
?
中間經(jīng)歷的4節(jié)點(diǎn)abcd的狀態(tài), 有可能在出現(xiàn)二分的網(wǎng)絡(luò)分區(qū)(ad|bc)時(shí)導(dǎo)致整個(gè)集群不可用。因?yàn)閍與d位于同一機(jī)房,這種二分網(wǎng)絡(luò)分區(qū)的情況在實(shí)際情況中還是不容忽視的。
?
?
怎么解決這個(gè)問(wèn)題呢?一種方法是做成員替換的時(shí)候,先刪除老成員,再加入新成員,即abc先變?yōu)閎c,再變?yōu)閎cd,這樣可以避免abcd的狀態(tài)。
?
?
另一種方法是使用Joint Consensus成員變更,abc先變?yōu)閍bc U bcd ?,再變?yōu)閎cd,也不會(huì)經(jīng)歷abcd的狀態(tài)。
?
四 ?Raft成員變更的工程實(shí)踐
?
Raft成員變更的理論雖簡(jiǎn)單,但實(shí)際工程實(shí)現(xiàn)上還是有很多地方要考慮。因?yàn)镽aft單步成員變更有正確性問(wèn)題及可用性問(wèn)題,工程上建議盡量使用Joint Consensus成員變更,這里主要討論一些Joint Consensus成員變更工程實(shí)現(xiàn)上必須考慮的問(wèn)題。
?
1 ?新成員先加入再同步數(shù)據(jù)還是先同步數(shù)據(jù)再加入
?
因?yàn)镽aft需要嚴(yán)格保證順序,而新成員上還沒(méi)有任何數(shù)據(jù),因此新成員加入集群后需要先同步數(shù)據(jù)才能正常工作。工程實(shí)現(xiàn)時(shí)就有兩種選擇,一種是讓新成員先加入再同步數(shù)據(jù),另一種是先給新成員同步數(shù)據(jù),同步完成后再加入。這兩種方式各有利弊。
表1 新成員先加入再同步數(shù)據(jù)和先同步數(shù)據(jù)再加入的優(yōu)缺點(diǎn)
?
新成員先加入再同步數(shù)據(jù),成員變更可以立即完成,并且因?yàn)橹灰蠖鄶?shù)成員同意即可加入,甚至可以加入還不存在的成員,加入后再慢慢同步數(shù)據(jù)。但在數(shù)據(jù)同步完成之前新成員無(wú)法服務(wù),但新成員的加入可能讓多數(shù)派集合增大,而新成員暫時(shí)又無(wú)法服務(wù),此時(shí)如果有成員發(fā)生Failover,很可能導(dǎo)致無(wú)法滿足多數(shù)成員存活的條件,讓服務(wù)不可用。因此新成員先加入再同步數(shù)據(jù),簡(jiǎn)化了成員變更,但可能降低服務(wù)的可用性。
?
新成員先同步數(shù)據(jù)再加入,成員變更需要后臺(tái)異步進(jìn)行,先將新成員作為L(zhǎng)earner角色加入,只能同步數(shù)據(jù),不具有投票權(quán),不會(huì)增加多數(shù)派集合,等數(shù)據(jù)同步完成后再讓新成員正式加入,正式加入后可立即開(kāi)始工作,不影響服務(wù)可用性。因此新成員先同步數(shù)據(jù)再加入,不影響服務(wù)的可用性,但成員變更流程復(fù)雜,并且因?yàn)橐冉o新成員同步數(shù)據(jù),不能加入還不存在的成員。
?
2 ?成員變更日志使用什么配置
?
成員變更日志本身是為了改變成員配置,處在成員配置變更的臨界點(diǎn)上,因此成員變更日志使用什么配置就很關(guān)鍵。
?
表2 Joint Consensus成員變更日志使用的成員配置
?
對(duì)于Joint Consensus成員變更,成員變更日志使用什么配置是確定的。Cold,new日志使用聯(lián)合一致成員配置Cold,new,需要老成員配置Cold和新成員配置Cnew兩個(gè)多數(shù)派確認(rèn)才能提交,Cnew日志使用新成員配置Cnew,只需要新成員配置Cnew的多數(shù)派確認(rèn)即可提交,但Cnew日志也會(huì)同步給老成員配置Cold,主要是為了讓Cold中不在Cnew中的成員自動(dòng)退出。
?
3 ?成員變更日志什么時(shí)候生效
?
成員變更通過(guò)成員變更日志來(lái)完成,讓各成員對(duì)成員配置達(dá)成一致,但成員變更日志與普通日志不同,并不一定要等到提交后Apply生效。
?
表3 成員變更日志的生效時(shí)機(jī)
?
對(duì)于Joint Consensus成員變更,成員變更日志什么時(shí)候生效是確定的。在Leader上開(kāi)始同步成員變更日志之前就需要生效,在Follower上成員變更日志持久化完成后就需要生效。成員變更日志還未提交就先生效了,因此在Leader切換后可能會(huì)回滾。
?
4 ?成員變更期間日志是否需要嚴(yán)格按序提交
?
考慮這樣一種情況,成員變更減少了成員數(shù)量,進(jìn)而減小了多數(shù)派集合,而更小的多數(shù)派更容易達(dá)成,造成成員變更之后的日志比之前的日志先達(dá)成多數(shù)派。
?
按照Raft論文中的commitIndex的推進(jìn)算法:
?
If there exists an N such that N > commitIndex, a majority of matchIndex[i] ≥ N, and log[N].term == currentTerm:
?
set commitIndex = N
?
一條日志達(dá)成多數(shù)派就往前推進(jìn)commitIndex至該日志,如果該日志之前有日志按照老成員配置還未達(dá)成多數(shù)派,也一并提交了。
?
這種情況是否會(huì)出問(wèn)題呢?實(shí)際上并不會(huì),因?yàn)槌蓡T變更之后,已經(jīng)有日志使用新成員配置提交了,不在新成員配置中的節(jié)點(diǎn)不可能再當(dāng)選Leader了,進(jìn)而不會(huì)覆蓋之前的日志,因此就算之前的日志按照老成員配置未達(dá)成多數(shù)派也可以安全的提交。
?
hashicorp raft的實(shí)現(xiàn)還是嚴(yán)格按序提交的,即只有前面的日志都達(dá)成多數(shù)派之后才能提交。
?
5 ?只有少數(shù)成員存活時(shí)怎么恢復(fù)服務(wù)
?
Raft只能在大多數(shù)成員存活的情況下才能正常工作,實(shí)際可能會(huì)遇到只有少數(shù)成員存活的情況,這個(gè)時(shí)候要怎么恢復(fù)服務(wù)呢。
?
因?yàn)橹挥猩贁?shù)成員存活,已經(jīng)不能達(dá)成多數(shù)派,不能寫(xiě)入數(shù)據(jù),也不能做正常的成員變更。需要提供一個(gè)強(qiáng)制更改成員配置的接口,通過(guò)它設(shè)置每個(gè)成員的成員配置列表,便于從大多數(shù)成員故障中恢復(fù)。
?
比如只剩一個(gè)成員S1存活的時(shí)候,強(qiáng)制更改成員配置設(shè)置成員列表為{S1},這樣形成一個(gè)只有S1的成員列表,讓S1繼續(xù)提供讀寫(xiě)服務(wù),后續(xù)再調(diào)度其他節(jié)點(diǎn)通過(guò)成員變更加入。通過(guò)強(qiáng)制修改成員列表,可以實(shí)現(xiàn)最大可用模式。
?
五 ?單步成員變更的工程實(shí)踐
?
單步成員變更雖然不推薦在工程中使用,這里還是總結(jié)一下單步成員變更的一些工程實(shí)踐,供研究討論。
?
1 ?單步成員變更日志使用什么配置
?
對(duì)于單步成員變更,成員變更日志是使用新成員配置 還是老成員配置Cnew呢?實(shí)際上單步成員變更日志無(wú)論使用新成員配置Cold還是老成員配置Cnew都不會(huì)破壞Cold與Cnew的多數(shù)派至少有一個(gè)節(jié)點(diǎn)相交,因此單步成員變更日志既可以使用新成員配置Cold也可以使用老成員配置Cnew,兩種方式各有利弊。
?
表4 單步成員變更日志使用老成員配置和使用新成員配置的優(yōu)缺點(diǎn)
?
單步成員變更日志使用老成員配置Cold,可以避免單步成員變更的正確性問(wèn)題,因此可以省略掉Leader上任后的no-op日志,同時(shí)在增加成員時(shí)可能只需要更小的多數(shù)派集合,但在減少成員時(shí)可能需要更大的多數(shù)派集合。
?
單步成員變更日志使用新成員配置Cnew,需要Leader上任后先提交一條no-op日志,以避免單步成員變更的正確性問(wèn)題,同時(shí)在減少成員時(shí)可能只需要更小的多數(shù)派集合,但在增加成員時(shí)可能需要更大的多數(shù)派集合。
?
單步成員變更日志不管使用新成員配置還是老成員配置,最好都同步給新老成員配置中的所有成員,這樣在增加成員時(shí)可以讓新成員遲早收到通知,在減少成員時(shí)也可以讓被刪除的成員收到通知而自動(dòng)退出。
?
Raft論文中單步成員變更日志使用新成員配置Cnew,etcd中單步成員變更日志使用老成員配置Cold。
?
2 ?單步成員變更日志什么時(shí)候生效
?
表5 單步成員變更日志的生效時(shí)機(jī)
?
對(duì)于單步成員變更,如果成員變更日志使用新成員配置,則與Joint Consensus成員變更一樣,Leader上開(kāi)始同步成員變更日志之前就需要生效,在Follower上成員變更日志持久化完成后就需要生效。如果成員變更日志使用老成員配置,理論上只需要在下一次成員變更開(kāi)始之前生效即可,但實(shí)際為了讓新加入的節(jié)點(diǎn)盡快開(kāi)始服務(wù),一般在成員變更日志提交后就生效。
?
Raft論文中單步成員變更日志使用新成員配置Cnew,本地持久化完成就生效;etcd中單步成員變更日志使用老成員配置Cold,提交后再生效。
?
六 ?總結(jié)
?
Raft提供了Joint Consensus成員變更和單步成員變更,極大的推動(dòng)了成員變更在工程中的應(yīng)用。本文總結(jié)了一些Raft單步成員變更的問(wèn)題,以及成員變更的工程實(shí)踐。Joint Consensus通用并且不容易踩坑,一階段成員變更坑比較多。工程上建議盡量使用Joint Consensus成員變更。
原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的Raft成员变更的工程实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Flink 源码 | 自定义 Forma
- 下一篇: 好物推荐|下载超过 23w 次的 IDE