技術解讀 | PolarDB-X 強一致分佈式事務

作者:齊木 七鋒

背景

ACID 事務是關係型數據庫一個重要的特性,也是 NewSQL 數據庫最大的挑戰之一。PolarDB-X是一款基於雲架構理念,並同時支持在線事務處理與在線分析處理 (Hybrid Transactional and Analytical Processing, HTAP)的融合型分佈式數據庫產品。因此,對PolarDB-X來說,支持分佈式ACID事務是必須的。

事務ACID概念

數據庫英文爲DataBase,是由Data + Base兩個單詞組成,可以理解爲存放數據的倉庫,最早數據庫的誕生也是爲解決數據的記賬問題,要充分理解ACID的概念,可以從最樸素的轉賬業務看下。

轉賬案例:
A賬戶餘額有100元,B賬戶餘額0元,在這個基礎上A向B轉賬40元,流程如下:
1、查詢A賬戶餘額,看金額是>=40元。
2、滿足條件則先從A賬戶扣款40元(當前A餘額=60、當前B餘額=0)
3、然後再向B賬戶增加40元(當前A餘額=60、當前B餘額=40)

在這個案例中,我們分別看一下ACID在其中扮演的一些要求:

  1. 持久性 (Durability)。持久性是指一旦事務成功提交後,只要修改的數據都會進行持久化,不會因爲異常、宕機而造成賬戶餘額和轉賬信息的丟失。通常處於性能考慮數據庫會進行內存寫入的優化,避免數據的每次操作都進行寫盤操作,數據庫通常會藉助於WAL日誌(比如redo log)來滿足持久性的要求。

  2. 原子性(Atomicity)。一個事務必須是一系列操作的最小單元,這系列操作的過程中,要麼整個執行,要麼整個回滾,不存在只執行了其中某一個或者某幾個步驟。對應到案例中,原子性就代表「檢查餘額、轉賬、到賬」三個步驟,這三個步驟就是一個整體,少了任何一個都不能稱爲一次轉賬。尤其在分佈式事務模型下,多個分片一般會通過2PC策略保持一個狀態協同。

  3. 一致性(Consistency)。事務的一致性要保證數據庫整體數據的完整性和業務的數據的一致性,初看和原子性有一些相似的語義。對應到案例中,一致性會要求業務查詢A/B賬號時看到100/0 或 60/40的餘額狀態。

  4. 隔離性(Isolation)。事務的隔離性是指併發事務操作時,事務之間不會互相影響,初看是和前三個定義不對齊,其定義更多的是從技術實現角度考慮,一直沒有統一的標準,不同數據庫廠商從自己的實現角度會有不一樣的行爲。

原子性和一致性的區別?

原子性和一致性都要保證一個數據的多個操作整體的成功或者失敗,初看兩者有一些相似的語義,其實細究一下還是略微有一些區別,原子性主要面向數據庫Write的行爲定義,而一致性主要面向Read的行爲定義。

拿實際例子來說,在分佈式場景下如果涉及多節點的數據操作,在微觀世界裏就不能保障多節點的操作發生在同一時刻,一定是有細微的時間差,在這個時間差內如果用高併發Read操作是否就會看到轉賬過程中不一致的餘額狀態?因此數據庫技術通常會藉助鎖機制或者MVCC多版本來實現一致性的行爲。比如通常的分佈式2PC,會在第一階段prepare加鎖,確保在第二階段部分節點提交時,剩餘部分節點未提交的數據會通過加鎖,避免高併發Read操作讀到中間提交一半狀態的數據。

隔離級別的技術演進?

隔離級別的發展比較有故事性,不同數據庫產商有各自的實現,缺乏統一的標準。

  1. 在1992年ANSI首先嚐試指定統一的隔離級別標準,當時主要是基於鎖機制來實現事務的併發控制隔離,定義了髒讀、不可重複讀、幻讀的異像,ANSI 92標準中指出可以解決這三類異像的事務隔離稱之爲"可串行化"。

  2. 在1995年,微軟的Jim Gray研究員們在《A Critique of ANSI SQL Isolation Levels》論文中批判了ANSI標準,新增了Lost Update(更新丟失)、Read/Write Skew(讀寫偏序)的異像場景,同時正式提出基於MVCC的快照隔離級別SI。而當時的Oracle是第一個在商業數據庫中應用MVCC的技術,將其基於MVCC的SI隔離技術稱之爲"可串行化",主要是因爲ANSI 92標準中對解決不可重複讀、幻讀異常定義的技術稱之爲可串行化,屬於特定的文字遊戲。

  3. 在1999年,在《Generalized Isolation Level Definitions》論文中提出了有向串行圖DSG(Direct Serialization Graph)的隔離定義,可以在SI隔離級別的基礎上解決Read/Write Skew(讀寫偏序)的異像問題,稱之爲SSI(Serializable Snapshot Isolation),主要的代表數據庫爲PostgreSQL/CockroachDB。

後續可以專門開一篇介紹隔離級別,這裏就不詳細展開具體的技術差異和實現細節。

分佈式事務模型

在正式介紹PolarDB-X的分佈式事務之前,也是非常有必要了解下當前世界上幾種主流的分佈式事務策略,因爲關係數據庫從80年代誕生開始,事務是區別於關係型數據庫和NoSQL最本質的地方,衆多學者一直在不斷研究和優化事務的技術,尤其在互聯網技術發展之後,數據出現爆炸式增長,分佈式逐漸進入大家的視野。

Percolator模型

Percolator模型最早是在2010年,由Google工程師在解決索引增量構建的工程方案中第一次提出,具體可參見:《Large-scale Incremental Processing Using Distributed Transactions and Notifications》。Google Percolator實現上是在Bigtable基礎上,在不改變 Bigtable 自身實現的前提下,通過行級事務和多版本控制,實現了 Snapshot Isolation 級別的跨行事務能力。

Percolator具體的實現細節網上已有蠻多的文章介紹,這裏就不做詳述,列舉一下其設計的重點:

  1. 引入全局時鐘 (timestamp oracle,簡稱TSO)。基於全局時鐘分配一個全局單調增加的版本,實現分佈式下事務多版本的事務id分配,在事務的開始start_ts和提交commit_ts進行可見性和衝突的判斷。

  2. 基於KV的數據結構。爲了支持分佈式2PC的原子性提交,數據結構上將每一行上每個列的值都設計爲data、write、lock的三個value,通過這樣的數據結構滿足事務一致性和隔離機制。

Google Percolator模型也有一些已知缺陷:提交階段延遲較高,僅支持樂觀鎖場景、衝突錯誤只能在提交時彙報等。這與傳統的關係數據庫基於悲觀事務的模型以及秒殺更新性能的還是有比較大的區別。

Omid模型

Omid模型是Yahoo的作品,同樣也是構建在類似HBase的KV基礎上,但和Percolator的Pessimistic方法相比,Omid是一種Optimistic的方式,正如其名《Optimistically transaction Management In Datastores》。其架構相對優雅簡潔,工程化做得也不錯,近幾年接連在ICDE、FAST、PVLDB上發了多篇論文。

Omid認爲Percolator的基於Lock的方案雖然簡化了事務衝突檢查,但是將事務的驅動交給客戶端,在客戶端故障的情況下,遺留的Lock清理會影響到其他事務的執行,並且維護額外的lock和write列,顯然也會增加不小的開銷。而Omid這樣的Optimistic方案完全由中心節點來決定Commit與否,強化了中心節點TSO的能力,記錄一個lastCommit事務的WriteSet內容,在事務提交的validate階段進行事務衝突判斷。

Calvin模型

Calvin模型最早是在12年提出來的概念,《Calvin: Fast Distributed Transactions for Partitioned Database Systems》。相比於傳統的數據庫,Calvin是一個另類思維的deterministic database,對於需要處理的事務,Calvin 會在全局確定好事務的順序,並按照這個提前確定的順序執行。對於這些事務的執行順序,我們可以認爲是一個全局的有序 log,分佈式下的多個分片只需要嚴格按照這個全局log進行當前分片的執行操作,每個分片的副本結合常規的主備或者Paxos/Raft複製機制保障持久化。

VoltDB是確定性數據庫的工程化的典型代表,經過研究發現(《OLTP Through the Looking Glass, and What We Found There》)目前數據庫事務只有不到10~20%的指令代價在處理數據本身,其餘的大部分都是在鎖管理、WAL、緩存管理上。因此,VoltDB設計爲一個確定性的全內存數據庫,基於確定性的事務配合串行化的執行,規避了事務併發的大部分開銷,從而可以達到單機50萬+的性能。

VoltDB具體的實現細節網上已有蠻多的文章介紹,其另一個最大的設計上引入了可編程的存儲過程,把交互式的事務設計的業務邏輯一次性打包發送到數據庫上,由數據庫進行確定性定序和執行。

XA模型

XA 協議全名爲 X/Open XA 協議,是一項通用的事務接口標準,最早在90年代開始提出。

X/OPEN是一個組織,它在規範中定義了分佈式事務處理模型,簡稱DTP(X/Open Distributed Transaction Processing Reference Model)。該DTP模型中,主要包含3種角色:AP(Application,應用程序)、RM(resouces manager資源管理器,通常指單個數據分片)、TM(transaction manager事務管理器,通常指事務協調者,提供給AP變成接口以及管理資源管理器)。

XA 協議則是在DTP模型下定義的一套標準交互接口,比如常見的有:xa_start、xa_end、xa_commit、xa_rollback等。目前主流的商業數據庫基本都實現了XA協議,比如Oracle、MySQL等。

XA 協議主要基於2PC兩階段提交實現事務的原子性,而分佈式下的一致性則需要更多的設計。以MySQL XA爲例,如果應用DTP模型的分佈式交互,需要將MySQL的隔離級別調整爲serializable纔可以滿足一致性的要求,主要的原因是分佈式下MySQL還是基於serializable對讀加鎖來提供了分佈式的一致性,因此會有比較大的性能影響。

在分佈式場景下,目前主流優化DTP模型下的分佈式一致性技術,主要就是分佈式MVCC(可以看下傳統數據庫的發展),在MVCC的實現上目前主要有兩種架構:

  1. GTM(全局事務管理)+MVCC。最典型的代表就是PGXC,其設計上是引入了全局活躍事務鏈表,每個分佈式事務開始和結束都需要和GTM進行交互,維護一個分佈式的全局活躍鏈表。在數據存儲上引入多版本的技術,可以滿足分佈式下的MVCC一致性。

  2. TSO(全局時鐘服務)+MVCC。最典型的代表就是PolarDB-X,設計上引入全局時鐘服務,配合數據存儲的多版本技術,可以滿足強一致的事務要求。

PolarDB-X 分佈式事務

使用體驗

終於進入正題,我們來看一下PolarDB-X如何支持分佈式事務。

首先看一下PolarDB-X面向用戶的使用,可以完全和單機的事務模型保持一致。

實現技術

PolarDB-X分佈式事務的主要技術:

  1. 持久性:Paxos

  2. 原子性:2PC + XA

  3. 隔離性 & 一致性:TSO + MVCC

我們簡單來分解一下技術,並做一下實現解讀。

Paxos

PolarDB-X的存儲引擎主要基於WAL + 多數派Paxos協議。

PolarDB-X分佈式事務中涉及到的每個DN節點都會有多副本的支持,在返回用戶事務提交成功後,如果有異常掉電、硬盤異常等場景,可以通過Paxos多數派機制自動完成切換,可滿足RPO=0,在數據庫節點重啓恢復時,結合WAL的日誌可以確保恢復到正確的狀態。

2PC + XA

PolarDB-X是存儲計算分離的設計,分佈式DTP模型裏的RM模型就是PolarDB-X裏的存儲節點DN,TM模型就是PolarDB-X裏的計算節點CN。

PolarDB-X DN通過Paxos保證了服務的高可用,CN本身不存儲數據,它是一個無狀態的服務,可以通過常規的負載均衡設備提供高可用能力,比如Lvs/HaProxy/F5等。每個應用AP發起的分佈式事務,路由到了其中一個CN後,當前這個CN就會承擔着當前事務的TM角色的工作,負責事務狀態和日誌的跟蹤,會持久化事務日誌。如果當前CN故障後,會由另外的CN節點基於持久化的事務日誌進行接管,解決2PC協議中的TM狀態不一致問題。

PolarDB-X CN節點(作爲TM事務管理器),會通過XA協議接口和PolarDB-X DN節點(作爲RM資源管理器)進行事務交互,比如在事務提交時,會有如下的交互流程:

  1. CN節點在接收到用戶客戶端的事務commit事件

  2. CN節點對所有事務參與DN分片,發起第一輪的xa prepare交互 (確認所有的參與者是否認可當前事務的提交,如果認可提交則需要持久化部分的狀態到WAL,保證持久性)

  3. CN節點收到所有DN返回prepare ok後,發起第二輪的xa commit交互 (提交每個DN節點事務的中間狀態)

2PC流程整體不算複雜,但需要重點考慮故障容錯,比如用戶請求、CN節點、DN節點在各個環節多會有異常crash的風險。PolarDB-X目前結合CN/DN本身的高可用設計,可以很好地支持2PC的故障容錯。

TSO + MVCC

全局時鐘需要一個生成全局單調遞增 Timestamp 的策略,常見的策略有 True Time(Google Spanner)、HLC(CockroachDB)、TSO(TiDB),我們目前的實現中使用了 TSO 策略,由 GMS(元數據管理服務)作爲一個三節點的高可用服務承擔生成 Timestamp 的任務。TSO 保證了正確的線性一致性和良好的性能,只是在跨全球機房部署的場景會帶來較高的延遲,後續我們也會有文章專門介紹跨機房部署下的TSO優化策略。

時間戳格式
時間戳格式採用物理時鐘+邏輯時鐘方式,物理時鐘精確到毫秒。

MVCC

我們在MySQL的單機MVCC多版本基礎上,擴展支持了分佈式的MVCC能力,我們在MySQL存儲上除了其自有的txid基礎上,新增了commit timestamp的概念(簡稱cts),這個cts的內容都是由PolarDB-X CN節點在做事務操作時傳遞給PolarDB-X DN節點,在write操作中寫入到存儲,在read操作時帶着一個查詢的cts和存儲記錄裏的cts做大小比較,最終決定數據的可見性。

我們除了在PolarDB-X DN節點的leader主副本中保存cts信息之外,也會將存儲帶cts的信息同步到follower/learner副本中,這樣可以很好地支持多副本的一致性讀。比如,我在主副本里寫入了一個cts=100的數據,我在下一次的查詢中帶着cts=101,查詢路由到只讀的learner節點,即使learner節點有數據複製延遲,我們也可以實現阻塞讀來滿足一致性。這部分的技術優化,將會在後續的PolarDB-X HTAP技術細節中深度展開。

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