Apache ShardingSphere 如何實現分散式事務

SphereEx發表於2022-04-20


陸敬尚,Apache ShardingSphere Committer,SphereEx 基礎設施研發工程師,熱愛開源,熱愛資料庫技術,目前專注於 Apache ShardingSphere 事務模組的開發。

背景

隨著業務的快速發展,資料的不斷膨脹,流量負載的增加,業務系統遇到了強烈的挑戰,對資料庫系統可擴充套件性提出了強烈的訴求。Oracle、MySQL、SQL Server、PostgreSQL 傳統單機資料庫線上擴充套件上的問題日益凸顯。為了解決擴充套件問題,出現了可水平擴充套件的分散式資料庫,於是分散式事務問題成為了必須面對的問題。

在這種背景下,ShardingSphere 提供了一套分散式資料庫增強計算引擎,通過可插拔架構構建基於資料庫之上的生態系統,提供了分散式事務的能力。

事務介紹

事務語義

事務語義定義了四個特性:原子性(Atomicity)、永續性(Durability)、一致性(Consistency)、隔離性(Isolatation)。

原子性(Atomicity)

在分散式場景下,一個事務的操作可能分佈在多個物理節點上,保證在多個節點上的操作都成功,或都不成功。

永續性(Durability)

事務提交後,即使斷電,事務的操作也是有效的。

一致性(Consistency)

注意:不是 CAP 理論中 C,CAP 中的 C 指的是多副本之間的資料一致問題,這裡是不同層次的抽象。

站在使用者的角度,資料從一個狀態,轉移到另外一個狀態,兩個狀態都滿足一定的約束。比如:

銀行賬戶資料,賬戶 A 有 500 元,賬戶 B 有 500 元,總額 1000,在一個事務中,執行完 A 和 B 的轉賬操作後,A 和 B 的賬戶總額還是 1000。

隔離性(Isolatation)

事務併發執行時,保證併發時資料的正確性。比如:兩個事務同時修改一條資料,保證兩個事務按一定順序執行,使資料保持在一個正確的狀態。

面臨的挑戰

分散式事務相對單機事務來說面臨下面的挑戰:

  1. 原子性,對於單機事務來說,使用 undo log 和 redo log 就可以保證全部提交或者全部回滾。而分散式事務涉及多個物理節點,每個節點情況是不同的,有的節點日誌寫成功,有的節點日誌寫不成功。

  2. 網路的不穩定,對於單機來說,通訊是穩定的,任何操作都可以得到回覆,不論成功與失敗。而分散式場景下,網路是不穩定的,有可能一個操作是得不到回覆的,怎樣保證分散式事務的可用性(異常事務的清理、恢復等)是一個問題。

  3. 併發控制,隨著 MVCC 的出現,操作的可線性化(linearizable)成為了剛需。在單機資料庫中,可以很容易地產生全域性單調遞增的事務號,在分散式場景中則不然。

解決方案

原子提交

針對原子性和網路不穩定問題,目前主流的解決方案是 2PC,2PC 定義了兩個角色 TM(Transaction Manager)、RM(Resource Manager)。

在分散式場景下,一個事務的操作可能分佈在多個節點上,整個事務分兩個階段。

  1. 第一階段,RM 鎖定相關資源並執行具體操作,返回成功與否給 TM。

  2. 第二階段,TM 更具第一階段 RM 返回的結果,如果全部成功,執行最後的提交操作(事務狀態的更改,鎖狀態刪除等),如果有失敗的,則回滾。

說明:當然會有一些優化點,比如不涉及多節點的事務轉化為一階段提交等。

注意:兩階段提交協議只解決了提交的問題,要麼提交成功,要麼不成功,不存在部分成功的中間狀態。和事務隔離級別沒有必然關係。

併發控制

併發控制,就是保證併發執行的事務在某一種隔離級別上的執行策略。自從多版本控制(MVCC)出現,主流資料庫基本拋棄了以前的兩階段鎖模型。

併發控制本質是對資料讀和寫的併發的控制。併發控制的策略決定了隔離級別,併發控制要解決兩個問題。

  1. 決定併發的粒度,比如 MySQL 有行鎖(粒度為一行),表鎖(粒度為一個表)等

  2. 三種併發場景的行為:

a. 讀讀併發,不需要特殊處理,因為不涉及資料的變更。

b. 寫寫併發,不能同時併發,否則會產生資料混亂。

c. 讀寫併發,效能優化主要在這裡做,有多種併發控制機制,基本都選擇了多版本併發控制(MVCC)。

MVCC 併發控制模型

現有主流實現方式有兩種:

  • 基於事務 ID 和 ReadView

每次事務獲取事務 ID,標識事務的開啟順序,通過活躍事務列表來獲取快照,儲存多個以事務 ID 為版本的資料,從而達到併發控制的效果。MySQL、Postgres-XL 都是採取的這種方案。

  • 基於 timestamp

引入 timestamp,通過在資料中新增 timestamp 相關屬性,通過對比資料的 commitTs(commit timestamp) 和 Snapshot timestamp 來判斷可見性,從而達到可線性化的併發控制效果。Spanner 採用的這種方案。

上面兩種方案都離不開全域性事務號的生成,常見的全域性事務號生成機制有 TrueTime(Spanner 採用),HLC(CockroachDB 採用有誤差的 HLC),TSO(Timestamp Oracle),詳細原理見參考文獻。

ShardingSphere 事務設計

ShardingSphere 事務功能建立在儲存 DB 的本地事務之上,提供 LOCAL、XA、BASE 三種模式事務,使用者只需使用原生事務方式(begin/commit/rollback),就可以使用三種模式,在一致性和效能做合適的權衡。

LOCAL

LOCAL 模式直接建立在儲存 DB 的本地事務上的,會存在一定的原子性問題,當然效能是最高的,如果可以容忍這個問題,這將是一個不錯的選擇。

XA

XA 模式,XA 協議是基於 2PC 定義的一套互動協議,定義了 xa start/prepare/end/commit/rollback 等介面,常用的實現有 Narayana、Atomics, ShardingSphere 整合了 Narayana、Atomics 的 XA 實現。

  1. app 連線到 Proxy 上,Proxy 建立一個 session 的物件和這個 connection 繫結。

  2. app 執行 begin,Proxy 通過 Narayana TM 新建一個邏輯 transaction,和當前 session 繫結。

  3. app 執行具體 SQL,session 負責建立到儲存 DB 的 connection,並把 connection 通過 Transaction.enlistResource() 介面把 connection 註冊到 transaction,執行 XA START {XID} 開啟事務,並執行路由改寫後的 SQL。

  4. app 執行 commit 命令,transaction 中註冊的連線儲存 DB 的 connection,分別執行 xa prepare,當所有 connection 返回 ok,更新 transaction 狀態為 prepared,每個 connection 執行 xa commit,都返回 ok 更新 transaction 狀態為 commited,提交成功。如果 prepare 過程部分失敗,使用者可以通過 rollback 命令出發回滾,不處理則有後臺程式進行清理。

  5. app 執行 rollback 命令,transaction 中註冊的連線儲存 DB 的 connection,分別執行 xa rollback,進行回滾。


BASE

Base(Basically Available, Soft State, Eventually Consistent)模式,BASE 事務是 CAP 理論中 C 和 A 權衡的結果,Seata 的 AT 模式是 Base 事務的一種實現,ShardingSphere 整合了 Seata 的 AT 實現。

  1. app 連線到 Proxy 上,Proxy 建立一個 session 的物件和這個 connection 繫結。

  2. app 執行 begin,Proxy 通過 Seata TM 新建一個邏輯 transaction,和當前 session 繫結,並註冊到 Seata Server。

  3. app 執行一條邏輯 SQL,session 負責建立到儲存 DB 的 connection,每個 connection 是 Seata 的 ConnectionProxy 例項,對路由改寫後的 actual sql 進行解析, 做一些攔截,比如:如果是修改操作,執行 begin 獲取本地鎖,執行一條 SQL, 執行 commit 釋放本地鎖,上報分支事務結果到 Seata Server。

  4. app 執行 commit 命令,Proxy 中的 Seata TM 通知 Seata Server 後,直接返回 app,Seata Server 非同步和 Proxy 互動,進行刪除事務日誌。

  5. app 執行 rollback 命令,Proxy 中的 Seata TM 通知 Seata Server 後,直接返回 app,Seata Server 非同步和 Proxy 互動,執行補償操作,刪除事務日誌。


詳細流程參考 Seata 官網。

使用示例

安裝包準備

以支援較好的 XA,整合 Narayana 的實現為例,進行介紹。由於 Narayana 的 License 問題,不能直接打包到安裝包內,需要新增額外的依賴。

根據官網下載好安裝包,解壓到 ${ShardingSphere} 目錄,往 ${ShardingSphere}/lib 目錄下新增以下 jar 包。(下載地址: )

jta-5.12.4.Final.jar
arjuna-5.12.4.Final.jar
common-5.12.4.Final.jar
jboss-connector-api_1.7_spec-1.0.0.Final.jar                                             | ------------------------------------------------------------------------------------------------------------------------------------
jboss-logging-3.2.1.Final.jar                                                    | ------------------------------------------------------------------------------------------------------------------------------------
jboss-transaction-api_1.2_spec-1.0.0.Alpha3.jar                                           | ------------------------------------------------------------------------------------------------------------------------------------
jboss-transaction-spi-7.6.0.Final.jar
mysql-connector-java-5.1.47.jar                                                   | ------------------------------------------------------------------------------------------------------------------------------------
narayana-jts-integration-5.12.4.Final.jar
shardingsphere-transaction-xa-narayana-5.1.1-SNAPSHOT.jar

MySQL 例項準備

  1. 準備兩個 MySQL 例項,127.0.0.1:3306,127.0.0.1:3307。

  2. 兩個 MySQL 例項分別建立使用者 root, 密碼為 12345678。

  3. 兩個 MySQL 例項分別建立 test 庫。

ShardingSphere-Proxy 配置

修改 server.yaml 事務配置

rules:  - !AUTHORITY
    users:      - root@%:root      - sharding@:sharding    provider:      type: ALL_PRIVILEGES_PERMITTED
  - !TRANSACTION
    defaultType: XA
    providerType: Narayana

修改 conf/conf-sharding.yaml

dataSources:
  ds_0:
    url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false    username: root
    password: 12345678
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  ds_1:
    url: jdbc:mysql://127.0.0.1:3307/test?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false    username: root
    password: 12345678
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
rules:
  - !SHARDING
    tables:
      account:
        actualDataNodes: ds_${0..1}.account${0..1}        tableStrategy:
          standard:
            shardingColumn: id
            shardingAlgorithmName: account_inline
        keyGenerateStrategy:
          column: id
          keyGeneratorName: snowflake
    defaultDatabaseStrategy:
      standard:
        shardingColumn: id
        shardingAlgorithmName: database_inline
    defaultTableStrategy:
      none:
    shardingAlgorithms:
      database_inline:
        type: INLINE
        props:
          algorithm-expression: ds_${id % 2}      account_inline:
        type: INLINE
        props:
          algorithm-expression: account${id % 2}    keyGenerators:
      snowflake:
        type: SNOWFLAKE
        props:
          worker-id: 123

啟動ShardingSphere-Proxy

參考如下命令啟動 Proxy:

cd ${ShardingSphere}./bin/start.sh

使用 ShardingSphere-Proxy

使用 MySQL Client 連線 ShardingSphere-Proxy 進行測試,參考如下命令。

mysql -h127.0.0.1 -P3307 -uroot -proot
mysql> use sharding_db;
Database changed
mysql> create table account(id int, balance float ,transaction_id int);
Query OK, 0 rows affected (0.12 sec)
mysql> select * from account;Empty set (0.02 sec)
mysql> begin;
Query OK, 0 rows affected (0.09 sec)
mysql> insert into account(id, balance, transaction_id) values(1,1,1),(2,2,2);
Query OK, 2 rows affected (0.53 sec)
mysql> select * from account;
+------+---------+----------------+
| id  | balance | transaction_id |
+------+---------+----------------+
|  2 |   2.0 |       2 |
|  1 |   1.0 |       1 |
+------+---------+----------------+2 rows in set (0.03 sec)
mysql> commit;
Query OK, 0 rows affected (0.05 sec)
mysql> select * from account;
+------+---------+----------------+
| id  | balance | transaction_id |
+------+---------+----------------+
|  2 |   2.0 |       2 |
|  1 |   1.0 |       1 |
+------+---------+----------------+2 rows in set (0.02 sec)

未來規劃

現在 ShardingSphere 的分散式事務整合了第三方的 2PC 實現方案,提供了原子性的保證,隔離性依賴於儲存 DB 的隔離保證,提供了可用的事務功能。未來基於全域性 Timestamp 實現 MVCC,結合 2PC,對事務隔離語義提供更好的支援。歡迎大家關注 ShardingSphere 的成長。

如果大家對 Apache ShardingSphere 有任何疑問或建議,歡迎在 GitHub issue 列表提出,或可前往中文社群交流討論。

GitHub issue:

貢獻指南:

中文社群:

參考文獻

1.ACID wiki:

2.ANSI isolation levels: https://renenyffenegger.ch/notes/development/databases/SQL/transaction/isolation-level

3.《Distributed Transactions Are Evil》:

4.《Distributed transactions and why you should care》:

5.《Fault-Tolerant Stream Processing at Internet Scale》:

6.《Oracle Two Phase Commit 2PC Tips》:

7.《2PC: Concurrency Control and Recovery in Database Systems》:

8.《An Empirical Evaluation of In-Memory Multi-Version Concurrency Control》:

9.《Concurrency Control And Recovery In Database Systems》:

10.《Optimistic Concurrency Control》:

11.《Base: An Acid Alternative》:

12.《Jepsen: CockroachDB beta-20160829》:

13.《Spanner: Google’s Globally-Distributed Database》:

歡迎新增社群經理微信(ss_assistant_1)加入交流群,與眾多 ShardingSphere 愛好者一同交流。


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

相關文章