作者:Anirban Rahut、Abhinav Sharma、Yichen Shen、Ahsanul Haque
- 我們推出 MySQL Raft 的目的是最終取代我們當(dāng)前的 MySQL 半同步數(shù)據(jù)庫。
- MySQL Raft 最大的好處是簡化了操作,讓 MySQL 服務(wù)器負(fù)責(zé)促銷和會員資格。這為 Raft 提供了可證明的安全性,并減少了顯著的操作痛苦。
- 使 MySQL 服務(wù)器成為真正的分布式系統(tǒng)也為下游系統(tǒng)利用它開辟了可能性。其中一些想法開始成形。
在 Meta,我們運(yùn)行著世界上最大的 MySQL 部署之一。該部署為社交圖譜以及許多其他服務(wù)提供支持,例如消息、廣告和提要。在過去的幾年里,我們實(shí)現(xiàn)了 MySQL Raft,這是一個 Raft 共識引擎,與 MySQL 集成以構(gòu)建復(fù)制狀態(tài)機(jī)。我們已經(jīng)將大部分部署遷移到 MySQL Raft ,并計劃用它完全替換當(dāng)前的 MySQL半同步數(shù)據(jù)庫。該項目為 Meta 的 MySQL 部署帶來了顯著的好處,包括更高的可靠性、可證明的安全性、故障轉(zhuǎn)移時間的顯著改進(jìn)以及操作的簡單性——所有這些都具有相同或可比的寫入性能。
背景
為了實(shí)現(xiàn)高可用性、容錯和擴(kuò)展讀取,Meta 的 MySQL 數(shù)據(jù)存儲是一個大規(guī)模分片、地理復(fù)制部署,具有數(shù)百萬個分片,保存 PB 級數(shù)據(jù)。部署包括在多個地區(qū)和多個大洲的數(shù)據(jù)中心運(yùn)行的數(shù)千臺機(jī)器。
以前,我們的復(fù)制解決方案使用的是 MySQL半同步(semisync)復(fù)制協(xié)議。這是一個僅數(shù)據(jù)路徑協(xié)議。MySQL 主節(jié)點(diǎn)將使用半同步復(fù)制到主區(qū)域內(nèi)但在主節(jié)點(diǎn)故障域之外的兩個僅記錄副本 (logtailers)。這兩個 logtailer 將充當(dāng)半同步 ACKer(ACK 是對事務(wù)已在本地寫入的主節(jié)點(diǎn)的確認(rèn))。這將允許數(shù)據(jù)路徑具有非常低的延遲(亞毫秒)提交,并為寫入提供高可用性/持久性。常規(guī)的 MySQL 主副本異步復(fù)制用于更廣泛地分布到其他區(qū)域。
控制平面操作(例如,升級、故障轉(zhuǎn)移和成員更改)將由一組 Python/ target=_blank class=infotextkey>Python 守護(hù)程序(以下稱為自動化)負(fù)責(zé)。自動化將進(jìn)行必要的編排,以將故障轉(zhuǎn)移位置的新 MySQL 服務(wù)器提升為主要服務(wù)器。自動化還將指向先前的主副本和剩余的副本,以從新的主副本進(jìn)行復(fù)制。成員更改操作將由另一個稱為MySQL 池掃描器(MPS)的自動化來協(xié)調(diào)。要添加新成員,MPS 會將新副本指向主副本并將其添加到服務(wù)發(fā)現(xiàn)存儲。故障轉(zhuǎn)移將是一個更復(fù)雜的操作,其中 logtailer(半同步 ACKer)的尾線程將被關(guān)閉以隔離之前死掉的主線程。
為什么需要 MySQL Raft?
過去,為了在復(fù)雜的升級和故障轉(zhuǎn)移操作期間幫助保證安全并避免數(shù)據(jù)丟失,一些自動化守護(hù)進(jìn)程和腳本會使用鎖定、編排步驟、防護(hù)機(jī)制和服務(wù)發(fā)現(xiàn)系統(tǒng) SMC。這是一個分布式設(shè)置,很難以原子方式完成。隨著越來越多的極端情況需要修補(bǔ),隨著時間的推移,自動化變得越來越復(fù)雜和難以維護(hù)。
我們決定采取完全不同的方法。我們增強(qiáng)了 MySQL 并使其成為真正的分布式系統(tǒng)。意識到升級和成員變更等控制平面操作是大多數(shù)問題的觸發(fā)因素,我們希望控制平面和數(shù)據(jù)平面操作成為同一復(fù)制日志的一部分。為此,我們使用了眾所周知的共識協(xié)議Raft。這也意味著成員資格和領(lǐng)導(dǎo)權(quán)的真實(shí)來源移動到服務(wù)器 (mysqld) 內(nèi)部。這是引入 Raft 的最大貢獻(xiàn),因?yàn)樗?MySQL 服務(wù)器的促銷和成員更改之間實(shí)現(xiàn)了可證明的正確性(安全屬性)。
Raft 庫和 MySQL Raft 插件
我們的 Raft for MySQL 實(shí)現(xiàn)基于Apache Kudu。我們根據(jù) MySQL 和部署的需要對其進(jìn)行了顯著增強(qiáng)。我們將此分支作為開源項目kuduraft發(fā)布。
我們添加到 kuduraft 的一些關(guān)鍵特性是:
- FlexiRaft — 支持兩種不同的交叉仲裁:數(shù)據(jù)仲裁和領(lǐng)導(dǎo)者選舉仲裁
- 代理——使用代理中間節(jié)點(diǎn)減少網(wǎng)絡(luò)帶寬的能力
- 壓縮——我們在分發(fā)之前壓縮一次二進(jìn)制日志(事務(wù))有效負(fù)載
- 日志抽象——支持不同的物理日志文件實(shí)現(xiàn)
- Primary ban——阻止某些實(shí)體暫時成為主要實(shí)體的能力
我們還必須對 MySQL 復(fù)制進(jìn)行相對較大的更改以與 Raft 接口。為此,我們創(chuàng)建了一個名為 MyRaft 的新閉源 MySQL 插件。MySQL 將通過插件 API 與 MyRaft 接口(類似的 API 也已用于半同步),而我們?yōu)?MyRaft 創(chuàng)建了一個單獨(dú)的 API 以與 MySQL 服務(wù)器接口(回調(diào))。

MySQL Raft 復(fù)制拓?fù)?/h1>
一個 Raft 環(huán)將由不同區(qū)域的多個 MySQL 實(shí)例(圖中有四個)組成。這些區(qū)域之間的通信往返時間 (RTT) 范圍為 10 到 100 毫秒。這些 MySQL 中的一些(通常是三個)被允許成為主要的,而其余的只允許成為純讀取副本(不具備主要能力)。Meta 的 MySQL 部署也對極低延遲提交有著長期的需求。使用 MySQL 作為存儲的服務(wù)(例如,社交圖譜)需要或已經(jīng)設(shè)計為如此極快的寫入。
為了滿足這一要求,F(xiàn)lexiRaft 的配置將僅使用區(qū)域內(nèi)提交(單區(qū)域動態(tài)模式)。為了實(shí)現(xiàn)這一點(diǎn),每個主要的有能力的區(qū)域?qū)⒂袃蓚€額外的 logtailer(見證或僅日志實(shí)體)。寫入的數(shù)據(jù)法定人數(shù)為 2/3(1 個 MySQL + 2 個 logtailer 中的 2 個 ACK?)。Raft 仍然會管理和運(yùn)行跨所有實(shí)體的復(fù)制日志(1 個具有主功能的 MySQL + 2 個 logtailers)* 3 個區(qū)域 +(不具有主功能的 MySQL)* 3 個區(qū)域 = 12 個實(shí)體。
Raft roles:leader,顧名思義,就是復(fù)制日志的一個term中的leader。Raft 中的領(lǐng)導(dǎo)者也將是 MySQL 中的主要領(lǐng)導(dǎo)者,并且接受客戶端寫入。follower 是環(huán)中的投票成員,被動接收來自 leader 的消息( AppendEntries )。從 MySQL 的角度來看,跟隨者將是一個副本,并將事務(wù)應(yīng)用于其引擎。它不允許從用戶連接直接寫入(設(shè)置了 read_only=1)。學(xué)習(xí)者將是環(huán)中的非投票成員,例如,非主要功能區(qū)域中的三個 MySQL(上圖)。從 MySQL 的角度來看,它將是一個副本。

復(fù)制日志
對于復(fù)制,MySQL 歷來使用二進(jìn)制日志格式。這種格式是 MySQL 復(fù)制的核心,我們決定保留它。從 Raft 的角度來看,二進(jìn)制日志變成了復(fù)制日志。這是通過對 kuduraft 的日志抽象改進(jìn)完成的。MySQL 事務(wù)將被編碼為一系列事件(例如,Update Rows 事件),每個事務(wù)都有開始和結(jié)束。二進(jìn)制日志也有適當(dāng)?shù)臉?biāo)題,通常以結(jié)束事件(輪換事件)結(jié)束。
我們不得不調(diào)整 MySQL 在內(nèi)部管理其日志的方式。在主節(jié)點(diǎn)上,Raft 會寫入二進(jìn)制日志。這與標(biāo)準(zhǔn) MySQL 中發(fā)生的情況沒有什么不同。在副本中,Raft 還會寫入二進(jìn)制日志,而不是標(biāo)準(zhǔn) MySQL 中的單獨(dú)中繼日志。這為 Raft 創(chuàng)造了簡單性,因?yàn)?Raft 只關(guān)心一個日志文件的命名空間。如果一個追隨者被提升為領(lǐng)導(dǎo)者,它可以無縫地返回到它的日志歷史記錄中,將交易發(fā)送給落后的成員。副本的應(yīng)用程序線程將從二進(jìn)制日志中獲取事務(wù),然后將它們應(yīng)用到引擎。在此過程中,將創(chuàng)建一個新的日志文件,即應(yīng)用日志。此應(yīng)用日志將在副本的崩潰恢復(fù)中發(fā)揮重要作用,但在其他方面是非復(fù)制日志文件。
所以,總結(jié)一下:
在標(biāo)準(zhǔn) MySQL 中:
- Primary 寫入 binlog 并將 binlog 發(fā)送到 replicas。
- 副本在中繼日志中接收并將事務(wù)應(yīng)用到引擎。在應(yīng)用期間,將創(chuàng)建一個新的僅限副本的二進(jìn)制日志。
在 MySQL 筏中:
- Primary 通過 Raft 寫入 binlog,Raft 將 binlog 發(fā)送給 followers/replicas。
- 副本/跟隨者在二進(jìn)制日志中接收并將事務(wù)應(yīng)用到引擎。在申請期間創(chuàng)建申請日志。
- Binlog 從 Raft 的角度來看就是復(fù)制的日志。
使用 Raft 在 MySQL primary 上寫入事務(wù)
事務(wù)將首先在引擎中準(zhǔn)備好。這將發(fā)生在用戶連接的線程中。準(zhǔn)備交易的行為將涉及與存儲引擎(例如InnoDB或MyRocks)的交互,并為交易生成內(nèi)存中的二進(jìn)制日志有效負(fù)載。在提交時,寫入將通過group commit /ordered_commit 流程。將分配 GTID,然后 Raft 將分配一個 OpId (term:index) 給交易。此時,Raft 會將事務(wù)壓縮,存儲在自己的 LogCache 中,并通過事務(wù)寫入一個 binlog 文件。它將異步開始將交易發(fā)送給其他追隨者以獲得 ACK 并達(dá)成共識。
處于事務(wù)“提交”狀態(tài)的用戶線程將被阻塞,等待來自 Raft 的共識。當(dāng) Raft 獲得三分之二的區(qū)域投票時,就會達(dá)成共識提交。Raft 還會將交易發(fā)送給所有區(qū)域外的成員,但會因?yàn)榉Q為 FlexiRaft 的算法(如下所述)而忽略他們的投票。在一致提交時,用戶線程將被解除阻塞,事務(wù)將繼續(xù)并提交給引擎。引擎提交后,寫入查詢將完成并返回給客戶端。不久之后,Raft 也會異步發(fā)送一個提交標(biāo)記(當(dāng)前提交的 OpId)給下游的追隨者,這樣他們也可以將事務(wù)應(yīng)用到他們的數(shù)據(jù)庫中。

崩潰恢復(fù)
必須對崩潰恢復(fù)進(jìn)行更改,以使其與 Raft 無縫協(xié)作。在交易的生命周期中,崩潰隨時可能發(fā)生,因此協(xié)議必須確保成員的一致性。以下是有關(guān)我們?nèi)绾问蛊浒l(fā)揮作用的一些重要見解。
- Transaction was not flushed to binlog:在這種情況下,內(nèi)存中的事務(wù)負(fù)載(仍然在 mysqld 進(jìn)程內(nèi)存中作為內(nèi)存緩沖區(qū))將丟失,并且引擎中準(zhǔn)備好的事務(wù)將在進(jìn)程重新啟動時回滾。由于 Raft 日志中沒有多余的未提交事務(wù),因此不需要與其他成員進(jìn)行對賬。
- 事務(wù)被刷新到 binlog 但從未到達(dá)其他成員:Mysqld 充當(dāng)事務(wù)協(xié)調(diào)器并作為參與者在引擎和復(fù)制的 binlog 之間運(yùn)行兩階段提交協(xié)議。在崩潰恢復(fù)時,引擎(例如 InnoDB 或 MyRocks)中準(zhǔn)備好的事務(wù)將被回滾(引擎尚未提交)。Raft 將進(jìn)行故障轉(zhuǎn)移,并選舉出新的領(lǐng)導(dǎo)者。該領(lǐng)導(dǎo)者不會在其 binlog 中包含此事務(wù),并且此后將從前領(lǐng)導(dǎo)者的 binlog 中截斷此事務(wù),因?yàn)楫?dāng)前任領(lǐng)導(dǎo)者重新加入環(huán)時(通過推送 No-Op 消息)。
- 事務(wù)被刷新到 binlog 并到達(dá)下一個領(lǐng)導(dǎo)者。Current leader 在提交給引擎之前就死了:類似于 no。2 上面,引擎中準(zhǔn)備好的事務(wù)將被回滾。以前的領(lǐng)導(dǎo)者將作為追隨者加入 Raft 環(huán)。在這種情況下,新領(lǐng)導(dǎo)者將在其二進(jìn)制日志中包含此事務(wù),因此不會發(fā)生截斷,因?yàn)槿罩緯ヅ洹.?dāng)新領(lǐng)導(dǎo)者發(fā)送提交標(biāo)記時,事務(wù)將從頭開始重新應(yīng)用。
Raft 啟動的狀態(tài)機(jī)轉(zhuǎn)換
故障轉(zhuǎn)移和定期維護(hù)操作可以觸發(fā) Raft 中的領(lǐng)導(dǎo)層變更。選出領(lǐng)導(dǎo)者后,MyRaft 插件將嘗試將伴隨的 MySQL 轉(zhuǎn)換為主要模式。為此,該插件將編排一組步驟。這些來自 Raft → MySQL 的回調(diào)將中止正在進(jìn)行的事務(wù),回滾正在使用的 GTID,將引擎端日志從應(yīng)用日志轉(zhuǎn)換為二進(jìn)制日志,并最終設(shè)置正確的只讀設(shè)置。這個機(jī)制比較復(fù)雜,目前還沒有開源。
靈活筏
由于Raft 論文和 Apache Kudu 僅支持單個全局仲裁,因此它在 Meta 上效果不佳,因?yàn)榄h(huán)很大但數(shù)據(jù)路徑仲裁需要很小。
為了規(guī)避這個問題,我們在 FlexiRaft 上進(jìn)行了創(chuàng)新,借鑒了Flexible Paxos 的思想。
在高層次上,F(xiàn)lexiRaft 允許 Raft 有不同的數(shù)據(jù)提交法定人數(shù)(小),但在領(lǐng)導(dǎo)者選舉法定人數(shù)(大)上采取相應(yīng)的命中。通過遵循群體交集的可證明保證,F(xiàn)lexiRaft 確保 Raft 的最長日志規(guī)則和適當(dāng)?shù)娜后w交集將保證可證明的安全性。
FlexiRaft 支持單區(qū)域動態(tài)模式。在這種模式下,成員按其地理區(qū)域分組在一起。Raft 的當(dāng)前法定人數(shù)取決于當(dāng)前領(lǐng)導(dǎo)者是誰(因此稱為“單區(qū)域動態(tài)”)。數(shù)據(jù)法定人數(shù)是領(lǐng)導(dǎo)者所在地區(qū)的大多數(shù)選民。在晉升期間,如果任期是連續(xù)的,則候選人將與最后一個已知領(lǐng)導(dǎo)者的區(qū)域相交。FlexiRaft 還會確保也達(dá)到 Candidate 區(qū)域的法定人數(shù),否則后續(xù)的 No-Op 消息可能會卡住。如果在極少數(shù)情況下項不連續(xù),F(xiàn)lexi Raft 將嘗試找出一組不斷增長的區(qū)域,為了安全需要與之相交,或者在最壞的情況下,將退回到 Flexible Paxos 的 N 區(qū)域相交情況. 由于預(yù)選和模擬選舉,
控制平面操作(促銷和會員變更)
為了序列化binlog中的promotion和membership change事件,我們劫持了MySQL二進(jìn)制日志格式的Rotate Event和Metadata事件。這些事件將攜帶相當(dāng)于 Raft 的 No-Op 消息和添加成員/刪除成員操作。Apache Kudu 不支持聯(lián)合共識,因此我們只允許一次更改一個成員資格(您可以在一輪中僅更改一個實(shí)體的成員資格以遵循隱式仲裁交集的規(guī)則)。
自動化
隨著 MySQL Raft 的實(shí)施,我們?yōu)?MySQL 部署實(shí)現(xiàn)了一個非常干凈的關(guān)注點(diǎn)分離。MySQL 服務(wù)器將通過 Raft 的復(fù)制狀態(tài)機(jī)負(fù)責(zé)安全。無數(shù)據(jù)丟失保證將被證明包含在服務(wù)器本身中。自動化(Python 腳本、守護(hù)進(jìn)程)將啟動控制平面操作并監(jiān)控機(jī)隊的健康狀況。它還會在維護(hù)期間或檢測到主機(jī)故障時通過 Raft 替換成員或進(jìn)行促銷。偶爾,自動化也可以改變 MySQL 拓?fù)涞膮^(qū)域布局。改變自動化以適應(yīng) Raft 是一項艱巨的任務(wù),跨越了多年的開發(fā)和推出工作。
在長時間的維護(hù)事件中,自動化會在 Raft 上設(shè)置領(lǐng)導(dǎo)禁止信息。Raft 將不允許那些被禁止的實(shí)體成為領(lǐng)導(dǎo)者,或者在無意的選舉中迅速撤離他們。自動化還將促進(jìn)從這些地區(qū)進(jìn)入其他地區(qū)。
從部署過程中遇到的挑戰(zhàn)中學(xué)習(xí)
將 Raft 部署到艦隊對團(tuán)隊來說是一次巨大的學(xué)習(xí)。我們最初在MySQL 5.6上開發(fā) Raft ,不得不遷移到MySQL 8.0。
其中一項重要的經(jīng)驗(yàn)是,雖然使用 Raft 更容易推理出正確性,但 Raft 協(xié)議本身對可用性的關(guān)注并沒有多大幫助。由于我們的 MySQL 數(shù)據(jù)仲裁非常小(三分之二的區(qū)域內(nèi)成員),該地區(qū)的兩個壞實(shí)體幾乎可以破壞仲裁并降低可用性。MySQL 集群每天都會經(jīng)歷大量變動(由于維護(hù)、主機(jī)故障、重新平衡操作),因此及時正確地啟動和執(zhí)行成員更改是持續(xù)可用性的關(guān)鍵要求。推出工作的很大一部分集中在及時更換 logtailer 和 MySQL,以便 Raft quorums 健康。
我們必須增強(qiáng) kuduraft 以使其在可用性方面更加穩(wěn)健。這些改進(jìn)不是核心協(xié)議的一部分,但可以被視為它的工程附加組件。Kuduraft 支持預(yù)選,但預(yù)選僅在故障轉(zhuǎn)移期間進(jìn)行。在優(yōu)雅的領(lǐng)導(dǎo)權(quán)交接過程中,指定的候選人直接進(jìn)入真正的選舉,連任。這會導(dǎo)致領(lǐng)導(dǎo)者卡住(kuduraft 不會自動降壓)。為了解決這個問題,我們添加了一個模擬選舉功能,它類似于預(yù)選,但只有在優(yōu)雅地交接領(lǐng)導(dǎo)權(quán)時才會發(fā)生。由于這是一個異步操作,因此不會增加促銷停機(jī)時間。模擬選舉將排除真正的選舉會部分成功并陷入困境的情況。
Handling byzantine failures:Raft 的成員列表被認(rèn)為是 Raft 自己加持的。但是在提供新成員期間,或者由于自動化競賽,可能會出現(xiàn)兩個不同的 Raft 環(huán)相交的奇怪情況。這些僵尸成員節(jié)點(diǎn)必須被淘汰,并且不能相互通信。我們實(shí)施了一項功能來阻止從此類僵尸成員到環(huán)的 RPC。在某些方面,這是對拜占庭演員的處理。在注意到部署中發(fā)生的這些罕見事件后,我們增強(qiáng)了 Raft 實(shí)施。
監(jiān)控 MySQL Raft 部署
在啟動 MySQL Raft 時,目標(biāo)之一是降低隨叫隨到的操作復(fù)雜性,以便工程師能夠找到根本原因并緩解問題。我們構(gòu)建了幾個儀表板、CLI 工具和scuba表來監(jiān)控 Raft。我們向 MySQL 添加了大量日志記錄,尤其是在促銷和成員變更方面。我們?yōu)榄h(huán)上的法定人數(shù)和投票報告創(chuàng)建了 CLI,這有助于我們快速確定環(huán)何時以及為何不可用(破碎的法定人數(shù))。對工具和自動化基礎(chǔ)設(shè)施的投資是齊頭并進(jìn)的,可能比服務(wù)器變更的投資更大。這項投資獲得了豐厚的回報,減少了運(yùn)營和入職培訓(xùn)的痛苦。
法定人數(shù)修復(fù)者
雖然這是不可取的,但仲裁確實(shí)時不時地被打破,導(dǎo)致可用性損失。典型的情況是自動化沒有檢測到環(huán)中不健康的實(shí)例/logtailer 并且沒有快速替換它們。發(fā)生這種情況的原因可能是檢測不力、工作隊列過載或缺少備用主機(jī)容量。當(dāng)法定??人數(shù)中的多個實(shí)體同時宕機(jī)時,相關(guān)故障不太常見。這些不會經(jīng)常發(fā)生,因?yàn)椴渴饡L試通過適當(dāng)?shù)姆胖脹Q策來隔離仲裁中關(guān)鍵實(shí)體的故障域。長話短說:盡管有現(xiàn)有的保障措施,但在規(guī)模上,意想不到的事情還是會發(fā)生。需要有可用的工具來緩解生產(chǎn)中的這種情況。我們基于對這一點(diǎn)的預(yù)期構(gòu)建了 Quorum Fixer。
Quorum Fixer 是一種用 Python 編寫的手動修復(fù)工具,可以抑制環(huán)上的寫入。它進(jìn)行帶外檢查以找出最長的日志實(shí)體。它強(qiáng)行改變了 Raft 內(nèi)部領(lǐng)導(dǎo)者選舉的法定人數(shù)期望,以便被選中的實(shí)體成為領(lǐng)導(dǎo)者。成功升級后,我們重置法定人數(shù)期望值,環(huán)通常會變得健康。
不自動運(yùn)行這個工具是一個有意識的決定,因?yàn)槲覀兿胍业礁驹虿⒋_定所有仲裁丟失的情況,并在此過程中修復(fù)錯誤(而不是讓它們默默地被自動化修復(fù))。
推出 MySQL Raft
在大規(guī)模部署中從半同步過渡到 MySQL Raft 是很困難的。為此,我們創(chuàng)建了一個名為 enable-raft 的工具(在 Python 中)。Enable-raft 通過加載插件并在每個實(shí)體上設(shè)置適當(dāng)?shù)呐渲茫╩ysql sys-vars)來協(xié)調(diào)從半同步到 Raft 的轉(zhuǎn)換。此過程涉及環(huán)的一小段停機(jī)時間。隨著時間的推移,該工具變得健壯,可以非常快速地大規(guī)模推出 Raft。我們已經(jīng)用它來安全地推出 Raft。
測試和影子工作流程
不用說,改變MySQL的核心復(fù)制管道是一個非常困難的工程。由于數(shù)據(jù)安全受到威脅,因此測試是信心的關(guān)鍵。我們在項目期間顯著利用了影子測試和故障注入。在每次 RPM 包管理器推出之前,我們會在測試環(huán)上注入數(shù)千次故障轉(zhuǎn)移和選舉。我們將觸發(fā)測試資產(chǎn)的替換和成員更改以觸發(fā)關(guān)鍵代碼路徑。
具有數(shù)據(jù)正確性檢查的長期運(yùn)行測試也很關(guān)鍵。我們有每晚在分片上運(yùn)行的自動化,確保主副本和副本的一致性。我們會收到任何此類不匹配的警報,并對其進(jìn)行調(diào)試。
表現(xiàn)
Raft 寫路徑延遲的性能相當(dāng)于半同步。半同步機(jī)制稍微簡單一些,因此預(yù)計會更精簡,但是我們優(yōu)化了 Raft 以獲得與半同步相同的延遲。我們優(yōu)化了 kuduraft 以不再向隊列中添加任何 CPU,盡管它引入了以前在服務(wù)器二進(jìn)制文件之外的更多職責(zé)。
Raft 對提升和故障轉(zhuǎn)移時間進(jìn)行了數(shù)量級的改進(jìn)。優(yōu)雅的晉升,這是車隊領(lǐng)導(dǎo)層變動的主要部分,得到了顯著改善,我們通常可以在 300 毫秒內(nèi)完成一次晉升。在半同步設(shè)置中,由于服務(wù)發(fā)現(xiàn)存儲將成為事實(shí)來源,客戶端注意到升級完成的時間會長得多,從而導(dǎo)致最終用戶在分片上的停機(jī)時間增加。
Raft 通常會在 2 秒內(nèi)進(jìn)行故障轉(zhuǎn)移。這是因?yàn)槲覀兠?500 毫秒為 Raft 健康跳一次心跳,并在連續(xù)三個心跳失敗時開始選舉。在半同步世界中,這一步是編排繁重的,需要 20 到 40 秒。因此,Raft 將故障轉(zhuǎn)移案例的停機(jī)時間縮短了 10 倍。
下一步
Raft 通過提供可證明的安全性和簡單性,幫助解決了 Meta 中 MySQL 的操作管理問題。我們的目標(biāo)是不干涉 MySQL 一致性的管理,并擁有針對罕見的可用性損失情況的工具,這些目標(biāo)已基本實(shí)現(xiàn)。Raft 現(xiàn)在在未來開辟了重要的機(jī)會,因?yàn)槲覀兛梢詫W⒂谠鰪?qiáng)對使用 MySQL 的服務(wù)的提供。我們的服務(wù)所有者提出的其中一項要求是具有可配置的一致性。可配置的一致性將允許所有者在入職時選擇服務(wù)是否需要 X 區(qū)域仲裁或在某些特定地區(qū)(例如歐洲和美國)要求副本的仲裁。FlexiRaft 對這種可配置的仲裁有無縫支持,我們計劃在未來開始推出這種支持。PACELC 定理)。
由于代理特性(使用多跳分布拓?fù)浒l(fā)送消息的能力),Raft 還可以節(jié)省跨大西洋的網(wǎng)絡(luò)帶寬。我們計劃使用Raft只從美國復(fù)制一次到歐洲,然后使用Raft的代理特性在歐洲內(nèi)部分發(fā)。這將增加延遲,但考慮到大部分延遲發(fā)生在跨大西洋傳輸中并且額外的躍點(diǎn)要短得多,這將是名義上的。
Meta 的數(shù)據(jù)庫部署和分布式共識空間中一些更具推測性的想法是關(guān)于探索無領(lǐng)導(dǎo)協(xié)議,如Epaxos。我們當(dāng)前的部署和服務(wù)已經(jīng)在強(qiáng)大的領(lǐng)導(dǎo)者協(xié)議附帶的假設(shè)下工作,但我們開始看到一些需求,其中服務(wù)將受益于 WAN 中更統(tǒng)一的寫入延遲。我們正在考慮的另一個想法是將日志從狀態(tài)機(jī)(數(shù)據(jù)庫)中分離出來,形成一個分解的日志設(shè)置。這將允許團(tuán)隊將日志和復(fù)制問題與數(shù)據(jù)庫存儲和 SQL 執(zhí)行引擎的問題分開管理。
致謝
在元規(guī)模上構(gòu)建和部署 MySQL Raft 需要大量的團(tuán)隊合作和管理支持。我們要感謝以下人員在使該項目取得成功方面發(fā)揮的作用。Shrikanth Shankar、Tobias Asplund、Jim Carrig、Affan Dar 和 David Nagle 在這次旅程中為團(tuán)隊成員提供了支持。我們還要感謝這個項目的干練項目經(jīng)理 Dan O 和 Karthik Chidambaram,他們讓我們走上正軌。
工程工作涉及幾位現(xiàn)任和前任團(tuán)隊成員的重要貢獻(xiàn),包括 Vinaykumar Bhat、Xi Wang、Bartlomiej Pelc、Chi Li、Yash Botadra、Alan Liang、Michael Percy、Yoshinori Matsunobu、Ritwik Yadav、Luqun Lou、Pushap Goyal、Anatoly Karp 和伊戈爾·波茲加。
作者:Anirban Rahut、Abhinav Sharma、Yichen Shen、Ahsanul Haque
出處
:https://engineering.fb.com/2023/05/16/data-infrastructure/mysql-raft-meta/