替代MySQL半同步,Meta技術團隊推出MySQL Raft共識引擎

【CSDN 編者按】Meta運行著世界上最大規模的MySQL部署之一。該部署驅動著社交圖譜以及許多其他服務,如訊息、廣告和動態。在過去幾年中,他們實施了MySQL Raft,這是一個與MySQL集成的Raft共識引擎,用於構建複製狀態機。目前已大部分部署遷移至MySQL Raft,並計劃完全用其替代當前的MySQL半同步複製。

原文連結:https://engineering.fb.com/2023/05/16/data-infrastructure/mysql-raft-meta/

未經授權,禁止轉載!

作者 | Anirban Rahut、Abhinav Sharma、Yichen Shen、Ahsanul Haque

翻譯 | ChatGPT 責編 | 夢依丹

MySQL Raft是MySQL資料庫中一種基於Raft協議的分散式一致性複製機制。近日,Meta技術團隊分享了他們基於Raft協議在資料庫基礎設施方面的實踐與創新,並打算取代當下使用的MySQL半同步資料庫(原文是用semisynchronous databases)。本文藉助ChatGPT,進行了編譯整理。

Meta運行著世界上最大規模的MySQL部署之一。該部署驅動著社交圖譜以及許多其他服務,如訊息、廣告和動態。在過去幾年中,他們實施了MySQL Raft,這是一個與MySQL集成的Raft共識引擎,用於構建複製狀態機。目前已大部分部署遷移至MySQL Raft,並計劃完全用其替代當前的MySQL半同步複製。這也給Meta的MySQL部署帶來了顯著的成效,包括更高的可靠性、可證明的安全性、故障轉移時間的顯著改善以及操作簡便性,同時具有相等或可比較的寫入性能。

遷移背景

遷移背景

為了實現高可用性、容錯性和讀取擴展性,Meta的MySQL資料儲存是一個大規模分片的地理複製部署,擁有數百萬個分片,保存著PB級別的資料。該部署包括數千臺機器,在多個洲際區域和資料中心運行。

以前,他們的複製解決方案是使用MySQL半同步(semisync)複製協議。這是一種僅限於資料路徑的協議。MySQL主伺服器會使用半同步複製將資料寫入兩個日誌記錄副本(logtailers),這些副本位於主要區域之外但不在主伺服器故障域內。這兩個 日誌記錄副本將充當半同步ACKer(ACK 是對主伺服器發出的事務已被本地寫入進行確認)。這將使得資料路徑具有非常低延遲的(亞毫秒級)提交,併為寫操作提供高可用性/耐久性。普通的MySQL主-從非同步複製則用於向其他區域進行更廣泛的分發。

控制平面操作(例如晉升、故障轉移和成員變更)由一組Python守護程序負責執行(以下簡稱自動化)。自動化將執行必要的編排工作,以在故障轉移位置上晉升新的MySQL伺服器作為主伺服器。自動化還會將以前的主伺服器和剩餘的從伺服器指向新的主伺服器進行復制。成員變更操作由另一個名為MySQL池掃描器(MPS)的自動化程序編排執行。要添加新成員,MPS將把新副本指向主伺服器並將其添加到服務發現儲存中。故障轉移是一項更復雜的操作,在此過程中,logtailers的尾部執行緒(半同步 ACKer)將被關閉以限制先前死亡的主伺服器。

為什麼需要MySQL Raft?

為什麼需要MySQL Raft?

過去,為了確保安全並避免在複雜的晉升和故障轉移操作期間資料丟失,幾個自動化守護程序和腳本會使用鎖定、編排步驟、圍欄機制和SMC(服務發現系統)。這是一個分散式設置,並且很難以原子方式完成。隨著越來越多的邊角情況需要修補,自動化變得更加複雜和難以維護。

基於此,Meta技術團隊決定採取完全不同的方法。他們增強了MySQL並使其成為真正的分散式系統。意識到控制平面操作如晉升和成員更改是大多數問題的觸發器,團隊希望控制平面和資料平面操作成為相同複製日誌的一部分。為此,他們使用了眾所周知的共識協議Raft。這就意味著成員身份和領導力真實性來源於伺服器(mysqld)內部。這是引入Raft最大貢獻之一,因為它使得在MySQL伺服器中進行晉升和成員更改時能夠證明正確性(安全屬性)。

Raft庫和MySQL Raft外掛

Raft庫和MySQL Raft外掛

Meta團隊基於Apache Kudu實現了MySQL Raft,針對MySQL和他們的部署需求進行了大量增強,並將這個分支作為開源項目kuduraft發佈。

kuduraft的一些核心功能包括:

  • FlexiRaft – 支持兩個不同交叉仲裁:資料仲裁和領導者選舉仲裁

  • 代理(Proxying) – 能夠使用代理中間節點來減少網路頻寬

  • 壓縮(Proxying ) – 在分發之前對二進位制日誌(事務)有效載荷進行一次壓縮

  • 日誌抽象化 – 支持不同的物理日誌檔案實

  • 主要禁止(Primary ban) -臨時阻止某些實體成為主要實體的能力

為了與Raft進行接口互動,他們還對MySQL複製進行相對大的更改,並創建了一個名為MyRaft的新的閉源MySQL外掛MyRaft。MySQL將通過外掛API與MyRaft進行接口互動(類似的API也用於半同步),同時他們還為MyRaft創建了一個單獨的API,用於與MySQL伺服器進行接口互動(回調)。

MySQL Raft複製拓撲結構

MySQL Raft複製拓撲結構

在MySQL Raft的複製拓撲中,使用Raft協議的MySQL實例形成一個環狀結構,每個實例在不同的地區。Raft環將由幾個位於不同地區的MySQL實例(圖中有四個)組成。這些地區之間的通訊往返時間(RTT)在10到100毫秒之間。其中幾個MySQL實例(通常是三個)充當主節點,處理寫入操作,而其餘的實例充當只讀副本,用於處理讀取操作。在Meta的MySQL部署中,對於提交操作的延遲要求非常低,因為使用MySQL作為儲存的服務需要快速的寫入能力。

為了在FlexiRaft配置中為了滿足低延遲提交要求,Meta採用了區域內提交的方式(即單區域動態模式)。為了實現該目標,每個具備主節點能力的區域將增加兩個日誌追隨者(也稱為見證者或僅記錄實體)。寫入操作的資料法定人數設為2/3,即需要從1個MySQL和2個日誌追隨者中獲得2個ACK。Raft仍然負責管理和運行跨所有實體的複製日誌,包括1個具備主節點能力的MySQL加上2個日誌追隨者的組合,乘以3個區域,再加上3個區域中的非主節點能力的MySQL,總共有12個實體。

Raft角色:在Raft協議中,有三種角色:領導者(Leader)、追隨者(Follower)和學習者(Learner)。領導者是在複製日誌中擔任領導者的角色,同時也是MySQL中的主節點,負責接收客戶端的寫入操作。追隨者是環中的投票成員,從領導者那裡被動地接收訊息(AppendEntries)。從MySQL的角度來看,追隨者是一個副本,會將事務應用到自己的引擎中。它不允許使用者連接進行直接寫入操作(設置為read_only=1)。學習者是環中的非投票成員,例如,在非主節點能力的區域中的三個MySQL實例。在MySQL的角度來看,學習者也是一個副本。

複製日誌

複製日誌

MySQL一直以來都使用二進位制日誌格式進行復制。這種格式對於MySQL的複製非常重要,因此Meta團隊決定保留該格式。從Raft的角度來看,通過對kuduraft進行日誌抽象和改進,實現了將二進位制日誌作為複製日誌的方式。MySQL的事務會被編碼為一系列事件,比如Update Rows事件,每個事務都有開始和結束時間。二進位制日誌還包含適當的標頭,並通常以結束事件(Rotate事件)作為結尾。

為了滿足Raft的需求,Meta不得不調整MySQL內部日誌的管理方式。在主節點上,Raft會將日誌寫入binlog,與標準MySQL的操作方式幾乎沒有區別。而在副本中,Raft同樣將資料寫入binlog,而不是像標準MySQL那樣寫入單獨的relay log。這種調整使得Raft更加簡化,因為它只需要關注一個名稱空間下的日誌檔案。如果某個副本被提升為領導者,它可以無縫地從自己的歷史記錄中獲取事務,並向滯後的成員發送事務。副本的應用程序執行緒會從binlog中獲取事務,並將其應用於資料庫引擎。在此過程中,還會創建一個名為”apply log”的新日誌檔案,該檔案在副本的崩潰恢復方面起著重要作用,但它並非可複製的日誌檔案。

簡言之:

在標準的MySQL中:

  • 主節點將寫入binlog並將其發送到副本。

  • 副本接收relay log並將事務應用於引擎。在應用期間,會創建一個新的僅限副本的binlog。

在MySQL Raft中:

  • 主節點通過Raft寫入binlog,並且Raft將binlog發送給跟隨者/副本。

  • 跟隨者/副本接收binlog並將事務應用於引擎。在應用期間創建一個apply log。

  • 從Raft角度來看,Binlog是複製日誌。

使用Raft在MySQL主節點上編寫事務

使用Raft在MySQL主節點上編寫事務的過程如下:首先,事務將在資料庫引擎中準備。這一過程將在使用者連接的執行緒中進行。準備事務的行為涉及與儲存引擎(如InnoDB或MyRocks)的互動,並生成用於該事務的記憶體中的binlog負載。

在提交時,寫入操作將通過組提交/有序提交流程傳遞。GTID(全局事務識別符號)將被分配,然後Raft將為該事務分配一個OpId(term:index)。此時,Raft將對該事務進行壓縮,並將其儲存到其LogCache中,並通過binlog檔案寫入該事務。它將非同步地開始向其他跟隨者發送事務以獲取ACK並達成共識。

請注意,term和index是Raft中的概念,用於唯一標識和順序化事務。

當事務提交時,Raft會通過投票達成共識,並解除使用者執行緒的阻塞。提交的事務將繼續在引擎中執行,並返回結果給客戶端。同時,Raft會非同步地將提交標記發送給其他跟隨者,以便它們也可以應用相同的事務。

崩潰恢復

崩潰恢復

崩潰恢復是與Raft協議無縫協作的關鍵部分。在事務的生命週期中,可能會發生崩潰,因此必須確保成員之間的一致性。以下是他們在崩潰恢復方面的關鍵思路:

  • 事務未刷新到binlog:如果事務在崩潰之前尚未刷新到binlog中,意味著該事務的記憶體負載(在mysqld進程記憶體中作為記憶體緩衝區)將丟失。當進程重新啟動時,引擎中準備好的事務將被回滾。由於Raft日誌中沒有未提交的額外事務,因此不需要與其他成員進行調和。

  • 事務已刷新到binlog但未到達其他成員:在這種情況下,MySQL充當交易協調器,並在參與者之間運行兩階段提交協議,該協議介於引擎和複製binlog之間。在崩潰恢復期間,引擎中準備好的交易將被回滾(引擎尚未提交)。Raft協議將經歷故障轉移,並選出新的領導者。新領導者在其binlog中不會有此交易記錄,因此在前任領導者加入環時,通過推送No-Op訊息來截斷該交易記錄,以實現更高的任期。

  • 事務已刷新到binlog並傳遞給下一個領導者,但當前領導者在提交給引擎之前崩潰:與上述第2點類似,引擎中準備好的交易將被回滾。前任領導者將作為追隨者加入Raft環。在這種情況下,新的領導者將在其binlog中具有此交易記錄,並且不會發生截斷,因為日誌匹配。當新的領導者發送提交標記時,該事務將重新開始應用。

這些措施確保了崩潰恢復的一致性,並保證了事務的正確性。通過與Raft協議的集成,MySQL在高可用性和容錯性方面取得了顯著的進展,使資料庫能夠在崩潰情況下保持穩定運行,並能夠快速恢復。

Raft狀態機轉換

Raft狀態機轉換

Raft啟動狀態機轉換是在故障轉移和常規維護操作中觸發的一項重要任務。一旦Raft選舉出新的領導者,MyRaft外掛會嘗試將相應的MySQL實例切換到主模式。為了完成這個轉換,外掛需要執行一系列協調步驟。

這些步驟涉及從Raft到MySQL的回調操作。它們包括中止正在進行的事務,回滾GTID(全局事務識別符號),從應用日誌切換到binlog,並設置正確的read_only屬性。這一過程相當複雜,並且目前沒有開源的實現可用。

FlexiRaft

FlexiRaft

在傳統的Raft協議中,只有一個全局仲裁者(leader)來處理資料路徑決策。然而,在Meta這樣的大規模環境中,環的規模很大,而資料路徑決策需要更小的仲裁範圍。由於Raft協議和Apache Kudu都只支持單個全局仲裁者,無法很好地適應Meta的需求。

為了解決這個問題,Meta團隊借鑑了Flexible Paxos的一些思路,並創新性地引入了FlexiRaft。FlexiRaft是對傳統Raft協議的改進,以支持更靈活的仲裁機制。

在高層次上,FlexiRaft在資料提交仲裁方面具有靈活性(較小的範圍),同時對領導者選舉仲裁產生較大的影響(更大的範圍)。通過遵循仲裁交集的可證明保證,FlexiRaft確保了Raft協議中最長日誌規則的執行和適當的仲裁交集,從而提供了可證明的安全性。

FlexiRaft支持單區域動態模式,其中成員根據地理區域進行分組。在此模式下,當前的資料提交仲裁取決於當前的領導者(因此稱為「單區域動態」)。資料仲裁由領導者所在地區的投票人數多數派決定。在選舉過程中,如果術語連續,則候選人將與最後已知領導者所在地區相交。FlexiRaft還確保獲得候選人所在地區的法定人數,否則隨後的No-Op訊息可能會被阻塞。在極少情況下,如果術語不連續,FlexiRaft會嘗試找出一組需要與之相交以實現安全性增長的地區;或者在最壞情況下,回退到Flexible Paxos中的N個區域交叉點案例。由於預選和模擬選舉的引入,術語間隙的發生非常罕見。

這些機制使得FlexiRaft在資料提交仲裁和領導者選舉仲裁方面具有靈活性和可證明的安全性,從而更好地適應大規模環境下的需求。它允許更準確地控制資料路徑決策的範圍,並提供了強大的一致性保證。

控制平面操作(促銷和會員變更)

控制平面操作(促銷和會員變更)

為了將晉升(即領導者更改)和會員變更事件序列化到MySQL的二進位制日誌中,Meta團隊對MySQL二進位制日誌格式的Rotate Event(旋轉事件)和Metadata Event(元資料事件)進行了修改。這些事件被用來攜帶Raft協議中的No-Op訊息(無操作訊息)以及添加成員/刪除成員操作的等效內容。由於Apache Kudu不支持聯合共識(即同時處理多個成員變更),因此他們只允許逐個進行成員變更。在一輪中,只能通過一個實體來改變成員身份,以遵守隱式法定交集規則。

通過修改MySQL的二進位制日誌格式,Meta團隊成功地將Raft協議中的晉升和會員變更事件與MySQL的二進位制日誌進行了集成,以確保一致性和可靠性。這樣可以將這些操作持久化並進行復制,從而在集群中保持一致的狀態。

自動化

自動化

通過實施MySQL Raft,Meta為MySQL部署實現了非常清晰的職責分離。MySQL伺服器負責通過Raft的複製狀態機確保安全性,以提供無資料丟失的保證。這個責任被直接集成到伺服器本身中。

為了管理和監控集群的運行狀況,他們還使用了自動化工具,如Python腳本和守護進程。這些工具負責啟動控制平面操作,並監視集群的健康狀態。它們能夠在維護期間或在檢測到主機故障時自動執行成員替換或升級等操作。有時,自動化工具還可以根據需要修改MySQL拓撲的區域佈置方式。

適應Raft協議所帶來的變化,並將這些變化反映到自動化工具中,是一項龐大的工作,需要多年的開發和推出。這是一個長期的過程,需要不斷地最佳化和改進,以確保自動化工具與MySQL Raft的要求保持一致,並能夠可靠地管理和維護整個部署。

在長時間維護事件期間,自動化工具會在Raft上設置領導禁止資訊。Raft將阻止這些被禁止的實體成為領導者,或在意外選舉時及時撤離它們。自動化工具還會將領導權轉移到其他區域,遠離被禁止的區域。這樣做的目的是確保在維護期間,被禁止的實體不會干擾系統的正常運行,並將領導權轉移到可靠的區域,以確保集群的穩定性和可用性。

MySQL Raft 推出的經驗與挑戰

MySQL Raft 推出的經驗與挑戰

在將Raft推廣到整個機群的整個過程中,團隊積累了大量寶貴的經驗和教訓。最初他們在MySQL 5.6上開發了Raft,隨後遷移到MySQL 8.0。

其中一個關鍵的經驗教訓是,儘管Raft協議可以相對容易地確保正確性,但它本身並不能很好地解決可用性問題。由於他們的MySQL資料仲裁非常小(區域內的三個成員中的兩個),若區域內出現兩個故障實體,將會破壞仲裁併導致可用性降低。MySQL機群每天都面臨相當多的變動(如維護、主機故障、平衡操作),因此及時、正確地進行成員變更對於保持持續可用性至關重要。推出過程中,團隊將主要精力放在及時進行日誌傳輸和MySQL替換上,以確保Raft仲裁保持健康。

Meta團隊不得不增強kuduraft以使其更加健壯可用。這些改進並非核心協議的一部分,但可以視為工程附加元件。Kuduraft支持預選舉,但僅在故障轉移期間進行。在領導權的平穩轉移期間,指定的候選人直接進入真正的選舉,並提高任期。這就會給領導者帶來困擾(kuduraft不會自動下臺)。為解決此問題,Meta添加了模擬選舉功能,類似於預先選舉,但僅在領導權平穩轉移時發生。由於這是非同步操作,因此它沒有增加晉升停機時間。模擬選舉將消除真實選舉部分成功並被卡住的情況。

處理拜占庭故障:根據Raft協議,成員列表是由Raft自身認可的。然而,在引入新成員或因自動化競爭而導致兩個不同的Raft環交叉的過程中,可能會出現一些異常情況。這些異常情況會導致出現殭屍成員節點,這些節點需要被清除並與其他成員節點停止通訊。為了解決這個問題,團隊實現了一個功能來阻止這些殭屍成員向環發送RPC請求,從而防止它們對系統造成干擾。這種處理方式可以被視為對拜占庭行為的一種應對方式。一旦團隊注意到在他們的部署中發生了這些罕見事件,他們對Raft實現進行了改進,以增強系統的穩定性和可靠性。

MySQL Raft推出後的監控

MySQL Raft推出後的監控

推出MySQL Raft時,一個重要目標是減少on-call人員的操作複雜性,以便工程師可以更輕鬆地定位和解決問題。為此,團隊建立了多個監控儀表板、CLI工具和scuba表來監視Raft的狀態。在MySQL中增加了大量的日誌記錄,特別是在晉升和成員更改方面。團隊還創建了用於環的法定人數和投票報告的CLI工具,可以快速識別環不可用(法定人數不足)的時間和原因。在工具化和自動化基礎設施方面的投資與伺服器變更的推出密切相關,並且可能比伺服器變更本身更具挑戰性。然而,這項投資帶來了巨大的回報,並大大減少了運維和入職過程中遇到的困難。這樣的投資使得團隊能夠更高效地管理和監控Raft,並及時解決潛在的問題。

Quorum Fixer

Quorum Fixer

儘管並不希望發生這種情況,但有時自動化無法及時檢測到環中的不健康實例或日誌記錄器,並進行及時替換。這可能是由於檢測不足、工作隊列過載或缺乏備用主機容量等原因造成的。儘管這種情況不常見,因為團隊在部署過程中採取了適當的放置策略來隔離關鍵群集實體的故障域,但仍然可能發生意外情況。

為此,Meta開發了Quorum Fixer,這是一個用 Python 編寫的手動修復工具,旨在解決可能導致可用性下降的拆分情況。它在環中禁止寫操作,並進行離線檢查以確定最長日誌實體。然後,它會強制更改 Raft 中的領導者選舉的法定人數期望值,從而選擇該實體作為領導者。一旦成功晉升,我們將法定人數期望值重置回原來的狀態,通常可以使環恢復正常。

我們有意決定不自動運行此工具,因為我們希望能夠發現所有的群集損失情況並修復其中的錯誤,而不是讓自動化默默地解決問題。因此,Quorum Fixer 提供了一種手動的方式來處理這些情況。通過使用該工具,我們能夠更加積極地識別並解決生產環境中的問題。

發佈MySQL Raft

發佈MySQL Raft

推出MySQL Raft對於大規模部署來說確實是具有挑戰性。為了應對這個問題,Meta團隊還開發了一個名為”enable-raft”的工具,使用Python編寫。該工具的目的是協調從半同步到Raft的過渡過程,並在每個實體上載入外掛並設置適當的配置(例如MySQL系統變數)。這個過程中需要短暫的停機時間來完成環境的轉換。

通過多次改進,enable-raft已變得非常穩健,並且能夠快速擴展Raft功能。他們已經成功地使用這個工具來安全地推出Raft,並將其應用於Meta的部署中。該工具在過渡過程中發揮了重要作用,它簡化了從半同步到Raft的切換,並在整個部署中保持穩定性。它是Meta團隊成功實施Raft的關鍵組成部分。

在進行MySQL核心複製管道的更改過程中,測試是非常重要的,因為資料安全性是一個關鍵問題。為了建立信心並確保變更的正確性,我們採用了大量的影子測試和故障注入。通過影子測試和故障注入以及長時間運行的資料正確性檢查,我們能夠更好地評估和驗證MySQL核心複製管道的更改,確保其在生產環境中的可靠性和安全性。

性能表現

性能表現

通過最佳化Raft實現,他們實現了與semisync相當的寫入路徑延遲表現,儘管semisync機制稍微簡單一些。團隊確保在Raft中承擔額外職責時不增加任何CPU負載,使其能夠處理許多之前不屬於伺服器二進位制檔案的任務。

Raft大大改善了晉升和故障轉移的時間。在整個集群中,優雅的領導權轉移佔據了絕大部分比例,並且通常可以在300毫秒內完成。相比之下,在semisync設置中,由於服務發現儲存將是真實來源,客戶端需要更長的時間來注意到晉升的完成,從而導致使用者在片段上的停機時間更長。

在故障轉移方面,Raft通常可以在2秒內完成。通過每500毫秒進行心跳檢測和Raft健康狀況,並在連續三個心跳失敗後開始選舉新的領導者。相比之下,在semisync環境中,這個過程需要進行編排並耗費20至40秒的時間。因此,在故障轉移情況下,Raft可以將停機時間縮短10倍。

總之,通過這些性能最佳化措施,團隊成功地改進了Raft的寫入路徑延遲表現,縮短了晉升和故障轉移的時間,使得在整個集群中的領導權轉移更加快速和優雅。

未來計劃

未來計劃

接下來,Raft將專注於增強MySQL服務功能,實現對MySQL一致性的無需干預管理。通過支持可配置法定人數的FlexiRaft,服務所有者可以選擇在入門時是否需要特定地理位置的複製副本,權衡一致性和延遲。這將為MySQL使用者提供更多靈活性和選擇。

Raft還計劃利用代理功能來節省跨大西洋的網路頻寬。通過僅在美國複製到歐洲一次,然後使用Raft的代理功能進行分發,雖然會增加一些延遲,但可以大大減少橫跨大西洋的傳輸時間。此外,團隊還在探索無領導協議(如Epaxos)和解耦日誌與狀態機的非集成日誌設置,以進一步提升部署和服務的效能和靈活性。

相關文章

震驚!C 語言字串處理有很多坑?

震驚!C 語言字串處理有很多坑?

【CSDN 編者按】毋庸置疑,在使用 C 字串時必須小心,否則你就會因為各種的未定義行為而感到頭疼。 原文連結:https://www.de...

用一個小時編寫一個小程序

用一個小時編寫一個小程序

【CSDN 編者按】要想快速編寫程序,必須要認真考慮技能、工具選擇、攤銷和回報。 原文連結:https://buttondown.email...

Kubernetes 真的很難嗎?

Kubernetes 真的很難嗎?

【CSDN 編者按】Kubernetes 提供了許多開箱即用的好東西,可以推進業務的發展。但這是否意味著,所有服務都要放到 Kubernet...