首次公開:京東強一致、高性能分佈式事務中間件 JDTX

在分佈式數據庫、雲原生數據庫、NewSQL 等名詞在數據庫領域層出不窮的當今,變革——在這個相對穩定的領域已愈加不可避免。相比於完全革新,漸進式增強的方案在擁有厚重沉澱的行業則更受青睞。

同所有分佈式領域的解決方案相同,分而治之的透明化數據分片方案,是新一代數據庫解決海量數據的核心理念。水平拆分使得分佈式事務的重要性,較之垂直拆分的業務系統進一步提升。另外,彈性擴(縮)容、HTAP 等概念也是新一代數據庫的關注重點。京東數科開源的 Apache ShardingSphere 在數據分片方面已逐漸成熟,在此場景之上開發的分佈式事務中間件 JDTX 與之共同組成了分佈式數據庫的內核拼圖。

JDTX 是由京東數科的數據研發團隊傾力打造的分佈式事務中間件。本次分享是 JDTX 首次公開出現在大眾視野面前,分享內容涵蓋 JDTX 的核心設計理念及相關的技術實現難點,希望能為打造分佈式事務解決方案的團隊提供一些思路。

背景

數據庫事務需要滿足 ACID(原子性、一致性、隔離性、持久性)4 個特性。

在單一數據節點中,事務僅限於對單一數據庫資源的訪問控制,稱之為本地事務。幾乎所有的成熟的關係型數據庫都提供了對本地事務的原生支持。但是在基於微服務的分佈式應用環境下,越來越多的應用場景要求對多個服務的訪問及其相對應的多個數據庫資源能納入到同一個事務當中,分佈式事務應運而生。

關係型數據庫雖然對本地事務提供了完美的 ACID 原生支持。但在分佈式的場景下,它卻成為系統性能的桎梏。如何讓數據庫在分佈式場景下滿足 ACID 的特性或找尋相應的替代方案,是分佈式事務的重點工作。

本地事務

在不開啟任何分佈式事務管理器的前提下,讓每個數據節點各自管理自己的事務。它們之間沒有協調以及通信的能力,也並不互相知曉其他數據節點事務的成功與否。本地事務在性能方面無任何損耗,但在強一致性以及最終一致性方面則力不從心。

兩階段提交

XA 協議最早的分佈式事務模型是由 X/Open 國際聯盟提出的 X/Open Distributed Transaction Processing(DTP)模型,簡稱 XA 協議。

基於 XA 協議實現的分佈式事務對業務侵入很小。它最大的優勢就是對使用方透明,用戶可以像使用本地事務一樣使用基於 XA 協議的分佈式事務。XA 協議能夠嚴格保障事務 ACID 特性。

嚴格保障事務 ACID 特性是一把雙刃劍。事務執行在過程中需要將所需資源全部鎖定,它更加適用於執行時間確定的短事務。對於長事務來說,整個事務進行期間對數據的獨佔,將導致對熱點數據依賴的業務系統併發性能衰退明顯。因此,在高併發的性能至上場景中,基於 XA 協議兩階段提交類型的分佈式事務並不是最佳選擇。

柔性事務

如果將實現了 ACID 的事務要素的事務稱為剛性事務的話,那麼基於 BASE 事務要素的事務則稱為柔性事務。BASE 是基本可用、柔性狀態和最終一致性這 3 個要素的縮寫。

在 ACID 事務中對一致性和隔離性的要求很高,在事務執行過程中,必須將所有的資源佔用。柔性事務的理念則是通過業務邏輯將互斥鎖操作從資源層面上移至業務層面。通過放寬對強一致性和隔離性的要求,只要求當整個事務最終結束的時候,數據是一致的。而在事務執行期間,任何讀取操作得到的數據都有可能被改變。這種弱一致性的設計可以用來換取系統吞吐量的提升。Saga 和 TCC 都是典型的柔性事務實現方案。

結論

基於 ACID 的兩階段事務和基於 BASE 的最終一致性事務都不是銀彈,可通過下表詳細對比它們之間的區別。


<table><thead>兩階段提交柔性事務
/<thead><tbody>業務改造無實現相關接口一致性支持最終一致隔離性支持業務方保證併發性能嚴重衰退略微衰退適合場景短事務 & 低併發長事務 & 高併發/<tbody>/<table>

缺乏併發度保障的兩階段事務不能稱之為完善的分佈式事務解決方案;而缺乏對 ACID 原義支持的柔性事務都甚至不能稱之為數據庫事務,它更適合服務層的事務處理。

放眼當前,實難找到無需權衡即可放之四海而皆準的分佈式事務解決方案。

JDTX 的分佈式事務解決方案

JDTX 的設計目標是強一致(支持 ACID 的事務原義)、高性能(甚至強於本地事務)、1PC(完全摒棄兩階段提交和兩階段鎖)的完全分佈式事務中間件,目前可用於關係型數據庫。它採用完全開放 SPI 的設計方式,提供與 NoSQL 對接的可能,能夠將多元異構數據維持在同一事務中。

設計理念

首先通過一張架構圖來直觀的瞭解一下 JDTX 的構成。

首次公开:京东强一致、高性能分布式事务中间件 JDTX

JDTX 由事務管理器(TM)和資源管理器(RM)組成。

事務管理器用於生成全局單調遞增的事務日誌序列號(LSN),事務的提交和回滾等核心流程處理,以及未提交事務的本地元祖(Tuple)持有。

資源管理器用於管理活躍事務數據。JDTX 的設計特點是將在事務中的數據(稱之為活躍數據)和不在事務中的數據(稱之為落盤數據)分離。活躍數據在落盤至預寫日誌系統(WAL)之後,並將數據保存至自研的多版本快照(MVCC)內存引擎中。落盤數據則是通過異步刷盤的方式,將 MVCC 引擎中的數據以流量可控的方式同步至最終的存儲介質中(如:關係型數據庫)。

事務內查詢會將落盤數據和活躍數據合併,並根據當前事務的隔離級別獲取出符合當前事務可見性的數據版本。

方案亮點

無損事務方案

JDTX 採用 WAL + MVCC 引擎的方式實現了事務的 ACID 原義。

原子性 & 一致性支持

JDTX 的 MVCC 引擎可以看做是一個集中式緩存,可以將兩階段提交簡化至一階段提交。維持單一節點中數據的原子性和一致性,即將分佈式事務的範疇縮減到本地事務的範疇。

MVCC 引擎可以通過分片 + 主從同步的方式維持水平擴展和高可用的能力。JDTX 保證所有對事務數據的訪問都通過 MVCC 引擎的活躍數據 + 最終數據端的落盤數據的合併的方式,以保證數據的原子性和一致性。

隔離性支持

JDTX 以多版本快照的方式實現事務隔離性。目前完整的支持 4 種標準隔離級別中的讀已提交和可重複讀,已經可以滿足絕大部分需求。

持久性支持

JDTX 將事務的活躍數據在存入 MVCC 引擎之前先落盤至 WAL 引擎,以保證服務器崩潰,內存數據丟失時,活躍數據依然能夠從 WAL 引擎中完全恢復。

高性能

JDTX 採用將活躍數據異步刷盤至數據庫的方式極大的提高了數據寫入的性能上限。它的性能瓶頸從數據庫寫入耗時轉移到了落盤至 WAL 引擎和存儲至 MVCC 引擎的耗時。

與數據庫的 WAL 系統類似,JDTX 的 WAL 也採用日誌順序追加的方式,因此可以簡單的理解為 JDTX 的 WAL 耗時 = 數據庫系統的 WAL 耗時。而 MVCC 緩存則採用哈希數據結構,其寫入耗時小於需要維護 BTree 索引的數據庫寫入耗時。因此,採用 JDTX 的事務方案,其數據更新性能甚至強於不開啟事務。

另外,JDTX 採取了無 UNDO 日誌的事務回滾策略。未提交的數據並不會進入 MVCC 引擎,而是被事務管理器本地持有。因此,只要清理掉未提交數據即可完成事務回滾。無 UNDO 日誌的設計進一步的提升了事務處理的性能。

高可用

WAL 引擎和 MVCC 引擎均採用分片 + 主備的方式,以保證 JDTX 不會產生單點故障。在 MVCC 引擎完全不可用的情況下,可通過恢復模式將 WAL 中的數據同步至數據庫,以保證數據的完整性。

跨多元數據庫事務

JDTX 將事務活躍數據和落盤數據分離的設計方案,使其落盤數據存儲端無任何限制。所有的事務活躍數據都會通過異步的落盤執行器存儲至後端數據庫,因此後端是否為同構數據庫,其實並無影響。

使用 JDTX 能夠保證跨多元存儲端(如:MySQL、PostgreSQL 甚至是 MongoDB、Redis 等 NoSQL)的分佈式事務維持在同一事務語義之中。

實現難點

MVCC 內核

事務隔離級別有兩種常見的實現方案,即鎖實現和 MVCC 實現。除了 Infomix 等少數數據庫,大部分關係型數據庫均採用 MVCC 實現。

讀未提交、讀已提交、可重複讀和可序列化這 4 種事務隔離級別的標準,是 ANSI 所定義的基於鎖實現的方式。事務的並行度隨著隔離級別的增加而衰減,除了併發度最低的可序列化,其他隔離級別都伴隨著對一致性的權衡和犧牲。

下表是基於鎖實現的隔離級別對照表。

<table><thead>隔離級別髒讀不可重複讀幻讀/<thead><tbody>讀未提交可能可能可能讀已提交
不可能可能可能可重複讀不可能不可能可能可序列化不可能不可能不可能/<tbody>/<table>

通過 MVCC 實現的隔離級別實際上只有 SI(快照隔離)和 SSI(可序列化快照隔離)這 2 種。SI 和 SSI 與 ANSI 的 4 種隔離級別並不能完全對照。其中的讀未提交,與讀已提交在 MVCC 的實現中性能並無差別,可以忽略不計。因此 SI 可以對應為讀已提交和可重複讀這 2 種隔離級別。實際上,即使是幻讀,在 SI 隔離級別中也是不會出現的。

由於快照併發控制並不能真正意義上保證事務是“可串行化”的,所以事務間的併發操作依舊有可能引發數據異常現象。但這裡的異常不同於之前提到的髒讀、丟失更新的異常,而是一種業務數據間邏輯語義層面的異常,也可以說是由於未能滿足數據間的語義約束而產生的異常。這被稱之為寫偏序(Write skew),它的檢測可依據併發事務間讀寫依賴的多版本可串行化圖(The multiversion serialization graph)來實現,即 SSI 隔離級別。

下表是基於 MVCC 實現的隔離級別對照表。

<table><thead>隔離級別髒讀不可重複讀幻讀寫偏序/<thead><tbody>讀未提交無需實現無需實現無需實現無需實現讀已提交不可能可能
可能可能可重複讀不可能不可能不可能可能可序列化不可能不可能不可能不可能/<tbody>/<table>

自研 MVCC 引擎是 JDTX 的主要難點之一。JDTX 採用與 PostgreSQL 類似的 MVCC 實現方案,通過 xmin 和 xmax 標記事務快照範圍,並在 MVCC 引擎中保存每個數據元祖(Tuple)的 xmin 和 xmax 的事務信息。同一數據的多版本以鏈表的數據結構存儲,通過其 xmin 和 xmax 來獲取數據的版本在當前事務快照中的可見性。

由於 MySQL 也並未實現 SSI 隔離級別,因此目前的 JDTX 只是實現了 SI 隔離級別,還並未實現 SSI 隔離級別。

MVCC 數據的清理(vacuum)是另一技術難點。過長的事務會導致 MVCC 版本過多,導致佔用大量存儲空間。尤其是 JDTX 是通過內存來存儲 MVCC 的活躍數據,因此對內存空間的釋放則更加敏感。由於 JDTX 的異步落盤機制,因此除了 MVCC 標準的垃圾回收邏輯之外,判斷數據是否落盤成為清理邏輯的額外規則。

SQL 查詢引擎

通過 SQL 查詢事務的活躍數據,是 JDTX 的另一個技術實現難點。MVCC 引擎並非關係型數據庫,並不能通過識別 SQL 來查詢相關數據。JDTX 則通過之前 Apache ShardingSphere 所積累的 SQL 解析模塊及其抽象語法樹(AST)來實現對 SQL 的理解,以及查詢基於內存的 MVCC 引擎中的數據。

對於 SPJ(select-project-join)的 OLTP 類型 SQL,可以從 SQL 的查詢結果中獲取數據主鍵。JDTX 將落盤數據從後端數據庫中取出作為最終展現數據的基礎,並在此之上從 MVCC 引擎中查詢出當前事務可見的活躍數據,並對其結果進行歸併。換句話說,每次事務內查詢都是由落盤數據 + 活躍數據歸併而成。歸併引擎部分參照了 LSM Tree 的設計思想。

對於非 SPJ 的 OLAP 類型 SQL,JDTX 則採用另外的查詢方式。基於聚合函數和分組的 SQL 無法通過主鍵直接將後端數據庫中的落盤數據和 MVCC 引擎中的鍵值數據直接匹配,因此採用以 MVCC 引擎中數據為主,並將 SQL 改寫為剔除活躍數據主鍵的新 SQL,再從後端數據庫中查詢無重複的聚合數據進行歸併。

使用限制

分佈式無銀彈,這是架構師們對現有的分佈式系統比較公認的看法。雖然 JDTX 具備了很多優點,但仍然有一些使用限制。它的使用限制主要有以下 3 點。

  1. 需要通過 JDTX 訪問數據庫。JDTX 通過其 MVCC 引擎控制事務的原子性、一致性和隔離性,並通過 WAL 控制事務的持久性。因此在使用 JDTX 的系統中,跨過事務中間件直接查詢數據庫,是得不到正確的事務數據的,修改數據庫則會導致數據紊亂。

  2. SQL 支持需要持續完善。查詢 MVCC 引擎的 SQL 方言兼容則需要持續提升。相對於無損的 ACID 事務原義支持所帶來的優勢,SQL 兼容度的下降,是 JDTX 帶來的權衡。

  3. 不支持無主鍵數據。JDTX 需要通過主鍵來合併 MVCC 引擎和數據庫中的數據。因此無法處理沒有主鍵的記錄。

JDTX 與 Apache ShardingSphere

通過 Apache ShardingSphere 提供的 JDBC 接入端,可以使 JDTX 無縫的對接至 Java 應用。除了 JDBC 接入端,Apache ShardingSphere 也提供了基於 MySQL 和 PostgreSQL 的 Proxy 接入端,使 JDTX 像一個單獨的數據庫一樣提供分佈式事務的服務。Apache ShardingSphere 將在未來將接入端剝離,使 JDTX 獨立使用成為可能。

Apache ShardingSphere 提供了分佈式事務的統一 SPI。JDTX 通過實現 ShardingSphere 提供的 SPI,可以很輕鬆的融入 Apache ShardingSphere 生態。結合 Apache ShardingSphere 與 JDTX,可以將數據分片與分佈式事務無縫結合

獨立使用 Apache ShardingSphere 或 JDTX,可以靈活解耦,高度定製,可以看做是基礎組件的樂高積木。而將其聯合使用,則能夠產生化學變化,甚至使它們具備組成分佈式數據庫基礎設施的能力。架設在產品最前端的 Apache ShardingSphere 用於 SQL 解析、數據庫協議和數據分片;位於中層的 JDTX 用於通過鍵值對和 MVCC 的方式處理事務活躍數據;最底層的數據庫則僅作為最終的數據存儲端。下圖是 ShardingSphere + JDTX 的架構圖。

首次公开:京东强一致、高性能分布式事务中间件 JDTX

最後附上 MySQL 架構圖,請讀者自行體會其相似之處。

首次公开:京东强一致、高性能分布式事务中间件 JDTX

JDTX 的後續規劃

JDTX 的自身目標是力爭將其打造成為一個分佈式事務的標準解決方案。在事務核心流程、MVCC 引擎、WAL 引擎、高可用等核心功能打磨成熟後,JDTX 會將主要精力投放在以下幾個方面:

  1. 提升 SQL 語句兼容性以及多元數據庫支持;

  2. 實現 SSI 隔離級別,提供完整的 MVCC 隔離級別解決方案;

  3. 完善管理端和監控端。

除了 JDTX 中間件自身,它也將與 ShardingSphere 等其他數據庫中間件更加一體化的提供分佈式數據庫級別的服務;並將與 Kubernetes 等雲原生平臺更加深度整合,為雲原生數據庫提供服務。

張亮,京東數科數據研發負責人, Apache ShardingSphere 發起人 & PPMC,JDTX 負責人。

熱愛開源,主導開源項目 ShardingSphere(原名 Sharding-JDBC) 和 Elastic-Job。擅長以 Java 為主分佈式架構,推崇優雅代碼,對如何寫出具有展現力的代碼有較多研究。

目前主要精力投入在將 ShardingSphere 和 JDTX 打造為業界一流的金融級數據解決方案之上。ShardingSphere 已經進入 Apache 孵化器,是京東集團首個進入 Apache 基金會的開源項目,也是 Apache 基金會首個分佈式數據庫中間件。

GitHub: https://github.com/terrymanu , 隨時歡迎技術交流和指正。

ShardingSphere源碼分析文章,請進入公眾號

【阿飛的博客】->服務端技術->分庫分表,不僅有ShardingSphere源碼分析,還有很多分庫分表實戰原創文章,超值的一看喲!!!


分享到:


相關文章: