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