分散式資料庫事務故障恢復的原理與實踐

OceanBase資料庫發表於2020-10-20
本文根據 OceanBaseDev Meetup#1 上海站分享整理,本次活動針對分散式資料庫的分散式事務以及落地實踐展開具體分享。
本期分享視訊以及 PPT 檢視地址見文末。
本文作者:孔繁宇(景嚴),螞蟻集團技術專家,2016年加入 OceanBase 事務組,參與了 OceanBase 1.0 及 OceanBase 2.0 版本的設計開發工作,目前主要負責 OceanBase 資料轉儲和當機恢復相關的工作。


關聯式資料庫中的事務故障恢復並不是一個新問題,自70年代關聯式資料庫誕生之後就一直伴隨著資料庫技術的發展,並且在分散式資料庫的場景下又遇到了一些新的問題。 本文將會就事務故障恢復這個問題,分別講述單機資料庫、分散式資料庫中遇到的問題和幾種典型的解決方案,以及 OceanBase 在事務故障恢復方面的相關實踐。


從單機資料庫說起


大家都知道,資料庫中事務具有四大屬性:ACID,其中和事務故障恢復相關的屬性是 A 和 D:

  • 原子性(Atomicity):事務內的修改要麼都生效,要麼都不生效;

  • 永續性(Durability):如果資料庫當機,已經完成提交的事務結果不應該丟失;


例如在如下圖所示的兩個事務執行過程中:


在資料庫出現當機時,Trx1 還沒有執行完成,而 Trx2 已經完成提交,原子性和永續性要求在當機恢復後,Trx1 的所有修改都不生效,且 Trx2 的所有修改都必須被持久化。為了達到這個要求,資料庫必須在當機重啟後執行兩個動作:

  • 回滾:移除所有未完成以及回滾事務的修改;

  • 重做:重新執行已經完成提交的事務的修改,確保永續性;



Shadow Paging


一種比較簡單的保證事務原子性和永續性的方法是 Shadow Paging。這個方法非常容易理解,資料庫維護兩個獨立的資料“版本”,分別稱為 master 和 shadow 版本,寫事務的所有修改操作寫入在 shadow 版本上(其他事務讀取僅讀取 master 版本,shadow 版本對讀取不可見),當寫事務提交時,需要在完成提交前將 shadow 版本切換為 master 版本。


當資料庫發生當機重啟時,並不需要做對應的回滾和重做操作(僅回收可能殘留的 shadow 版本資料即可)。

LMDB(Lightning Memory-Mapped Database) 就是一個真正應用了 Shadow Paging 方法的資料庫例子。LMDB 是一個基於記憶體檔案對映的 KV 資料庫,事務修改時採用 Copy-on-write 的方式對 B+tree 索引結構進行修改,當寫事務在修改資料時,會對修改部分 Copy 出新的 B+tree,並在事務提交前,將新的根節點落盤,讀取事務總是從當前生效的最新根節點開始執行。



資料落盤策略


回顧前文,在當機重啟時,必須要執行回滾和重做兩個操作。回滾的目的是消除磁碟上存在的未提交事務的修改,但 Shadow Paging 方法在事務提交前並不會修改 master 版本,所以無需執行回滾操作;重做的目的是將已經提交但是沒有完成落盤的事務修改恢復出來,但 Shadow Paging 方法在事務提交前一定已經將所有修改完成落盤並修改 master 版本,所以也無需執行重做操作。

由此可以看出,事務故障恢復鎖需要執行的操作和事務執行過程中資料落盤的策略是相關的。


資料庫領域中將事務執行過程中資料落盤的策略歸納描述為兩點:

  • Steal/No-Steal:指事務在執行過程中是否允許未提交的事務修改磁碟上的最新資料;

  • Force/No-Force:指事務在提交前是否要求將所有修改落盤;


實現了 Steal 屬性的資料庫系統,需要在當機重啟後做回滾操作以消除未提交事務的修改;實現 No-Force 的資料庫系統,需要當機重啟對已提交事務做重做操作來恢復出未落盤的修改。

Shadow Paging 屬於 No-Steal & Force 的系統,所以當機恢復的過程非常簡單。但是當機恢復過程的簡單是以執行時的複雜為代價的,No-Steal 要求事務在提交前都不能落盤,對大事務不友好;Force 在事務提交時增加了寫盤壓力和延時。通常來說,Steal & No-Force 對注重執行時表現的系統是比較理想的。


Logging


那麼如何實現一個滿足 Steal & No-Force 的資料庫系統呢?接下來我們分析幾種基於日誌的實現方法。


Redo 日誌

如果在事務修改過程中生成 Redo(記錄修改後的新值)日誌,則在當機重啟後,系統可以通過回放 Redo 日誌進行已提交事務的重做過程,但是無法做到未提交事務的回滾,因此,採用 Redo 日誌的系統規則如下:

  1. 對於每一次修改,產生 Redo 日誌記錄(包含修改後的新值);

  2. 事務 Commit 前(Commit 日誌落盤),事務的所有修改不能落盤(No-Steal);

  3. 事務提交成功前,事務的所有日誌記錄(非資料)必須先落盤 (No-Force);


RocksDB 是一個典型的使用 Redo 日誌的例子(暫不討論 WriteUnprepared),事務的寫入在提交前不能落盤,快取在記憶體中事務專屬的 WriteBatch中,當事務確定提交時,首先生成所有修改的 Redo 日誌並落盤,然後才能將 WriteBatch 中的資料寫入到 memtable 中。


Redo 日誌屬於 No-Steal & No-Force 的系統,如前文所述,No-Steal 意味著對大事務執行不友好。

Undo/Redo 日誌

如果在事務修改過程中同時記錄修改前的舊值作為 Undo 日誌(實現中並不一定採用日誌形式),在當機重啟後,系統就擁有了回滾未提交事務的能力,這種做法稱為 Undo/Redo 日誌:

  1. 對每一次修改,產生日誌同時記錄舊值和新值;

  2. 未提交事務允許落盤,在修改落盤之前,對應的日誌記錄必須先落盤(Steal);

  3. 事務提交成功前,事務的所有日誌記錄(非資料)必須先落盤 (No-Force);


大名鼎鼎的 Oracle 資料庫就是採用這種模式,事務的每一次修改都會產生對應的 undo record(記錄在 undo block 中)和 redo record,並且在刷髒頁之前,保證髒頁上對應的未落盤事務日誌必須先落盤;在事務 commit 前,要保證事務的所有日誌落盤完成。


Undo/Redo 日誌屬於 Steal & No-Force 系統,目前絕大多數流行的關聯式資料庫系統都採用了這樣的思路,例如 Oracle、MySQL、PostgreSQL 等。

日誌回收

任何基於日誌的系統都會遇到日誌回收的問題。雖然我們可以保留所有日誌來滿足事務故障恢復的需求,但是日誌空間不能無限的膨脹下去,並且如果在當機重啟時總是從整個資料庫的第一條日誌開始重做,當機恢復的速度也無法滿足系統要求。因此,我們需要一種手段來儘可能的減少當機恢復依賴的日誌數量,這個手段就是 Checkpoint。

一種最為簡單的 Checkpoint 方法流程如下:

  1. 停止所有事務執行(暫停新開啟事務並結束執行中的事務);

  2. 將當前記憶體中所有未落盤的修改落盤;

  3. 記錄當前點為一次生效的 checkpoint;

  4. 恢復事務執行;


這個方法的正確性也很容易理解,因為在第二步之後,磁碟上已經有了完整的資料,不再需要任何日誌。但這個方法的問題也很明顯,就是要停止所有事務執行,這幾乎是無法接受的。

有很多不同的 Checkpoint 方法可以避免這個問題,我們以 Oracle 中的 Media recovery checkpoint 舉例,其過程為:

  1. 取當前 SCN(Redo point);

  2. 通知 dbwr 將當前所有髒頁落盤;

  3. 完成後將 SCN 作為 checkpoint 點更新到元資訊中;



整個過程中不影響正常事務的執行,其正確性的關鍵在於完成髒頁落盤後,Redo point 前日誌對應的修改都完全落盤了,不再需要依賴日誌回放來進行故障恢復。




分散式資料庫帶來的問題



在分散式資料庫中,事務故障恢復的目的仍然是要保證事務的原子性和永續性。和單機資料庫的不同在於,在分散式資料庫中,資料的修改位於不同的節點。


比如在這個例子中,事務的修改涉及到3個不同的節點,當事務要提交時,必須保證3個節點上的資料同時提交,而不能部分提交、部分回滾。


Saga


Saga 是1887年提出的一種把長事務拆小並保證整體事務原子性的方法,也可以用來解決分散式事務的問題。其核心思路是對每個子事務產生對應的“補償事務”,當分散式事務整體提交時,依次提交各個節點上的子事務,如果過程中遭遇失敗,則對已經提交的節點上的子事務執行補償事務回滾已提交的修改。


如上圖例中,事務在3個節點上各自產生一個子事務,在分散式事務提交時提交各個子事務,在第3個節點上提交子事務失敗,需要對另外兩個成功提交的子事務執行補償事務完成回滾操作。

這種方法的優點在於正常提交流程處理簡單,而缺點在於補償回滾過程邏輯處理複雜。


兩階段提交


兩階段提交可能是最為知名的分散式事務原子性解決方案了。兩階段提交,顧名思義,整個事務提交流程分為兩階段來執行:

  • Prepare:協調者通知參與者 Prepare,參與者寫 Prepare 日誌成功後回覆協調者 Prepare ok;

  • Commit:協調者收到所有參與者 Prepare 成功應答後通知參與者 Commit;


每個節點都需要將每個階段的結果記錄在持久化的日誌中,用以恢復自身狀態。


協議流程本身很簡單,兩階段提交協議的核心在於協議應對當機時的處理:當參與者發生當機時,如果參與者還沒有回覆過協調者 Prepare ok,則協調者假定參與者決定回滾;當協調者發生當機時,參與者會按照自己的狀態決定下一步動作。


上圖是兩階段提交參與者的狀態機,如果參與者已經回覆過 Prepare ok(處於 Prepared 狀態),則參與者必須依賴協調者的訊息通知才能決定最終事務狀態,我們稱參與者的這個狀態為“事務未決”。如果此時協調者發生當機,則兩階段提交流程會阻塞。這也是所有應用兩階段提交協議的系統所必須要解決的問題。

應用兩階段提交協議的系統很多,我們以 PG-XC 為例,PG-XC 的資料儲存在不同的 Data Node 上,在分散式事務提交時,通過 Coordinator 執行兩階段提交協議保證多個 Data Node 上事務修改的原子性。


另外,近幾年比較流行的 Percolator 協議,可以看做是兩階段提交協議的變種(Percolator 包含了一套完整的分散式事務解決方案,本文聚焦在其中事務原子性的部分)。Percolator 是 Google 提出的,在僅支援行級事務的 Bigtable 基礎上將單行事務“組合”成多行事務的方案。

當多行事務發起提交時:

  1. 選定其中一行作為"Primary record",將該行寫入到 Bigtable 中,Primary record 上會記錄整個事務的狀態,此時為未提交狀態;

  2. 將其他行作為“Secondary record”分別寫入到 Bigtable 中,其中都包含了 Primary record 的位置資訊,通過查詢 Primary record 上的事務狀態來決定自身狀態;

  3. 修改 Primary record 上的事務狀態為已提交;

  4. 非同步清理 Secondary record 上的狀態;



從兩階段提交協議的角度分析 Percolator,其每行上的事務都是整個分散式事務的參與者,Primary record 相當於協調者,當所有參與者都持久化成功後,修改 Primary record 上事務狀態的過程也就等價於協調者寫的 commit 日誌。





OceanBase 事務故障恢復


OceanBase 採用 share-nothing 架構,資料按照分片規則分佈在各個節點上,每個節點均有自己的儲存引擎,各自管理不同的資料分割槽,每個分割槽通過  Paxos 同步日誌實現高可用,當事務操作一個單獨的資料分片時,執行的是單機事務,當事務操作不同資料分片時,執行的是分散式事務,會遇到分散式事務的原子性問題。


單機事務故障恢復


OceanBase 採用基於 MVCC 的事務併發控制,這意味著事務修改會保留多個資料版本,並且單個資料分片上的儲存引擎基於 LSM-tree 結構,會定期進行轉儲(compaction)操作。

如下圖所示,事務的修改會以新版本資料的形式寫入到記憶體中最新的活躍 memtable 上,當 memtable 記憶體使用達到一定量時,memtable 凍結並生成新的活躍 memtable,被凍結的 memtable 會執行轉儲轉變為磁碟上的 sstable。資料的讀取通過讀取所有的 sstable 和 memtable 上的多版本進行合併來得到所需要的版本資料。


單機事務故障恢復採用了 Undo/Redo 日誌的思路實現。事務在寫入時會生成 Redo 日誌,藉助 MVCC 機制的舊版本資料作為 Undo 資訊,實現了 Steal & No-Force 的資料落盤策略。在事務當機恢復過程中,通過 Redo日誌進行重做恢復出已提交未落盤的事務,並通過恢復儲存的舊版本資料來回滾已經落盤的未提交事務修改。


分散式事務故障恢復

當事務操作多個資料分片時,OceanBase 通過兩階段提交來保證分散式事務的原子性。


如上圖所示,當分散式事務提交時,會選擇其中的一個資料分片作為協調者在所有資料分片上執行兩階段提交協議。還記得前文提到過的協調者當機問題麼?在 OceanBase 中,由於所有資料分片都是通過 Paxos 複製日誌實現多副本高可用的,當主副本發生當機後,會由同一資料分片的備副本轉換為新的主副本繼續提供服務,所以可以認為在 OceanBase 中,參與者和協調者都是保證高可用不當機的(多數派存活),繞開了協調者當機的問題。

在參與者高可用的實現前提下,OceanBase 對協調者進行了“無狀態”的優化。在標準的兩階段提交中,協調者要通過記錄日誌的方法持久化自己的狀態,否則如果協調者和參與者同時當機,協調者恢復後可能會導致事務提交狀態不一致。但是如果我們認為參與者不會當機,那麼協調者並不需要寫日誌記錄自己的狀態。


上圖是兩階段提交協議協調者的狀態機,在協調者不寫日誌的前提下,協調者如果發生切主或當機恢復,它並不知道自己之前的狀態是 Abort 還是 Commit。那麼,協調者可以通過詢問參與者來恢復自己的狀態,因為參與者是高可用的,所以一定可以恢復出整個分散式事務的狀態。

除此之外,OceanBase 還對兩階段提交協議的時延進行了優化,將事務提交回應客戶端的時機提前到 Prepare 階段完成後(標準兩階段提交協議中為 Commit 階段完成後)。


在上圖中(綠色部分表示寫日誌的動作),左側為標準兩階段提交協議,使用者感知到的提交時延是4次寫日誌耗時以及2次 RPC 的往返耗時;右側圖中 OceanBase 的兩階段提交實現,由於少了協調者的寫日誌耗時以及提前了應答客戶端的時機,使用者感知到的提交時延是1次寫日誌耗時以及1次 RPC 的往返耗時。



總結



關聯式資料庫領域雖然歷史悠久,但是仍然充滿了活力。這些年來,隨著硬體的發展,新的技術和思路也不斷的湧現出來,從本文描述的單機資料庫到分散式資料庫中事務故障恢復的的方案,相信大家也都能感受到這些年來資料庫技術的發展是如何一步步適應著硬體的發展趨勢。未來又會怎樣?更大的記憶體、更快速的網路、更廉價的硬碟、甚至是非易失性記憶體的普及,這些變化會給資料庫技術帶來怎樣的可能性?讓我們一起拭目以待。(迫不及待的同學,歡迎加入 OceanBase 團隊,一起創造資料庫技術的未來!)



回顧資料



關於本次分享歡迎留下您的建議:http://oceanbasedev.mikecrm.com/w77l9yx
視訊回顧:https://www.bilibili.com/video/BV1Rk4y1k7Vf
PPT 檢視地址:https://tech.antfin.com/community/activities/1283/review/1011


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69909943/viewspace-2728225/,如需轉載,請註明出處,否則將追究法律責任。

相關文章