Raft 集群成员变更、日志压缩、客户端交互
Raft 集群成員變更、日志壓縮、客戶端交互
集群成員變更
在集群服務(wù)器發(fā)生變化時(shí),不能一次性的把所有的服務(wù)器配置信息從老的替換為新的,因?yàn)?#xff0c;每臺(tái)服務(wù)器的替換進(jìn)度是不一樣的,可能會(huì)導(dǎo)致出現(xiàn)雙主的情況,如下圖:
如上圖,Server 1和Server 2可能以 Cold 配置選出一個(gè)主,而Server 3,Server 4和Server 5可能以 Cnew 選出另外一個(gè)主,導(dǎo)致出現(xiàn)雙主。
raft使用兩階段的過程來完成上述轉(zhuǎn)換:
- 第一階段,新老配置都存在,稱為joint consensus
第二階段,替換成新配置
領(lǐng)導(dǎo)者首先創(chuàng)建Cold,new的log entry,然后提交(保證大多數(shù)的old和大多數(shù)的new都接收到該log entry);
領(lǐng)導(dǎo)者創(chuàng)建Cnew的log entry,然后提交,保證大多數(shù)的new都接收到了該log entry。
這個(gè)過程中,有幾個(gè)問題需要考慮。
- 新加入的server一開始沒有存儲(chǔ)任何的log entry,當(dāng)它們加入到集群中,可能有很長(zhǎng)一段時(shí)間在追加日志的過程中,導(dǎo)致配置變更的log entry一直無法提交
Raft為此新增了一個(gè)階段,此階段新的server不作為選舉的server,但是會(huì)從leader接受日志,當(dāng)新加的server追上leader時(shí),才開始做配置變更。
- 原來的主可能不在新的配置中
在這種場(chǎng)景下,原來的主在提交了Cnew log entry(計(jì)算日志副本個(gè)數(shù)時(shí),不包含自己)后,會(huì)變成follower狀態(tài)。
- 移除的server可能會(huì)干擾新的集群
移除的server不會(huì)受到新的leader的心跳,從而導(dǎo)致它們election timeout,然后重新開始選舉,這會(huì)導(dǎo)致新的leader變成follower狀態(tài)。Raft的解決方案是,當(dāng)一臺(tái)server接收到選舉RPC時(shí),如果此次接收到的時(shí)間跟leader發(fā)的心跳的時(shí)間間隔不超過最小的electionTimeout,則會(huì)拒絕掉此次選舉。這個(gè)不會(huì)影響正常的選舉過程,因?yàn)?#xff0c;每個(gè)server會(huì)在最小electionTimeout后發(fā)起選舉,而可以避免老的server的干擾。
日志壓縮
Raft的日志會(huì)隨著處理客戶端請(qǐng)求數(shù)量的增多而不斷增大,在實(shí)際系統(tǒng)中,日志不可能會(huì)無限地增長(zhǎng),原因如下:
- 占用的存儲(chǔ)空間隨著日志增多而增加
- 日志越多,server當(dāng)?shù)糁貑r(shí)需要回放的時(shí)間就越長(zhǎng)
因此,需要定期地清理日志,Raft采用最簡(jiǎn)單的快照方法。對(duì)系統(tǒng)當(dāng)前做快照時(shí),會(huì)把當(dāng)前狀態(tài)持久化到存儲(chǔ)中,然后到快照點(diǎn)的日志項(xiàng)都可以被刪除。
Raft算法中每個(gè)服務(wù)器單獨(dú)地做快照,即把當(dāng)前狀態(tài)機(jī)的狀態(tài)寫入到存儲(chǔ)中(狀態(tài)機(jī)中的狀態(tài)都是已提交的log entry回放出來的)。除了狀態(tài)機(jī)的狀態(tài)外,Raft快照中還需要一些元數(shù)據(jù)信息,包括如下:
快照中包含的最后一個(gè)日志條目的索引值和任期號(hào),記錄這些信息的目的是為了使得AppendEntriesRPC的一致性檢查能通過,因?yàn)?#xff0c;在復(fù)制緊跟著快照后的日志條目時(shí),AppendEntries RPC帶上需要復(fù)制的日志條目前一個(gè)日志條目的(索引值,任期號(hào)),即快照的最后一個(gè)日志條目的(索引值,任期號(hào)),因此,快照中需要記錄最后一個(gè)日志條目的(索引值,任期號(hào))
為了支持集群成員變更,快照中保存的元數(shù)據(jù)還會(huì)存儲(chǔ)集群最新的配置信息。
當(dāng)服務(wù)器完成快照后,可以刪除快照最后一個(gè)日志條目及其之前所有的日志條目,以及之前的快照。
雖然每個(gè)服務(wù)器是獨(dú)立地做快照的,但是也有可能存在需要領(lǐng)導(dǎo)者向跟隨者發(fā)送整個(gè)快照的情況,例如,一個(gè)跟隨者的日志處于領(lǐng)導(dǎo)者的最近一次快照之前,恰好領(lǐng)導(dǎo)者做完快照之后把其快照中的日志條目都刪除了,這時(shí),領(lǐng)導(dǎo)者就無法通過發(fā)送日志條目來同步了,只能通過發(fā)送完整快照。
領(lǐng)導(dǎo)者通過 InstallSnapshot RPC來完成發(fā)送快照的功能,跟隨者收到此RPC后,根據(jù)不同情況會(huì)有不同的處理:
當(dāng)follower中缺失快照中的日志時(shí)
- follower會(huì)刪除掉其上所有日志,并清空狀態(tài)機(jī)
當(dāng)follower中擁有快照中所有的日志時(shí)
- follower會(huì)刪掉快照所覆蓋的log entry,但快照后所有日志都保留。備注:這里論文中沒有提是否還是從leader接受快照,個(gè)人覺得follower可以自己做快照,并拒絕掉leader發(fā)快照的RPC請(qǐng)求
對(duì)于Raft快照,關(guān)于性能需要考慮的點(diǎn)有:
server何時(shí)做快照,太頻繁地做快照會(huì)浪費(fèi)磁盤I/O;太不頻繁會(huì)導(dǎo)致server當(dāng)?shù)艉蠡胤艜r(shí)間增加,可能的方案為當(dāng)日志大小到一定空間時(shí),開始快照。備注:如果所有server做快照的閾值空間都是一樣的,那么快照點(diǎn)也不一定相同,因?yàn)?#xff0c;當(dāng)server檢測(cè)到日志超過大小,到其真正開始做快照中間還存在時(shí)間間隔,每個(gè)server的間隔可能不一樣
寫快照花費(fèi)的時(shí)間很長(zhǎng),不能讓其影響正常的操作??梢圆捎胏opy-on-write操作,例如linux的fork
客戶端交互
Raft 中的客戶端發(fā)送所有請(qǐng)求給領(lǐng)導(dǎo)人。當(dāng)客戶端啟動(dòng)的時(shí)候,他會(huì)隨機(jī)挑選一個(gè)服務(wù)器進(jìn)行通信。
如果選擇的服務(wù)器是領(lǐng)導(dǎo)者,那么客戶端會(huì)把請(qǐng)求發(fā)到該服務(wù)器上
如果選擇的服務(wù)器不是領(lǐng)導(dǎo)者,該服務(wù)器會(huì)把領(lǐng)導(dǎo)者的地址告訴給客戶端,后續(xù)客戶端會(huì)把請(qǐng)求發(fā)給該領(lǐng)導(dǎo)者
如果此時(shí)沒有領(lǐng)導(dǎo)者,那么客戶端會(huì)timeout,客戶端會(huì)重試其他服務(wù)器,直到找到領(lǐng)導(dǎo)者
Raft 的目標(biāo)是要實(shí)現(xiàn)線性化語義(每一次操作立即執(zhí)行,只執(zhí)行一次,在他調(diào)用和收到回復(fù)之間)。但是,如上述,Raft 是可以執(zhí)行同一條命令多次的:例如,如果領(lǐng)導(dǎo)人在提交了這條日志之后,但是在響應(yīng)客戶端之前崩潰了,那么客戶端會(huì)和新的領(lǐng)導(dǎo)人重試這條指令,導(dǎo)致這條命令就被再次執(zhí)行了。解決方案就是客戶端對(duì)于每一條指令都賦予一個(gè)唯一的序列號(hào)。然后,狀態(tài)機(jī)跟蹤每條指令最新的序列號(hào)和相應(yīng)的響應(yīng)。如果接收到一條指令,它的序列號(hào)已經(jīng)被執(zhí)行了,那么就立即返回結(jié)果,而不重新執(zhí)行指令。
只讀的請(qǐng)求可以不寫log就能執(zhí)行,但是它有可能返回過期的數(shù)據(jù),有如下場(chǎng)景:
領(lǐng)導(dǎo)人響應(yīng)客戶端請(qǐng)求時(shí)可能已經(jīng)被新的領(lǐng)導(dǎo)人作廢了,但是他還不知道
Raft 需要使用兩個(gè)額外的措施在不使用日志的情況下保證這一點(diǎn)。
首先,領(lǐng)導(dǎo)人必須有關(guān)于被提交日志的最新信息。領(lǐng)導(dǎo)人完全特性保證了領(lǐng)導(dǎo)人一定擁有所有已經(jīng)被提交的日志條目,但是在他任期開始的時(shí)候,他可能不知道那些是已經(jīng)被提交的。為了知道這些信息,他需要在他的任期里提交一條日志條目。Raft 中通過領(lǐng)導(dǎo)人在任期開始的時(shí)候提交一個(gè)空白的沒有任何操作的日志條目到日志中去來實(shí)現(xiàn)。
第二,領(lǐng)導(dǎo)人在處理只讀的請(qǐng)求之前必須檢查自己是否已經(jīng)被廢黜了(他自己的信息已經(jīng)變臟了如果一個(gè)更新的領(lǐng)導(dǎo)人被選舉出來)。Raft 中通過讓領(lǐng)導(dǎo)人在響應(yīng)只讀請(qǐng)求之前,先和集群中的大多數(shù)節(jié)點(diǎn)交換一次心跳信息來處理這個(gè)問題??蛇x的,領(lǐng)導(dǎo)人可以依賴心跳機(jī)制來實(shí)現(xiàn)一種租約的機(jī)制,但是這種方法依賴時(shí)間來保證安全性(假設(shè)時(shí)間誤差是有界的)。
總結(jié)
以上是生活随笔為你收集整理的Raft 集群成员变更、日志压缩、客户端交互的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一致性算法 - Raft
- 下一篇: 用 Go 语言实现 Raft 选主