PolarDB-X 存儲引擎核心技術 | Lizard 分佈式事務系統

PolarDB-X 分佈式數據庫

分佈式數據庫架構

關係型數據庫作爲支撐企業級數據的在線存儲方案,發揮了無可替代的作用。隨着海量數據的增長,以及面對創新業務爆發性增長的場景,如何能夠快速,業務無損的進行在線數據庫擴容,對數據庫的架構提出了巨大的挑戰,除此以外,企業的精細化經營,也要求數據庫能夠一站式提供事務處理能力和數據分析能力,爲了應對這些挑戰,分佈式數據庫應運而生,相比着傳統的事務型數據庫,分佈式數據庫着力解決的幾個核心技術問題:

  1. 能否快速進行水平拆分,線性擴展事務處理能力
  2. 能否實現業務無損,像使用單機數據庫一樣,保證 ACID 特性
  3. 能否保證業務持續可用,實現企業級容災能力
  4. 能否擴展多種結構化數據,應對靈活的事務處理和分析處理混合負載

分佈式數據庫的蓬勃發展,在技術的選型上,也經歷了不同的階段和技術發展路徑:

1 基於中間件的 Sharding 分案

在分佈式數據庫發展的早期,使用數據庫中間件實現數據的分片和路由,達到水平擴容的能力,高效的支撐業務的爆發增長。但這種方案使用的中間件部署在應用端,所有的數據分片變動,都要業務感知和配合,同時在分片之間也無法實現數據庫的 ACID 特性,所以,中間件的 Sharding 方案只解決了部分數據寫入能力的擴展,無法形成一個完整的分佈式數據庫。

2 基於共享資源池的 Scale Up 方案

在提升擴展能力方面,雖然水平線性擴展能力的 Scale Out 方案是終極形態,但階段性的 Scale Up 方案能夠輕量級的實現有限擴展能力,滿足一個時期的要求。另一方面,數據庫的雲原生化 ( Cloud-Native) 的發展要求的極致彈性能力,同樣需要基於資源的池化,形成了以 PolarDB,Aurora 爲代表的 Cloud-Native 數據庫,不斷的分層資源池化,帶來不斷的彈性能力,並形成擴展能力。

3 基於 Share Nothing/Everthing 的水平擴展方案

在具有分佈式計算和分佈式存儲能力之上,形成的 Share Nothing/Everthing 的方案,在不損失單機數據庫所具有的 ACID 能力以外,能夠做到對業務無感知的透明水平擴容能力,並能夠在分佈式協議的基礎上,實現業務的持續可用,例如 PolarDB-X, OceanBase,TIDB 等。

PolarDB-X 簡介

雲原生數據庫 PolarDB 分佈式版,即 PolarDB-X 是阿里巴巴自主設計研發的高性能雲原生分佈式數據庫產品,爲用戶提供高吞吐、大存儲、低延時、易擴展和超高可用的雲時代數據庫服務,PolarDB-X 具有:

  1. 雲原生化,基於存儲計算分離的 Share Nothing 架構,實現極致的彈性能力和水平擴展能力
  2. 透明分佈式,以單機數據庫的體驗,操作分佈式數據庫
  3. 自研的 Lizard 分佈式事務系統,保證 ACID 特性和全局一致性
  4. 自研的分佈式複製協議 X-Paxos,保證業務持續可用
  5. 高效的交互協議 X-Protocol,實現請求流水線處理
  6. 行列混合存儲,實現 HTAP 處理能力
  7. 全面開源,擁抱 MySQL 生態,對 MySQL 全面的兼容

PolarDB-X 架構

PolarDB-X 採用了 Timestamp Oracle (TSO) 架構實現全局授時服務,由集羣 GMS 提供,CN (Computing Node) 節點作爲入口,除了提供基礎的數據分片和路由功能,並實現了強大的執行引擎能力,DN (Data Node),提供了基礎的數據存儲服務,並提供分佈式事務和高可用能力,CDC (Change Data Capture),作爲數據流通道,提供了數據生態能力,PolarDB-X 總體架構如圖所示:

PolarDB-X 分佈式存儲引擎

作爲 DN 和 GMS 節點的存儲引擎,其在 MySQL 開源生態的基礎上,擴展和自研了大量的核心能力,有效的支撐 PolarDB-X 分佈式數據庫產品, 其中包括:

  1. 連續遞增的全局自增列
  2. 全局有序的TSO發號器
  3. 高效穩定的分佈式事務
  4. 安全嚴格的全局一致性
  5. 持續可用的分佈式協議
  6. 持久可靠的多副本存儲

SEQUENCE 引擎

業務背景

在數據存儲中,唯一遞增的序列號是一個通用服務,通常用作 1) 數據的有序存儲,2) 節點或全局的事件定序。 PolarDB-X 存儲引擎實現一個 Sequence 服務,其通過一個邏輯引擎的方式來實現。

使用語法

Sequence 包括一系列屬性,包括:起始值 (start value), 最小值 (min value), 最大值 (max value), 步長 (increment by), 緩存 (cache / nocache), 循環 (cycle / nocycle), 其語法如下:

1.創建 SEQUENCE

CREATE SEQUENCE [IF NOT EXISTS] <數據庫名>.<Sequence名稱>
   [START WITH <constant>]
   [MINVALUE <constant>]
   [MAXVALUE <constant>]
   [INCREMENT BY <constant>]
   [CACHE <constant> | NOCACHE]
   [CYCLE | NOCYCLE];

2.訪問 SEQUENCE

SELECT Nextval(seq);
SELECT Currval(seq);

實現原理

邏輯引擎

Sequence 底層使用 InnoDB 存儲引擎來保存這些屬性,所以,Sequence Engine 被定義成一個邏輯引擎,負責sequence 的緩存策略和訪問入口,真實的數據保存在 InnoDB 表中。

自治事務

爲了保證 Sequence 的唯一特性,Sequence 窗口的滑動,涉及到對底層數據的修改,這一部分修改,使用的是自治事務,也就是脫離主事務上下文,自主提交,如果主事務回滾,獲取的 sequence 號將會被丟棄,而不是回滾,以保證其唯一性。

租約窗口

Sequence 根據租約 (lease) 類型的不同,支持了兩類 sequence,一類是數字型即 Number Sequence,數字窗口用來實現最大吞吐能力和最少可丟棄數字的最佳平衡。另外一類是時間型既 Time Sequence, 時間窗口用來實現最大吞吐能力和最少不可用時間的最佳平衡。

高可用

Sequence 的高可用依賴其所在的存儲引擎的高可用方案,Sequence 的修改日誌通過 BINLOG 日誌和 X-Paxos 協議進行復制,如果發生切換,Sequence 丟棄一個租約窗口的數據,來保證唯一性。在性能上能達到 3萬 QPS / Core,並能輕鬆在上百核的 CPU 上運行並且沒有性能熱點。

全局自增列

在 MySQL 生態中,Auto Increment 使用非常廣泛,在單機數據庫 MySQL 下,能夠保證其 ID 生成是唯一遞增,並儘量連續,在分佈式數據庫中,數據可能分佈在不同的節點中,通常的 Auto Increment 的兼容方法,例如分段法,

如果業務有四個分片, 就可以爲每個分片進行分段:

分片 1:{ 0001 - 1000 }

分片 2:{ 1001 - 2000 }

分片 3:{ 2001 - 3000 }

分片 4:{ 3001 - 4000 }

使用這種方法,可以達到唯一特性,但無法從全局看到,是連續遞增的表現,在分片之間擺動跳號。

SEQUENCE 默認實現了數字型生成器,也就是其窗口租約類型是數字,比如窗口是 100,其 Cache Size 就等於 100,窗口內的數字從內存獲取,cache 使用完,就推進到下一個窗口。 異常情況,最大可丟失一個窗口的數字,來保障其唯一性,所以,Cache Size 的設置,需協調儘量提升性能和儘可能少丟失窗口數字的平衡。PolarDB-X 分佈式數據庫中的 Auto Increment 可以對應一個 Sequence,用 nextval 來爲這個字段生成唯一連續的數字,保障分佈在多節點的數據分片中,實現自增列按照插入的順序生成。

TSO 發號器

TSO 發號器用來爲 PolarDB-X 分佈式數據庫事件來定序,是分佈式事務和全局一致性的基礎,同樣需要保證,唯一遞增,並且實現高吞吐能力,除此以外,TSO 爲了表達可讀性時間,其設置了特殊的格式:

物理時鐘 邏輯時鐘 保留位
42位 16位 6位

Sequence 實現的時間型生成器,也就是 lease type 等於 Time, 定製 TSO 的格式,使用 42 個 bit 表達毫秒級的物理時間,16 個 bit 表達一個遞增的自然數,理論設計上秒級可以實現最大 3000w 的 TPS 吞吐能力,足夠支撐一個超大規模的分佈式數據庫。

其中租約窗口用 Cache Size 來表達,比如:cache size = 2s,其代表租約窗口是 2 秒鐘,2秒窗口內的數字從內存生成,並提前推高所有高可用節點到下一個未來2秒的開始,也就是這個窗口,代表異常切換後,TSO發號器可用的最大等待物理時間,以保證唯一性, 對於 Cache Size 的設置,需協調最大吞吐能力和最小不可用時間的最佳平衡。

Lizard 分佈式事務系統

PolarDB-X 存儲引擎,想要實現全局分佈式事務的 ACID 特性,和全生態一致性,必須依賴一套分佈式事務系統,而MySQL 社區版本的 InnoDB 事務系統,存在諸多的弊端。

InnoDB 單機事務系統

弊端1:Read/Write 衝突嚴重

InnoDB 事務系統在內存結構中維護了全局活躍事務,包括活躍事務 (transaction) 鏈表,活躍視圖 (read view)鏈表等結構,並由一把大鎖保護,其簡略結構如下:

寫路徑上:

  1. 事務啓動時,分配事務 ID,並插入到全局的活躍事務 ID 數組中
  2. 事務過程中,修改操作會將事務 ID 更新到行記錄上來,表示該行記錄的最新修改者
  3. 事務提交後,將事務 ID 從全局的活躍事務 ID 數組刪除

讀路徑上:

  1. 查詢啓動時,啓動 Read View,並將全局的活躍事務 ID 數組拷貝到 Read View 上
  2. 查詢過程中,根據行記錄上的事務 ID 號,判斷是否在 Read View 上的活躍事務 ID 數組中來決定可見性

可以看到,無論是寫路徑還是讀路徑,都需要訪問全局結構(全局活躍事務數組),這個過程需要在一個事務大鎖的保護下完成。在過去,這樣的設計是高效且可靠的。隨着單機 CPU 多核能力的大幅加強,這樣的設計越來越成爲制約性能提升的瓶頸。以 PolarDB-X DN 節點在公有云上目前售賣的最大規格(polarx.st.12xlarge.25, 90C, 720G, 最大併發連接數2W)爲例,壓測 Sysbench read_write 場景,CPU 有接近 17% 的時間耗在無用的等待上:

弊端2:Commit 無法外部定序

在 InnoDB 事務系統中,其提交的真實順序,由內部確定,其產生的提交號爲 trx->no,其由內部來遞增,外部既不能訪問,也不能修改,當在分佈式數據庫集羣中,使用兩階段提交協議進行提交分佈式事務時,不同分片的提交號都由自己產生,各不相同,無法實現由 TSO 來統一定序的能力。

弊端3:MVCC 視圖無法傳播

InnoDB 事務系統的 MVCC 依賴視圖 Read View,而 Read View 是由一個活躍事務 ID 數組來表達,其實現:

  1. 事務 ID 無法在分片之間同步和識別
  2. 數組大小跟當時活躍事務數量有關,無法固定大小和高效傳播

這就帶來了很大的限制:

  1. 在單機存儲計算分離的模式下,無法高效使用 read view 進行存儲計算下推
  2. 在分片集羣模式下,無法得到全局一致性版本
  3. 在生態上下游下,無法存儲 read view 版本

所以,PolarDB-X 存儲引擎,自研了 Lizard 分佈式事務系統,來替換傳統的 InnoDB 單機事務系統。針對 InnoDB 事務系統的弊端,Lizard 事務系統,分別設計了 SCN 單機事務系統和 GCN 分佈式事務系統來解決這些弊端,有效的支撐分佈式數據庫能力。

Lizard SCN 單機事務系統

SCN 事務系統架構

關係型數據庫的 MVCC 機制,依賴數據的提交版本來決定其可見性,所以,Lizard 單機事務系統,引入了 SCN (System Commit Number) 來表達事務的提交順序,並設計了事務槽 (Transaction Slot) 來持久化事務的提交版本號即 SCN,其架構圖如下:

寫事務:

  1. 事務啓動時,申請事務槽 Transaction Slot,地址記爲 UBA
  2. 事務過程中,對修改的記錄填入 (SCN=NULL, UBA) 兩個字段
  3. 事務提交時,獲取提交號 SCN,並回填到事務槽上,並完結事務狀態,返回客戶提交完成

讀事務:

  1. 查詢啓動時,啓動事務視圖 Vision,即從 SCN 生成器上獲取當前 SCN,作爲查詢的 Vision
  2. 查詢進行時,根據行記錄的 UBA 地址找到對應的事務槽,獲知事務的狀態以及提交號
  3. 根據記錄 SCN 和 視圖 SCN 進行數字大小比較,就可以判斷可見性

FlashBack 查詢

許多用戶在線上運維數據庫過程中,可能會出現一些誤操作,常見的比如更新操作或刪除語句沒有帶限定條件或指定了錯誤的限定條件,導致數據因爲人工誤操作而被破壞或丟棄。特別是如果操作的是重要的配置信息,則會嚴重影響業務運行。這個時候往往需要 DBA 快速對數據進行回滾操作以恢復業務。

然而,數據庫進行業務回滾的代價通常是很高的。以 MySQL 數據庫爲例,通常的手段是:拿最近的一個備份好的全量數據庫,然後重放 BINLOG 日誌到指定的時間點。這個過程取決於用戶備份的頻率,可能需要持續一天以上。除此之外,該功能依賴於 BINLOG 日誌,在未開啓binlog功能的情況下,想要恢復數據庫到指定時間點,幾乎是沒有手段的。

Lizard事務系統提供了 Native Flashback Query 的能力,讓用戶能夠及時回查到過往某個時間點的數據,從而對數據進行回檔,以挽救數據庫。

FlashBack 查詢是數據庫針對過去某一個時間點的一致性查詢,其需要確定過去某一個時間點的版本號,以及針對這個版本號,保留了數據相應的 undo,以得到一致性的版本。爲了滿足 FlashBack 查詢,Lizard SCN 事務系統支持了可定製的 undo 保留策略和 [SCN - TIMESTAMP] 之間的轉換機制。

FlashBack 語法

SELECT ... FROM tablename
  AS OF [SCN | TIMESTAMP] expr;

TIMESTAMP 和 SCN 轉換

Lizard SCN 事務系統的視圖 Vision,無法識別 Timestamp 作爲版本號進行可見性比較,所以,系統啓動了一個 SCN snapshot 後臺任務,按照用戶設置的 Interval 來記錄 SCN 和 Timestamp 之間的對應關係,根據保留週期保存在 mysql 庫中的系統表中,其表結構如下:

CREATE TABLE `innodb_flashback_snapshot` (
  `scn` bigint unsigned NOT NULL,
  `utc` bigint unsigned NOT NULL,
  `memo` text COLLATE utf8mb3_bin,
  PRIMARY KEY (`scn`),
  KEY `utc` (`utc`)
) /*!50100 TABLESPACE `mysql` */

用戶的 AS OF TIMESTAMP 查詢,在事務系統內部,首先會查詢 snapshot 表,找到對應的 SCN,使用這個 SCN 作爲查詢視圖,再進行可見性比較。

UNDO 保留週期

Lizard SCN 事務系統,保留了兩個維度的設置,來靈活制定 undo 的保留週期:

1.時間維度

參數 innodb_undo_retention 可以制定 undo 保留多長時間,單位爲秒

2.空間維度

參數 innodb_undo_space_reserved_size 可以制定 undo 保留多大的空間,單位爲 MB

保留的時間或者空間越大,可以支持的過去的時間點查詢也就越久,但也會帶來空間上的使用。

實現原理

閃回查詢的核心在於歷史版本。在 DN 存儲引擎中,Purge 系統負責清理Undo中不再需要的歷史版本數據。目前Purge系統的推進策略是盡力且及時,導致歷史版本數據一旦不被需要,則馬上有被清理的可能性。

基於此,引入了Undo Reservation機制。該機制會阻擋Purge系統的推進,讓歷史版本數據能夠在Undo中保留一段足夠長的時間,讓用戶在出現誤操作的時候,能夠找回誤操作之前的數據版本。

當然,如果將所有的歷史版本數據都保留下來,Undo空間會膨脹地非常快。針對這種情況,目前Undo Reservation機制會綜合考慮兩個維度來阻擋Purge系統的推進:時間與空間。

用戶可以根據實際需求,調整Undo Reservation阻擋Purge系統的程度。比如,用戶可以要求只要Undo的空間不超過10G,就可以一直保留歷史版本數據。那麼,如果一個數據庫在一年內的更新量非常少,則通過Flashback Query功能,甚至可以找回一年前的數據。

下面結合下圖以“SELECT ... FROM tablename AS OF $SCN”爲例,簡單闡述本方案的運作流程。

從圖中可以看到,Purge系統的推進被Undo reservation機制所阻擋,僅僅推進到:清理掉SCN小於等於80的事務所產生歷史數據。

  1. SELECT * FROM t1 AS OF SCN 150 發起閃回查詢。
  2. 掃描到一條行記錄,發現SCN是無效值,則通過UBA回查Transaction Table,獲取事務狀態信息。可以看到,該記錄的SCN=200,並非本次閃回查詢所需要的版本。
  3. 通過Rollptr找到本記錄的上一個歷史版本,發現SCN=150,顯然該數據版本是本閃回查詢所需要的,返回該行記錄給用戶。

另外,如果再次進行閃回查詢:SELECT * FROM t1 AS OF SCN 60。沿着歷史版本鏈找也沒有找到該版本數據,則說明該歷史數據已經被 Purge 系統清理掉,此時返回 "Snapshot too old." 錯誤

SCN 事務系統的代價

相比於 InnoDB 事務系統,Lizard SCN 事務系統帶來了巨大的優勢:

  1. 解綁對全局結構的訪問依賴,讀寫衝突得到大幅緩解
  2. 視圖升級爲 Vision,只有一個 SCN 數字,不再有活躍事務ID 數組,易於傳播
  3. 支持自定義的 FlashBack 查詢

但同時也引入了一些代價,因爲事務提交只修改了事務槽,行記錄上的 SCN 一直爲 NULL 值,所以,每次的可見性比較,都需要根據 UBA 地址訪問事務槽,來確定真實的提交版本號 SCN,爲了減輕事務槽的多次重複訪問,我們在Lizard SCN 事務系統上引入了 Cleanout,一共分爲兩類,Commit Cleanout 和 Delayed Cleanout。

Commit Cleanout

事務在修改過程中,收集部分記錄,在事務提交後,根據提交的 SCN,回填部分收集的記錄,因爲需要儘量保證提交的速度不受影響,僅僅根據當前記錄數和系統的負載能力,回填少量的記錄,並快速的提交返回客戶。

Delayed Cleanout

查詢過程中,在根據 UBA 地址回查事務槽 SCN,判斷其事務狀態以及提交版本號之後,如果事務已經提交,就嘗試幫助進行行記錄的 Cleanout, 我們稱之爲 Delayed Cleanout,以便下次查詢的時候,直接訪問行記錄 SCN 進行可見性判斷,減輕事務槽的訪問。

Transaction Slot 複用

由於事務槽不能無限擴展,爲了避免空間膨脹,採用 Reusing 方案。事務槽會持續的保存到一個 free_list 鏈表上,在分配的時候,優先從 free list 中獲取進行復用。

另外頻繁的訪問 free_list 鏈表以及從 free_list 鏈表上摘取,需要訪問多個數據頁,這帶來了巨大的開銷,爲了避免訪問多個數據頁,事務槽 page 會被先放入 cache 快表中,下次獲取時直接從 cache 快表上獲取,這大大降低了讀多個數據頁帶來的開銷。

SCN 事務系統性能表現

雖然 Cleanout 帶來了部分的代價,但由於分擔到了查詢過程中,並且沒有集中的熱點爭搶存在,在測試結果上,相比 InnoDB 事務系統,Lizard SCN 事務系統整體的吞吐能力大幅提升。

  QPS TPS 95% Latency (ms)
Lizard-8032 636086.81 31804.34 16.07
MySQL-8032 487578.78 24378.94 34.33
MySQL-8018 311399.84 15577.15 41.23

(注:以上數據測試環境爲 Intel 8269CY 104C,數據量爲1600萬,場景爲 Sysbench Read Write 512 併發)

相比於MySQL-8032,Lizard SCN 事務系統性能提升 30%,延時降低 53%。

Lizard GCN 分佈式事務系統

分佈式事務模型

能否完整支持事務ACID是企業級分佈式數據庫最核心的特性。目前,主流的分佈式事務模型有:

1.Percolator模型

Percolator 是 Google 在 2010 年提出的一種分佈式事務處理模型,它的設計目標是在大規模分佈式系統中實現高效的事務處理。Percolator 是一種樂觀的事務模型,寫寫衝突被延遲到事務提交時纔會進行檢測。可見性與衝突檢測依賴於事務的開始時間戳以及提交時間戳。Percolator 模型,包括後續的 Omid 模型,其特點是:實現原理容易理解且易於工程實現,作爲一種高效且直接的分佈式事務處理模型,被廣泛應用於主流分佈式數據庫,其代表包括:TiDB、OceanBase 等。另外,由於其事務過程數據存放於內存中,事務大小會受到內存資源的限制。

2.Calvin模型

Calvin 模型由 Brown 等人於2012年提出。它旨在提供高性能、高可用性和強一致性的分佈式事務處理。Calvin 模型的核心思想是通過全局調度器,事先確定好各個調度節點的子事務執行順序,從根源上規避掉併發事務的鎖資源、緩存資源等資源的開銷。爲此,Calvin 模型還引入了一種稱爲 "transaction flow graph"(事務流圖)的數據結構,用於描述事務的執行順序和依賴關係。事務流圖是一個有向無環圖,其中節點表示子事務,邊表示子事務之間的依賴關係。通過事務流圖,Calvin 模型可以在分佈式環境中實現事務的一致性和原子性。

儘管 Calvin 模型具有許多優點,但也存在一些缺點和挑戰。在 Calvin 模型中,每個事務的執行都是獨立的,並且在分佈式環境中以並行方式執行。這種並行執行可能導致一些數據一致性問題,例如讀取到過期或不一致的數據。雖然Calvin 模型提供了一些機制來解決這些問題,如版本控制和衝突檢測,但仍然無法完全消除數據一致性的風險。其次,Calvin 模型需要一個全局調度器來協調和管理所有事務的執行。全局調度器需要考慮諸多因素,如事務的依賴關係、併發控制、負載均衡等,這增加了調度的複雜性。此外,全局調度器也可能成爲系統的瓶頸,限制了整個系統的擴展性和可伸縮性。

3.XA模型

XA 模型是一種用於管理分佈式事務的標準接口規範。它定義了在分佈式環境中進行事務處理所需的協議和操作。XA模型的名稱來自於 X/Open 組織(現在是The Open Group),它制定了 XA 接口規範,以便不同的事務處理管理器(Transaction Manager)和資源管理器(Resource Manager)之間能夠進行協作,實現數據的一致性。

在 XA 模型中,事務管理器(Transaction Manager)負責協調和管理事務的執行,而資源管理器(Resource Manager)則負責管理和操作特定的資源,XA 模型通過定義一組標準的接口和操作,使得事務管理器和資源管理器之間可以進行協作。

XA 模型的核心是兩階段提交(Two-Phase Commit,2PC)協議。在2PC協議中,事務管理器和資源管理器之間通過一系列的消息進行通信,以確保所有參與的資源管理器都在一個事務中執行相應的操作,並且最終要麼全部提交,要麼全部回滾。在這個過程中,事務管理器作爲協調者(Coordinator),負責發起和管理2PC協議的執行。

XA模型的具體實現需要事務管理器和資源管理器都遵循 XA 接口規範。事務管理器需要實現事務的開始、提交、回滾等操作,以及協調 2PC 協議的執行。資源管理器需要實現參與 2PC 協議的相關操作,如準備(Prepare)、提交(Commit)、回滾(Rollback)等。

PolarDB-X 分佈式事務模型

PolarDB-X 作爲一款企業級的分佈式數據庫,數據天然分片到不同的存儲節點上,跨節點的修改和訪問變成了常態,保障數據庫的 ACID 特性,以及如何做到透明分佈式也變成了一項有挑戰的事情。

業務場景和挑戰

1.轉賬模型,如何保證 ACID 特性

如下圖所示,一個經典的轉賬模型,在跨節點的情況下,如何保證事務的原子性,以及跨節點查詢的一致性,單機的事務系統已經無法完成。

2.多維度分區鍵,如何做到業務透明訪問

在業務模型設計的時候,對應的業務訪問,通常會涉及到多個維度,傳統的本地索引無法多維度路由,這就需要引入全局二級索引來應對多維度的業務訪問訴求,以便達到業務無感知,並高效的多維度路由訪問方式,如何在跨節點的維護全局二級索引,同樣需要分佈式事務來保證。

Two-Phase Commit 協議

爲了實現 PolarDB-X 具有數據庫的 ACID 特性和強一致性,其分佈式事務模型採用了兩階段提交 (Two-Phase Commit,2PC) 協議,按照嚴格的 XA Spec 的定義,其中:

1.Transaction Manager

CN 節點承擔事務管理的職責,作爲協調器 (Coordinator) , 負責分佈式事務狀態的持久化和分佈式事務的推進流轉

2.Resource Manager

DN 節點承擔事務參與者的職責,作爲參與方 (Participants),負責接受用戶數據的修改和事務狀態的變化

GCN 分佈式事務系統架構

爲了實現嚴格的 2PC 協議, PolarDB-X 存儲引擎在 SCN 單機事務系統的基礎上,實現了 GCN ( Global Commit Number) 分佈式事務系統,其架構圖如下:

Lizard 事務系統的事務槽 (Transaction Slot) 在原有的基礎上擴展了一個字段,用於保存一個 GCN,即 Global Commit Number,這個 GCN 來源於 TSO 發起號,至此,跨節點的事務將使用 GCN 來代替 SCN 爲分佈式事務全局定序。

事務原子性

跨節點的分佈式事務,嚴格按照 2PC 協議的標準來實現,根據 XA Spec 定義的接口,CN 節點通過以下的語法來操作 DN 節點,

XA {START|BEGIN} xid
XA END xid
XA PREPARE xid
XA COMMIT xid [ONE PHASE] $GCN
XA ROLLBACK xid $GCN
XA RECOVER

在 XA COMMIT / ROLLBACK 的時候,CN 節點從 GMS 獲取一個 TSO,作爲本次的外部提交號,也就是 GCN,傳給所有 DN 參與方,並持久化。

通過 2PC 協議, PolarDB-X 嚴格保證用戶跨節點事務的原子性。

讀強一致性

在用戶的跨節點訪問中,CN 節點將獲取一個 TSO 作爲本次查詢的 MVCC 視圖,並通過 AS OF 查詢下發到 DN 存儲節點,其語法如下:

SELECT ... FROM tablename
  AS OF GCN expr;

在 GCN 事務系統中, 由於分佈式事務由 GCN 定序,所以查詢的可見性比較,也從單機的 SCN 比較轉換成 GCN 比較,來保證全局的強一致性,由於外部定序和兩階段的提交過程, 在可見性比較的時候,對於無法決策的 Prepare 狀態,PolarDB-X 採用了等待的策略,等事務狀態完結,再進行 GCN 的大小比較。

XA 協調日誌下沉

協調日誌

分佈式數據庫中,兩階段提交協議(Two-Phase Commit,2PC)是一種經典的分佈式事務處理協議。該協議分爲兩個階段:準備階段和提交階段。在準備階段,各個節點會將數據狀態反饋給協調節點,協調節點再根據各節點的狀態,決定是否進行提交。在提交階段,協調節點通知各個節點進行提交或者回滾操作。

雖然2PC協議可以確保數據的一致性,但其在性能上存在較大問題。一個典型的問題是:兩階段提交過程中,多個節點需要進行通信:發起請求和等待應答。多節點之間的交互次數要比單機數據庫要多得多,這導致了嚴重的延時問題,極大地限制了分佈式數據庫事務提交的性能和可擴展性。特別是對於OLTP型的業務,其大部分事務都是短平快類型,提交性能對於整個分佈式數據庫影響巨大。

其次,兩階段提交必須保證故障容災,各個節點、各個環節都有可能發生異常。因此,協調者通常需要記錄協調日誌來保證最終各個節點事務的狀態是一致的。

可以看到,協調者與協調日誌是整個兩階段提交流程的關鍵,如何降低兩階段提交協議的交互次數,如何保證持久化協調日誌,如何善後清理協調日誌,如何保證協調日誌高可用是分佈式數據庫設計中的核心問題之一。

日誌下沉

在Lizard事務系統中,這些問題都將被妥善處理。其核心思想是:協調日誌會被下沉到存儲引擎上。當發生分佈式事務時,其中一個參與方會被選爲主分支。主分支負責持久化協調日誌,並提供協調日誌反查能力。主分支在事務啓動時,會開闢一塊事務槽空間;當事務提交或回滾時,相關事務狀態會被持久化到事務槽上。其他節點在做故障恢復時,必須先找到主分支,並根據主分支的事務狀態驅動本分支的事務完成提交或回滾。

其次,事務槽會被 Lizard 事務系統有目的的保留一段時間,直至事務狀態信息不再被需要。隨後,事務槽會隨着數據庫的清理系統自動被清理回收。也就是說,提交和回滾並不是事務的最終狀態,當代表事務存在的事務槽信息被清理後,事務最終進入 Forget 狀態。

同時,協調日誌在內部會通過 X-Paxos 協議實現多副本,徹底保證了協調日誌的可靠性。

故障恢復

當發生故障時,協調日誌是故障恢復的重要環節。Lizard事務系統提供了協調日誌回查能力,以協助故障恢復。

Status Explain Action
ATTACHED 正在有 Session 處理該 XA 事務 等待重試
DETACHED 沒有 Session 處理該 XA 事務 嘗試 attach 該 XA 事務,進行提交或回滾
COMMIT 已提交 提交其它分支
ROLLBACK 已回滾 回滾其它分支
FORGET 事務信息已被清理 事務完結

值得注意的是,XID 是 XA 分佈式事務模型裏對事務的唯一標識,是一個外部指定的事務標識號,區別於 InnoDB 的 trx_id 以及 MySQL 的 GTID。通過 XID 回查事務狀態信息,對於原生 MySQL 是困難的,可能需要觸發大量的 IO 操作,影響在線業務。Lizard 事務系統卻沒有這個顧慮,原因在於:

  1. 樂觀查找:在 prepare 階段,會給 CN 節點返回事務槽地址的 Hint 信息,通過 Hint 信息樂觀地查找事務槽,大部分情況下以最多一個 IO 的代價查找到相關的事務信息。
  2. 悲觀查找:Lizard 事務系統捆綁了 XID 與事務槽地址的映射關係,最壞情況下,只需要查找一組事務槽即可,而不需要對事務槽進行全量搜索。另外,爲了防止 HA 後退化到全量搜索,這個映射關係會被 X-Paxos 協議廣播到所有的副本。

日誌下沉 VS 傳統2PC

協調日誌下沉到存儲引擎對於傳統 2PC 提交有顯著的改進:

  1. 協調日誌跟隨 COMMIT / ROLLBACK 操作,一起完成持久化,甚至是多副本同步,但並不會帶來額外的開銷
  2. 協調日誌的清理跟隨事務槽的清理,不需要額外複雜的清理機制
  3. 縮短了 prepare 的時間,提高了 2PC 提交的吞吐能力。
  4. 降低了分佈式讀/單分片讀因爲讀取到 prepare 事務的記錄而被迫阻塞的可能性。

單分片優化

分佈式事務的代價

分佈式事務相比於單機事務,需要更多的代價:

  1. 提交過程需要從 TSO 獲取全局事務提交號
  2. 提交過程需要進行 2PC 提交,涉及到多個節點之間交互,通信開銷顯著提升

目前,分佈式事務需要完成一個完整的 2PC 提交,同步開銷爲 3 RT + 2 BINLOG。與之對比,單機事務的同步開銷僅爲 1 RT + 1 BINLOG。

單分片事務

如果數據的修改僅僅涉及到一個分片,在提交過程中,則完全可以採用一階段提交來完成,從而節省掉 2PC 提交帶來的開銷。這樣的單機(寫)事務在 PolarDB-X 中被稱爲單分片(寫)事務。

同樣的,如果數據的查詢僅僅涉及到單個分片,也可以不通過與 TSO 交互,直接在 DN 節點上完成查詢操作。這樣事務在 PolarDB-X 同樣被稱爲單分片(讀)事務。

單分片事務與純粹的單機本地事務看起來很像,但內部邏輯是完全不同。最關鍵的區別是,單分片事務的可見性判斷依賴於全局事務提交號 GCN,而純粹的單機本地事務的可見性僅僅取決於本地提交號 SCN。

單分片事務提交序

單分片事務的提交號不能從 TSO 獲取,因此如何確定單分片事務的提交號,是單分片事務最核心的問題。一個直觀的理解是,單分片事務發生在本節點已提交的分佈式事務之後,以及本節點已發起的分佈式查詢之後。即它的提交號必須比本節點所有已提交的分佈式事務的提交號大,並且比分佈式查詢的快照號大。

爲此,Lizard 事務系統內部維護了本節點的全局 GCN,該 GCN 被稱爲 narrow_GCN(narrow GCN, 狹隘的全局提交號)。narrow_GCN 在以下場景會被推高:

  1. 分佈式查詢事務會推高 narrow_GCN
  2. 分佈式寫事務會推高 narrow_GCN

當單分片(寫)事務提交時,會獲取本節點的 narrow_GCN 作爲自己的全局提交號。

另外,單分片(寫)事務之間也有提交順序,該提交順序僅僅由本地提交號 SCN 決定。

至此,單分片(寫)事務的提交號可以確定爲(narrow_GCN,SCN),該提交號反映了單分片事務的提交序。

單調遞增 narrow_GCN

narrow_GCN 作爲提交號,必須要滿足單調遞增永不回退。narrow_GCN 會被持久化到 Lizard 系統表空間中。然而,如果每次事務提交都需要持久化 narrow_GCN,則會形成嚴重的性能熱點,限制了數據庫的整體吞吐能力。

爲此,Lizard 事務系統優化了 narrow_GCN 的持久化性能。其核心思想是,對 narrow_GCN 的修改只記錄重放日誌,而不對實際的數據頁進行修改。也就是說,數據頁的實際修改會被延後,期間所有的修改都被合併爲一次修改,大幅度改善了 narrow_GCN 持久化的性能問題。

可見性

單分片事務與分佈式事務並存時,可見性判斷會面臨巨大的挑戰,因爲同時會存在分佈式讀與分佈式寫,分佈式讀與單分片寫,單分片讀與分佈式寫,單分片讀與單分片寫的情況。一個典型的案例是:

  1. 單分片讀啓動,並使用當時DN節點的最大 GCN(假設爲95)爲 Snapshot_GCN 作爲視圖
  2. 單分片寫啓動,賬戶A(轉賬前餘額爲1000)向賬戶B(轉賬前餘額爲1000)轉賬100塊,並最終提交,取得提交號爲 DN 節點的最大 GCN(同樣爲95)

單分片讀可能會讀到一個不一致的狀態:

  1. 當單分片寫未提交時,單分片讀到賬戶A的餘額爲1000塊。
  2. 當單分片寫提交後,單分片讀此時纔讀取到賬戶B的餘額,發現已提交,並且餘額爲1100

顯然,這違反了事務的原子性以及一致性。類似的不一致性問題其它場景下也會發生。

可見性判斷的關鍵在於定序,Lizard 事務系統通過全局提交號加本地提交號組合的方式準確給所有的事務確定先後順序。更具體地說,可見性判斷先依據全局提交號 GCN 來確定順序,而當 GCN 已經無法確定先後順序時,會進一步依據本地提交號 SCN 來確定先後順序。

二級索引可見性

MySQL 的二級索引的修改並不會產生 UNDO,也就是說,二級索引沒有多版本。MySQL 原生事務系統對於二級索引可見性判斷,主要依賴於數據頁上的 max_trx_id 字段,該字段表示了所有修改該二級索引數據頁上的事務中,最大的事務ID號。

當 MySQL 查詢生成視圖時,同時會獲取當時數據庫裏最小的活躍事務ID號。當讀取二級索引時:

  1. 如果數據頁上的 max_trx_id 小於視圖的最小的活躍事務ID號,則本數據頁上所有的二級索引記錄都可見
  2. 否則,無法判斷出該二級索引的可見性,需要回到主鍵索引上進行可見性判斷。這個過程一般稱爲回表。

回表需要回到主鍵上進行 B+Tree 的查找,直到找到對應的主鍵記錄。顯然,回表會帶來巨大的查詢開銷,特別是可能會帶來大量的隨機 IO。一個設計優秀的事務系統,應該儘量減少回表的次數。

MySQL 的二級索引可見性判斷是一個很自然的解決方案。然而在分佈式查詢場景,這個方案就不再可行,原因在於所有的分佈式查詢,都採用了 Flashback Query。在這種查詢下,需要的不是現在的(最新的)系統裏的最小的活躍事務ID號,而是當時的(歷史的)最小的活躍事務ID號。

一個可靠的方案是:對於所有的二級索引查詢都進行一次回表。然而,從上面分析可以獲知回表的代價是很高的。

Lizard事務系統採用 TCS (Transaction Commit Snapshot) 方案徹底解決了分佈式讀查詢二級索引總是需要回表的問題。其核心思想是,每間隔一段時間內部產生一個事務系統提交快照信息。該快照信息裏保存了當時的事務系統狀態,如 GCN, SCN, min_active_trx_id 等等。根據系統配置,保留一段時間內的所有事務系統提交快照信息。

當分佈式查詢構建視圖時,需要拿着視圖的 Snapshot_GCN,到 TCS 裏查找,找到一個最貼近的 min_active_trx_id來作爲數據庫在 Snapshot_GCN 時的近似 min_active_trx_id 值,並用該值與二級索引數據頁上的 max_trx_id 進行比較來決定二級索引的可見性。

測試結果顯示,Lizard事務系統在二級索引可見性上的優化,相比全回表方案,提升在400%以上。

XA 完整性

PolarDB-X的分佈式事務依賴於XA模型。然而,MySQL XA事務在多副本策略下,容易導致主備不一致。經過測試,直到 MySQL 8.0.32 版本,經過多輪完善修改後,XA 事務仍然有概率導致主備不一致。該問題的根源在於,BINLOG 日誌作爲 XA 事務參與方之一,由於其本身爲文本追加的格式,事實上並不支持回滾能力。這個問題在 PolarDB-X 的分佈式模型中會被放大,原因在於 PolarDB-X DN 節點內部依賴 X-Paxos 的多數派協議,BINLOG 日誌同時作爲協議的載體,需要承擔更多更復雜的狀態流轉邏輯。

Lizard 事務系統針對 MySQL XA 事務完整性問題,提出了基於 GTID 的全量事務日誌回補方案,徹底解決了 MySQL 由來已久的 XA 事務完整性問題。其核心思想是,BINLOG 日誌除了承擔了內部協調日誌功能外,還保存了全量的事務操作日誌。在故障恢復中,存儲引擎會提供 GTID executed 集合,binlog 日誌會根據 GTID 集合,回補在存儲引擎中丟失的事務。

全局分佈式一致性

異步複製節點一致性

事務型的分佈式數據庫一般會採用讀寫分離的模式提升讀的性能,因此分佈式事務除了保障主庫的一致性以外,還需要保證用戶在使用讀寫分離模式下對於備庫的一致性。

PolarDB-X 產品上支持主實例和只讀實例,主實例由基於 X-Paxos 多數派的 Leader/Follower 角色組成,只讀實例由基於 X-Paxos 的 Learner 角色組成。PolarDB-X 針對分佈式事務一致性的設計,除了在存儲節點(DN)的 Leader 主副本中保存事務信息之外,也會將數據的事務多版本信息同步到 Learner 副本中,可以保證只讀實例上的多個分區數據讀的一致性。

爲了實現異步複製一致性,Learner 節點內部需要維護 apply_index,表達 Learner 節點已經應用的日誌位點,如何儘快推高 apply_index 是異步複製一致性的關鍵部分。 PolarDB-X DN 存儲引擎在 Learner 節點上採用多線程併發重放 X-Paxos 日誌,內部會維護一段亂序區間,允許局部亂序併發,全局仍然保證有序性。經過測試,這種設計能夠充分利用 Learner 節點的併發性能。

異構複製節點一致性

MySQL BINLOG 是MySQL記錄變更數據的"二進制日誌",它可以看做是一個消息隊列,隊列中按順序保存了MySQL 中詳細的增量變更信息,通過消費隊列中的變更條目,下游系統或工具實現了與 MySQL 的實時數據同步,這樣的機制也稱爲 CDC(Change Data Capture,增量數據捕捉)。

PolarDB-X 存儲引擎爲全局提交版本號 GCN,新增了一種 binlog event 類型,保存了全局提交版本號,並作爲事務日誌的一部分寫入到了 BINLOG 文件中,CDC 可以同時爲下游的結構化數據,提供版本能力,構建異構節點的一致性。

全局備份一致性

關係型數據庫一般會採用全量 + 增量的備份來保障數據安全, 在多個存儲節點的分佈式環境下,如何同時恢復所有存儲節點到一個相同的時間點,是全局備份一致性的要求。

PolarDB-X 在存儲節點(DN)的數據和變更日誌中都保存了分佈式事務的全局時鐘(包含了時間戳信息),任意時間點的數據恢復(PITR,point-in-time recovery)都可以快速將時間戳轉化爲分佈式的全局時鐘,在備份恢復中按數據的版本可見性進行處理,使集羣的所有 DN 節點,共同恢復到一個一致的時間點。

作者:冷香、攢葉、宋格

點擊立即免費試用雲產品 開啓雲上實踐之旅!

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

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