Apache ShardingSphere 如何實現分散式事務
陸敬尚,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)
事務併發執行時,保證併發時資料的正確性。比如:兩個事務同時修改一條資料,保證兩個事務按一定順序執行,使資料保持在一個正確的狀態。
面臨的挑戰
分散式事務相對單機事務來說面臨下面的挑戰:
-
原子性,對於單機事務來說,使用 undo log 和 redo log 就可以保證全部提交或者全部回滾。而分散式事務涉及多個物理節點,每個節點情況是不同的,有的節點日誌寫成功,有的節點日誌寫不成功。
-
網路的不穩定,對於單機來說,通訊是穩定的,任何操作都可以得到回覆,不論成功與失敗。而分散式場景下,網路是不穩定的,有可能一個操作是得不到回覆的,怎樣保證分散式事務的可用性(異常事務的清理、恢復等)是一個問題。
-
併發控制,隨著 MVCC 的出現,操作的可線性化(linearizable)成為了剛需。在單機資料庫中,可以很容易地產生全域性單調遞增的事務號,在分散式場景中則不然。
解決方案
原子提交
針對原子性和網路不穩定問題,目前主流的解決方案是 2PC,2PC 定義了兩個角色 TM(Transaction Manager)、RM(Resource Manager)。
在分散式場景下,一個事務的操作可能分佈在多個節點上,整個事務分兩個階段。
-
第一階段,RM 鎖定相關資源並執行具體操作,返回成功與否給 TM。
-
第二階段,TM 更具第一階段 RM 返回的結果,如果全部成功,執行最後的提交操作(事務狀態的更改,鎖狀態刪除等),如果有失敗的,則回滾。
說明:當然會有一些優化點,比如不涉及多節點的事務轉化為一階段提交等。
注意:兩階段提交協議只解決了提交的問題,要麼提交成功,要麼不成功,不存在部分成功的中間狀態。和事務隔離級別沒有必然關係。
併發控制
併發控制,就是保證併發執行的事務在某一種隔離級別上的執行策略。自從多版本控制(MVCC)出現,主流資料庫基本拋棄了以前的兩階段鎖模型。
併發控制本質是對資料讀和寫的併發的控制。併發控制的策略決定了隔離級別,併發控制要解決兩個問題。
-
決定併發的粒度,比如 MySQL 有行鎖(粒度為一行),表鎖(粒度為一個表)等
-
三種併發場景的行為:
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 實現。
-
app 連線到 Proxy 上,Proxy 建立一個 session 的物件和這個 connection 繫結。
-
app 執行 begin,Proxy 通過 Narayana TM 新建一個邏輯 transaction,和當前 session 繫結。
-
app 執行具體 SQL,session 負責建立到儲存 DB 的 connection,並把 connection 通過 Transaction.enlistResource() 介面把 connection 註冊到 transaction,執行 XA START {XID} 開啟事務,並執行路由改寫後的 SQL。
-
app 執行 commit 命令,transaction 中註冊的連線儲存 DB 的 connection,分別執行 xa prepare,當所有 connection 返回 ok,更新 transaction 狀態為 prepared,每個 connection 執行 xa commit,都返回 ok 更新 transaction 狀態為 commited,提交成功。如果 prepare 過程部分失敗,使用者可以通過 rollback 命令出發回滾,不處理則有後臺程序進行清理。
-
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 實現。
-
app 連線到 Proxy 上,Proxy 建立一個 session 的物件和這個 connection 繫結。
-
app 執行 begin,Proxy 通過 Seata TM 新建一個邏輯 transaction,和當前 session 繫結,並註冊到 Seata Server。
-
app 執行一條邏輯 SQL,session 負責建立到儲存 DB 的 connection,每個 connection 是 Seata 的 ConnectionProxy 例項,對路由改寫後的 actual sql 進行解析, 做一些攔截,比如:如果是修改操作,執行 begin 獲取本地鎖,執行一條 SQL, 執行 commit 釋放本地鎖,上報分支事務結果到 Seata Server。
-
app 執行 commit 命令,Proxy 中的 Seata TM 通知 Seata Server 後,直接返回 app,Seata Server 非同步和 Proxy 互動,進行刪除事務日誌。
-
app 執行 rollback 命令,Proxy 中的 Seata TM 通知 Seata Server 後,直接返回 app,Seata Server 非同步和 Proxy 互動,執行補償操作,刪除事務日誌。
詳細流程參考 Seata 官網。
使用示例
安裝包準備
以支援較好的 XA,整合 Narayana 的實現為例,進行介紹。由於 Narayana 的 License 問題,不能直接打包到安裝包內,需要新增額外的依賴。
根據官網下載好安裝包,解壓到 ${ShardingSphere} 目錄,往 ${ShardingSphere}/lib 目錄下新增以下 jar 包。(下載地址:https://mvnrepository.com/)
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 例項準備
-
準備兩個 MySQL 例項,127.0.0.1:3306,127.0.0.1:3307。
-
兩個 MySQL 例項分別建立使用者 root, 密碼為 12345678。
-
兩個 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:https://github.com/apache/shardingsphere/issues
貢獻指南:https://shardingsphere.apache.org/community/cn/contribute/
中文社群:https://community.sphere-ex.com/
參考文獻
1.ACID wiki:https://en.wikipedia.org/wiki/ACID
2.ANSI isolation levels:https://renenyffenegger.ch/notes/development/databases/SQL/transaction/isolation-level
3.《Distributed Transactions Are Evil》:https://wiki.c2.com/?DistributedTransactionsAreEvil
4.《Distributed transactions and why you should care》:https://towardsdatascience.com/distributed-transactions-and-why-you-should-care-116b6da8d72
5.《Fault-Tolerant Stream Processing at Internet Scale》:http://static.googleusercontent.com/media/research.google.com/en/us/pubs/archive/41378.pdf
6.《Oracle Two Phase Commit 2PC Tips》:http://www.dba-oracle.com/ttwophasecommit2pc.htm
7.《2PC: Concurrency Control and Recovery in Database Systems》:https://courses.cs.washington.edu/courses/cse551/09au/papers/CSE550BHG-Ch7.pdf
8.《An Empirical Evaluation of In-Memory Multi-Version Concurrency Control》:https://15721.courses.cs.cmu.edu/spring2018/papers/05-mvcc1/wu-vldb2017.pdf
9.《Concurrency Control And Recovery In Database Systems》:https://courses.cs.washington.edu/courses/cse551/09au/papers/CSE550BHG-Ch7.pdf
10.《Optimistic Concurrency Control》:https://15721.courses.cs.cmu.edu/spring2018/slides/04-occ.pdf)
11.《Base: An Acid Alternative》:https://queue.acm.org/detail.cfm?id=1394128
12.《Jepsen: CockroachDB beta-20160829》:https://jepsen.io/analyses/cockroachdb-beta-20160829
13.《Spanner: Google’s Globally-Distributed Database》:https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf