Part 19:Raft論文翻譯-《CONSENSUS BRIDGING THEORY AND PRACTICE》(日志壓縮-對(duì)基于內(nèi)存的狀態(tài)機(jī)進(jìn)行快照)
5.1 對(duì)基于內(nèi)存的狀態(tài)機(jī)進(jìn)行快照
第一種快照方法適用于狀態(tài)機(jī)的數(shù)據(jù)結(jié)構(gòu)保存在內(nèi)存中。對(duì)于數(shù)據(jù)集為幾GB或幾十GB的狀態(tài)機(jī),這是一個(gè)合理的選擇。它使操作能夠快速完成,因?yàn)樗鼈冇肋h(yuǎn)不必從磁盤獲取數(shù)據(jù);它也很容易編程,因?yàn)榭梢允褂秘S富的數(shù)據(jù)結(jié)構(gòu),每個(gè)操作都可以運(yùn)行到完成(不阻塞I/O)。

圖5.2顯示了在Raft中使用基于內(nèi)存的快照技術(shù)。每個(gè)服務(wù)器獨(dú)立的進(jìn)行快照,將剛提交的log entry包含進(jìn)去。大部分的工作就是對(duì)當(dāng)前狀態(tài)機(jī)狀態(tài)的快照數(shù)據(jù)進(jìn)行序列化,這是對(duì)于特定的狀態(tài)機(jī)實(shí)現(xiàn)。例如,LogCabin的狀態(tài)機(jī)使用了樹作為主要的數(shù)據(jù)結(jié)構(gòu),它使用預(yù)定深度優(yōu)先遍歷對(duì)此樹進(jìn)行序列化(這樣在應(yīng)用快照時(shí),父節(jié)點(diǎn)在其子節(jié)點(diǎn)之前)。狀態(tài)機(jī)還必須序列化它們保留的信息,以便為客戶端提供線性化信息(請(qǐng)參見第6章)。
一旦狀態(tài)機(jī)對(duì)某些log entry進(jìn)行了快照,相應(yīng)的log entry就可以被丟棄了。Raft將先保存用于重啟的狀態(tài)信息:快照中最后一個(gè)log entry的index和term,還有對(duì)應(yīng)index的最新的配置信息。然后,Raft就丟棄之前的log entry了。而且前面的快照也可以被丟棄。
如上所述,Leader可能偶爾需要將快照發(fā)送給其它的慢Follower以及新加入集群的服務(wù)器。在快照時(shí),狀態(tài)就是指的最新的快照里面的狀態(tài),Leader將通過所謂的InstallSnapshot RPC來將這個(gè)快照同步給其它服務(wù)器,如圖5.3所示。當(dāng)某個(gè)Follower接收到這個(gè)快照RPC的時(shí)候,它必須決定如何處理現(xiàn)在的log entry。一般情況下,快照里面應(yīng)該包含了Follower log里面沒有的新log entry。這種情況下,丟棄當(dāng)前的整個(gè)log(這個(gè)log里面可能有與快照里面沖突的log entry)。如果,F(xiàn)ollower接收到一個(gè)快照且有一個(gè)在此快照之前的log index(就是說,這個(gè)快照可能是部分快照,前面已經(jīng)有一些快照發(fā)送過了),那么和這個(gè)快照里面位置重疊的log entry將被覆蓋,隨后的log entry將被保留。
本節(jié)的其余部分將討論基于快照內(nèi)存的狀態(tài)機(jī)的第二個(gè)問題:
第5.1.1節(jié)討論了如何與正常操作并行生成快照,以盡量減少其對(duì)客戶端的影響;
第5.1.2節(jié)討論何時(shí)拍攝快照,平衡空間使用和快照的開銷;以及
第5.1.3節(jié)討論了在實(shí)現(xiàn)快照過程中出現(xiàn)的問題。
5.1.1 Snapshotting concurrently(并行緩存)
創(chuàng)建快照可能需要很長時(shí)間,無論是序列化狀態(tài)還是將快照寫入磁盤。例如,在今天的服務(wù)器上復(fù)制10GB的內(nèi)存大約需要一秒鐘,而序列化它通常需要更長的時(shí)間:即使是固態(tài)磁盤也只能在一秒鐘內(nèi)寫入大約500MB的內(nèi)存。那么,序列化和快照寫入需要與正常的操作并行來處理以避免系統(tǒng)的可用性受到影響。
幸運(yùn)的是,寫時(shí)復(fù)制技術(shù)允許在進(jìn)行快照寫入的時(shí)候進(jìn)行新的快照更新??赏ㄟ^以下兩種方式實(shí)現(xiàn):
狀態(tài)機(jī)可以被存儲(chǔ)于不可變的數(shù)據(jù)結(jié)構(gòu)中。因?yàn)樾碌臓顟B(tài)機(jī)不會(huì)修改已經(jīng)保存的狀態(tài)機(jī),一個(gè)快照任務(wù)可以使用數(shù)據(jù)結(jié)構(gòu)的引用而且一致的寫入到快照;
另一個(gè)方法,可以使用操作系統(tǒng)的寫時(shí)復(fù)制技術(shù)。在Linux操作系統(tǒng)中,內(nèi)存中的狀態(tài)機(jī)可以使用fork方法來復(fù)制服務(wù)器內(nèi)存的相應(yīng)地址空間中的數(shù)據(jù)(也就是狀態(tài)機(jī)所在的內(nèi)存地址,fork會(huì)利用寫時(shí)復(fù)制技術(shù)保證更新不會(huì)破壞原始數(shù)據(jù))。子進(jìn)程可以寫入快照并退出,父進(jìn)程可以繼續(xù)提供服務(wù)。LogCabin就是使用這種方式來實(shí)現(xiàn)的。
服務(wù)器需要額外的內(nèi)存來同步進(jìn)行快照,這是應(yīng)該被計(jì)劃和管理。狀態(tài)機(jī)必須具有到快照文件的流媒體接口,以便在創(chuàng)建快照時(shí)不必完全存儲(chǔ)在內(nèi)存中。但是,寫時(shí)復(fù)制機(jī)制需要額外的內(nèi)存,這與在快照過程中更改的狀態(tài)機(jī)狀態(tài)的比例成正比。此外,由于false sharing,依賴操作系統(tǒng)通常會(huì)使用更多的內(nèi)存(例如,如果兩個(gè)不相關(guān)的數(shù)據(jù)項(xiàng)恰好在內(nèi)存的同一頁面上,即使只有第一個(gè)發(fā)生更改,第二個(gè)項(xiàng)也將被重復(fù))。不幸的是,在快照過程中內(nèi)存容量耗盡,服務(wù)器應(yīng)該停止接受新的日志條目,直到完成快照;這將暫時(shí)犧牲服務(wù)器的可用性(集群可能仍然可用),但至少允許服務(wù)器恢復(fù)。最好不要中止快照并稍后再試,因?yàn)橄乱淮螄L試也可能面臨同樣的問題。
5.1.2 何時(shí)進(jìn)行快照?
服務(wù)器必須決定何時(shí)進(jìn)行快照。如果服務(wù)器快照過頻繁,則會(huì)浪費(fèi)磁盤帶寬和其他資源;如果快照頻率過于小,則會(huì)耗盡存儲(chǔ)容量,并增加重啟期間重播日志所需的時(shí)間。
一個(gè)簡(jiǎn)單的方法就是等日志到達(dá)某個(gè)固定的大小時(shí)候進(jìn)行快照。如果這個(gè)大小被設(shè)置的很大,那么磁盤的帶寬負(fù)載就會(huì)很小,但是,對(duì)于小的狀態(tài)機(jī)來說,這個(gè)日志就太大了。
一個(gè)更好的方法是比較快照的大小與日志的大小。如果快照將比日志小許多倍,那么可能值得進(jìn)行快照。然而,在進(jìn)行快照之前計(jì)算快照的大小可能很困難,這會(huì)給狀態(tài)機(jī)帶來巨大負(fù)擔(dān),或者需要幾乎與實(shí)際使用快照來動(dòng)態(tài)計(jì)算大小一樣多的工作。壓縮快照文件也可以節(jié)省空間和帶寬,但很難預(yù)測(cè)壓縮后的輸出會(huì)有多大。
幸運(yùn)的是,我們可以使用前一個(gè)快照的大小作為參考而不是下一個(gè)快照的大小。服務(wù)器可以在當(dāng)log的size大于前一個(gè)快照的size*擴(kuò)展因子的時(shí)候進(jìn)行快照。
快照將對(duì)CPU和磁盤的帶寬產(chǎn)生沖擊并影響對(duì)客戶端的響應(yīng)能力。在此方法中,集群中可以一次讓不超過半數(shù)的服務(wù)器進(jìn)行快照,這不會(huì)影響集群的可用性。因?yàn)镽aft只要多數(shù)派可以正常運(yùn)行就行。例如,當(dāng)Leader準(zhǔn)備進(jìn)行快照的時(shí)候,它可以先退出集群進(jìn)行快照,讓其他服務(wù)器來管理集群。這將是Raft后面可能會(huì)進(jìn)行的一個(gè)重點(diǎn)工作,因?yàn)檫@不僅能夠提升系統(tǒng)性能也能簡(jiǎn)化算法復(fù)雜度。
5.1.3 具體實(shí)現(xiàn)的思考
本節(jié)回顧了快照實(shí)現(xiàn)所需的主要組件,并討論了實(shí)現(xiàn)它們的困難:
保存和加載快照:保存快照涉及序列化狀態(tài)機(jī)的狀態(tài)并將數(shù)據(jù)寫入文件,而加載是相反的過程。我們發(fā)現(xiàn)這相當(dāng)簡(jiǎn)單。從狀態(tài)機(jī)到磁盤上文件的流接口可以避免在內(nèi)存中緩沖整個(gè)狀態(tài)機(jī)狀態(tài);壓縮流并對(duì)其應(yīng)用校驗(yàn)和也可能有益。LogCabin首先將每個(gè)快照寫入一個(gè)臨時(shí)文件,然后在寫入完成并已刷新到磁盤時(shí)重命名該文件;這將確保沒有服務(wù)器在啟動(dòng)時(shí)加載部分寫入的快照;
傳輸快照:傳輸快照涉及到實(shí)現(xiàn)InstallSnapshot RPC的Leader和Follower兩部分的代碼。這相當(dāng)簡(jiǎn)單,Leader和Follower可以共用一些代碼。這種傳輸?shù)男阅芡ǔ2⒉皇呛苤匾?,因?yàn)檫@個(gè)Follower此時(shí)可能并沒有正式成為集群的一員。另一方面,如果集群遭受其他故障,那可能就需要Follower盡快進(jìn)行日志復(fù)制以回復(fù)集群的可用性;
消除不安全的日志訪問和丟棄log entry:我們最初設(shè)計(jì)LogCabin并沒有擔(dān)心日志壓縮,所以代碼假設(shè)如果log entry i出現(xiàn)在日志中,log entry 1到i?1也會(huì)出現(xiàn)。采用日志壓縮后,上述假設(shè)可能就不再正確;因?yàn)樵谶M(jìn)行AppenEntries RPC會(huì)進(jìn)行l(wèi)og匹配,并會(huì)向前檢查log entry,如果前面的log entry已經(jīng)做了鏡像并被丟棄,那就會(huì)出現(xiàn)log entry訪問的越界情況,需要處理這個(gè)情況;
利用寫時(shí)復(fù)制進(jìn)行并發(fā)快照:可以基于操作系統(tǒng)的fork操作實(shí)現(xiàn)寫時(shí)復(fù)制;
何時(shí)進(jìn)行快照的決策:建議在開發(fā)期間可以每次apply一個(gè)log entry后就進(jìn)行快照操作,這有利于快速發(fā)現(xiàn)問題。等開發(fā)完成后,可以加入相應(yīng)的快照操作執(zhí)行時(shí)間選擇策略。
我們分塊的進(jìn)行開發(fā)和測(cè)試具有很大的挑戰(zhàn)。因?yàn)樵跊Q定進(jìn)行l(wèi)og丟棄時(shí),很多組件都需要就緒,實(shí)現(xiàn)者應(yīng)該仔細(xì)考慮實(shí)現(xiàn)和測(cè)試這些組件的順序。