PolarDB-X 存儲架構之“基於Paxos的最佳生產實踐”

作者:七鋒

背景

MySQL數據庫從誕生以來就以其簡單開放、易用、開源爲其主打的特點,成爲不少開發者首選的數據庫系統。阿里在09年開始提出去IOE的口號,也選擇基於開源MySQL進行深度發展,結合TDDL的技術完成了去IOE的工作,這也是早期的PolarDB-X發展的技術棧。2014年開始,隨着業務高速的增長,以及“異地多活”的新需求驅動,基於MySQL的一致性協議技術X-Paxos在阿里集團得到了全面的發展和驗證,可參見:PolarDB-X 一致性共識協議 (X-Paxos)

從19年開始,PolarDB-X 2.0結合了分佈式SQL引擎和基於X-Paxos的數據庫存儲技術,基於阿里集團多年雙十一的技術積累和穩定性驗證,以雲的方式提供雲原生分佈式數據庫產品,爲傳統企業、金融業務數字化轉型和去IOE過程提供更好的技術產品和服務。

架構設計

上圖展示的是一個部署三個節點的存儲集羣,設計上引入了多分組X-Paxos技術替換傳統的複製協議,上圖中Paxos Group 0~N代表不同的X-Paxos分組,基於多分組技術可支持多點寫入+多點可讀的能力,同時分佈式下多個分片可以屬於不同的Paxos分組。

多分組Paxos

我們在設計上,在同一個物理節點中允許管理多個X-Paxos實例。每個節點上基於分佈式的數據分區(Partition),將不同分區的數據綁定到某一個X-Paxos實例,通過將每個X-Paxos實例的Leader分散到多個物理節點中,從而提供不同分區數據的多點可寫能力,解決了傳統MySQL數據庫下的單點寫瓶頸的問題。

多分組Paxos相比於單分組的Paxos,並不是簡單的啓動多份X-Paxos實例,而是需要做一定的合併優化,來降低多分組Paxos所帶來的資源開銷,主要包含三個模塊的優化:消息服務、分區管理、協調者。

1. 消息服務:多分組引出兩個風險,連接風暴和消息風暴。連接風暴是指,假設沿用原有單分組X-Paxos架構的設計,如果一個集羣有3個節點,每個節點有3個分組,那麼單節點需要監聽3個不同的端口,並維持6個獨立的連接。多分組X-Paxos消息服務提供一套共享的網絡層,多個分組之間複用同樣的網絡連接。消息風暴是指,假設同步的總量不變,數據由多節點分散寫入會讓單個網絡包變小,同時每個分組的Leader還會定期維持租約心跳,當分組數變多之後,網絡會充斥大量小包導致收發包質量降低。因此共享的消息服務需要提供日誌聚合和心跳聚合的能力。此外,我們還可以通過共享Timer模塊讓同一節點上多個分組的Leader共享任期,減少Leader租約管理的成本。

2. 分區管理:分區管理模塊維護X-Paxos分組和數據分片的映射關係。多分組X-Paos對接分佈式下的數據分區,存儲上收到計算層發送的DML操作後,以物理庫或表做爲分區鍵(Partition Key)傳遞到Consensus層。Consensus層接收字符串類型的分區鍵,轉換成對應X-Paxos分組的Group ID。其中Hash Table模塊提供快速的查詢能力,Meta Store模塊負責映射關係的持久化。當映射關係出現改變時,Hash Table會把最新的變更同步給Meta Store。Meta Store提供統一的接口,數據持久化是藉助InnoDB引擎的MySQL系統表來保證數據修改的原子性和持久性。如上圖所示,Consensus層保留了獨立性和通用性,不依賴分佈式下的分區邏輯,管理分區鍵和Group ID的關係,並驅動元數據表的同步更新。

3. 協調者:當集羣需要負載均衡時,我們可能會增加新的X-Paxos分組,還會把一部分數據分片從某個分組切換到新的分組中。這些分區管理模塊的修改行爲稱爲分組變更(Group Change)。需要考慮以下幾個問題:如何確保同一數據分片不會同時屬於兩個分組,即跨Group信息的分佈式一致?如何讓集羣中多個實例的分區元數據一致,防止雙寫造成數據衝突?如何保證在做分組變更的過程中,任意分組內部的節點變更(Membership Change,如切主、加減節點)不會影響分組變更的正確性?這類問題一般有兩種解決方案,集中式和分佈式。前者是引入一個外部的配置中心(如Placement Driver/PD),通過集中化的單點管理規避掉分佈式一致性的問題。該方案的缺點是有單點故障的風險,因此一般情況下,PD也會部署成主備甚至三節點的形式,藉助冗餘來提高可用性,這又進一步增加了系統交付的成本。在PolarDB-X中,我們採用的是分佈式的方案,集羣中每一個節點既是參與者,也可以是協調者。通過兩階段日誌同步,解決跨X-Paxos分組分佈式一致性的問題。此外,Paxos協議保證了參與者和協調者的高可用,傳統2PC協議中協調者或參與者宕機引出的一系列問題都自然而然地規避掉了。

事務提交和複製

基於MySQL發展的存儲節點DN,其複製流程是基於X-Paxos驅動Consensus日誌進行復制。Leader節點在事務prepare階段會將事務產生的日誌收集起來,傳遞給X-Paxos協議層後進入等待。X-Paxos協議層會將Consensus日誌高效地轉發給集羣內其他節點。當日志在超過集羣半數實例上落盤後 X-Paxos會通知事務可以進入提交步驟。否則如果期間發生Leader變更,期間prepare的事務會根據Paxos日誌的狀態進行相應的回滾操作。

Follower節點也使用X-Paxos進行日誌的管理操作,爲提升接收效率,收到Leader傳遞過來的日誌以後將日誌內容Append到Consensus Log末尾,Leader會異步地將達成多數派的日誌的消息發送給Follower,Follower的協調者線程會負責讀取達成多數派的日誌並加以解析,並傳遞給各個回放工作線程進行併發的數據更新。Follower的併發回放可以有多種方式,包括按照Leader上的Group Commit維度或者是按照表級別的維度,未來會引入最新的writeset方式來精確控制最大併發。

相比於傳統的MySQL基於binlog的semi-sync複製模式,我們引入X-Paxos做了比較多的優化。

1.異步化事務提交。傳統的MySQL都是 One Thread per Connection的工作模式, 在引入線程池後是以一個線程池孵化一定數量的工作線程, 每個線程負責處理一次query的解析、優化、修改數據、提交、回網絡包等等。集羣需要跨地域部署下,一次事務的提交由於需要在集羣之間同步事務日誌,受限於網絡的RTT的限制,會達到數十毫秒的級別,那麼對於一個普通的寫事務來說,大量的時間會耗費在同步節點日誌等待事務提交的過程。在大壓力下,很快數據庫內部的工作線程會耗盡, 吞吐達到瓶頸。如果一味的放大數據庫內部的工作線程數目,那麼線程上下文的代價會大幅增加。如果將整個事務的提交異步化,將工作線程從等待X-Paxos日誌同步中解放出來,去處理新的連接請求,在大負載下可以擁有更高的處理能力。

異步化提交核心思想是將每個事務的請求分成兩個階段,提交之前一個階段,提交和回包一個階段。兩個階段都可以由不同的工作線程來完成。爲了完成異步化的改造,我們增加了等待同步隊列和等待提交隊列,用於存儲處於不同階段的事務。前者隊列中的事務是等待Paxos多數派日誌同步的事務,後者是等待提交的事務。

異步化流程:
a. 當CN節點發起Commit/Rollback/Prepare時, 處理客戶端連接的線程池Worker產生事務日誌並將事務上下文存儲到等待同步的隊列中。
b. 等待同步隊列的消費由少量數目的worker線程來完成,其餘工作線程可以直接參與其他任務的處理。事務等待多數派完成後會被推入等待提交隊列。
c. 等待提交隊列裏的事務都是可以被立即提交的,所有的worker線程發現該隊列裏有事務,就可以順道取出來執行提交操作。

這樣一來,系統中只有少數的線程在等待日誌同步操作, 其餘的線程可以充分利用CPU處理客戶端的請求,異步化提交結合MySQL的Group Commit邏輯,將原有需要等待的操作全部異步化,讓Worker線程可以去執行新的請求響應。在測試中,異步化改造在同城部署的場景中相比同步提交有10%的吞吐率提升,跨區域的部署後有500%的吞吐提升。

2.熱點更新優化。熱點更新原本就是數據庫的一個難題,受制於行鎖競爭,性能吞吐一直很難提升上去。X-Paxos下面對跨域場景下的長傳網絡更加是雪上加霜,提交的時間變長,事務佔據行鎖的時間也顯著增加。

爲了解決此問題,PolarDB-X在AliSQL的熱點功能之上優化了複製,使得在保證數據強一致的情況下,熱點更新性能提升非常明顯。

如上圖所示,針對熱點行更新的基本思路是合併多個事務對同一行的更新。爲了讓批量的更新事務能夠同時進行提交,PolarDB-X在存儲引擎中增加了一種新的行鎖類型——熱點行鎖。熱點行鎖下,熱點更新的事務之間是相容的,爲了保證數據的一致性,對同一批的熱點更新事務日誌打上特殊tag, X-Paxos會根據tag將這一整批事務的日誌組成一個單獨的網絡包進行集羣間的數據同步,保證這些事務是原子的提交/回滾。除此之外爲了提升日誌回放的效率,PolarDB-X將每個批次事務中對於熱點行的更新日誌也做了合併。

多副本配置和部署

PolarDB-X設計目標是支持跨地域部署,在多地域保證集羣數據強一致,即使某個城市的機房全部宕機,只要保證集羣多數派節點存活,所有的數據都不會丟失。我們在部署方式設計上也比較靈活,支持容災成本和多樣化的部署需求。

  1. 選主優先級。應用往往對於容災後新主節點是有要求的,比如機房流量均衡、以及應用和地域之間的關聯性(比如新疆業務的應用,期望對應的分區數據在新疆機房)。針對這樣的需求,PolarDB-X可以根據不同的分區優先級,在分區級別X-Paxos級別設置不同的優先級,在原有Leader節點故障時,選舉Leader的順序會按照集羣存活節點的權重順序進行,同時在運行過程中如果發現有權重更高的節點,會主動發起一次Leader Transfer將Leader角色過繼過去。

  2. 策略化多數派。一致性複製可分爲兩檔,強複製和弱複製。可以搭配選主優先級,比如設置同城機房的節點爲強同步複製,我們可以配置在規定日誌複製到多數節點的基礎上必須還要複製到了所有強複製的節點纔可以推進狀態機並返回客戶端事務提交成功的響應。這是一個類Max protection模式的設計,如果檢測到強一致節點宕機,可自動降級。

  3. 日誌型副本。默認三副本的機制,相比於傳統的主備模式,會在存儲成本上會多一份數據。PolarDB-X在副本設計上分爲普通型(Normal)和日誌型(Log)兩類。日誌型節點可以和普通型節點組成Paxos的一致性投票,不過它只有投票權,沒有被選舉權。通過日誌型副本只記錄Paxos日誌,在滿足RPO=0的前提下,也可以和普通的主備模式的存儲成本對齊。

  4. Follower Read。針對只讀Leaner節點在跨機房部署下,如果所有Leaner節點的日誌都從Leader節點複製會對Leader節點造成過大的網絡和IO瓶頸,PolarDB-X也支持Leaner掛載到Follower節點獲取一致性數據。

  5. 多副本一致性讀。在分佈式環境下,基於Paxos的副本相比於主副本會有一定的Apply延遲,如果要實現多副本的線性一致性讀時,需要有一定的保證機制。PolarDB-X裏會有兩種一致性讀的方案。第一種是基於Paxos的LogIndex來,首先在Leader節點上獲取一下index,然後觀察follower/leaner副本的LogIndex是否已經超過,如果超過說明所需要的數據已經在了,可以直接讀取。第二種是基於TSO全局版本號,查詢的時候獲取一個全局版本,指定版本在follower/leaner副本中進行讀取,在存儲上我們支持版本的阻塞讀能力,用戶可以設置等待時間直到對應版本數據同步到該副本上。這兩種方案,前者只滿足RC級別的當前讀,後者可滿足RR級別的歷史讀,結合PolarDB-X的HTAP架構,可以分流部分AP查詢到只讀副本上,並滿足事務RC/RR的隔離級別。

推薦的兩種部署:

高可用檢測和恢復

PolarDB-X 引入Paxos的一致性協議,基於Paxos算法已有的租約自動選舉的策略,可以避免“雙主”的問題出現。如果當前Leader被網絡隔離,其他節點在租約到期之後,會自動重新發起選主。而那個被隔離的Leader,發送心跳時會發現多數派節點不再響應,從而續租失敗,解除Leader的狀態。Follower約定在lease期間不發起新的選主,Leader先於Follower lease超時,從時序上最大程度上規避了“雙主”問題的出現。

除了常規的租約策略之外,考慮雲環境的各種異常情況,我們還需要有更進一步的優化:

1.狀態機診斷。從數據一致性角度來說,選主流程結束後,新的Leader必須回放完所有的老日誌才能接受新的數據寫入。因此,成功選主並不等價於服務可用,實際的不可用時間是選主時間(10s)和日誌回放追平時間之和。在遇到大併發和大事務場景下,Follower可能會產生比較大的回放延遲。假如此時恰好主庫出現故障,新選主的節點由於回放延遲,服務不可用時間充滿了不確定性。

故障有可恢復和不可恢復之分,通過我們觀察,除了那種機器宕機、磁盤壞塊這類徹底恢復不了的場景,大部分故障都是短期的。比如網絡抖動,一般情況下網絡架構也是冗餘設計的,可能過一小段時間鏈路就重新正常了。比如主庫OOM、Crash等場景,mysqld_safe會迅速的重新拉起實例。恢復後的老主一定是沒有延遲的,對於重啓的場景來說,會有一個Crash Recovery的時間。這個時候,最小不可用時間變成了一個數學問題,到底是新主追回放延遲的速度快,還是老主恢復正常的速度快。因此,我們做了一個狀態機診斷和主動切換的功能。在數據庫內核中,通過狀態機診斷接口,可以收集回放延遲、Crash Recovery、系統負載等狀態。當狀態機健康狀況影響服務可用性時,會嘗試找一個更合適的節點主動切換出去。主動切換功能和權重選主也是深度整合的,在挑選節點的時候,也會考慮選主權重的信息。最終,服務恢復可用後診斷邏輯會自動停止,避免在穩定Leader的情況下產生不必要的切換。

2.磁盤探活。對於數據庫這樣有狀態的服務來說,存儲是影響可用性的重要因素之一。在本地盤部署模式下,數據庫系統會遇到Disk Failure或者Data Corruption這樣的問題。我們曾經遇到過這樣的情況,磁盤故障導致IO卡住,Client完全無法寫入新的數據。由於網絡是連通狀態,節點之前的選舉租約可以正常維持,三節點自動容災失效導致故障。有時候還會發生一些難以捉摸的事情,由於IO已經完全不正常了,進程在kernel態處於waiting on I/O的狀態,無法正常kill,只有重啓宿主機才能讓節點間通信完全斷掉觸發選主。

針對這類問題,我們實現了磁盤探活功能。對於本地盤,系統自動創建了一個iostate臨時文件,定期向其中執行隨機數據讀寫操作。對於雲盤這類分佈式存儲,我們對接了底層的IO採樣數據,定期來感知IO hang或者Slow IO的問題。探測失敗次數達到某個閾值後,系統會第一時間斷開協議層的網絡監聽端口,之後配合重啓實例可以恢復。

3.反向心跳。基於已有的策略,我們已經可以覆蓋99%的常規可用性問題。在長時間的線上實踐中,我們發現有些問題從節點內部視角發現不了,比如主庫連接數被佔滿,open files limit配置不合理導致“Too many open files”報錯,以及代碼bug導致的各種問題......對於這些場景,從選舉租約、狀態機、磁盤探活的角度,都無法正確的檢測故障,因此最好有一個能從App視角去建連接、執行業務查詢和返回結果的全鏈路檢測流程。因此催生了Follower反向心跳的需求,即Follower通過業務查詢SQL接口去主動探測Leader的可用性。該設計有兩個優勢:首先是內核自封閉,藉助三節點的其他非Leader節點,不依賴外部的HA agent進行選主判定,就不用再考慮HA agent本身的可用性問題;其次和內核邏輯深度整合,不破壞原有的選主邏輯。

整個流程如圖所示,假設Node 1是Leader,給其他兩個Follower正常發送心跳,但是對外的App視角已經不可服務。當Node 2和Node 3通過反向心跳多次嘗試發現Leader的SQL接口不可服務之後,這兩個Follower不再承認Leader發來的Heartbeat續租消息。之後若Node 2的選舉權重相對較高,他會首先超時,並用新的term發起requestVote由Node 3投票選成主,對外開始提供服務。這個時候Node 2作爲新主,也會同時開始給Node 1和Node 3發續租心跳。Node 1在收到新主發來的心跳消息之後,發現term比自己當前term大,就會主動降級成Follower。整個三節點又能回到正常的狀態。

總結

PolarDB-X融合了基於X-Paxos的數據庫存儲技術,通過經歷阿里集團多年雙十一的技術積累和穩定性驗證,PolarDB-X在穩定性、易用性、高可用特性上都會有不錯的表現。未來,我們也會在Paxos副本在多節點混部和遷移、跨地域容災的Paxos Quorum自動降級、Geo-Partition特性、以及分佈式熱點分區優化上做更多的探索和嘗試,給用戶提供更好的分佈式數據庫體驗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章