1. 程式人生 > 實用技巧 >開源的分散式事務解決方案-Seata

開源的分散式事務解決方案-Seata

Seata 是什麼?

(1)Seata 是一款開源的分散式事務解決方案,致力於在微服務架構下提供高效能和簡單易用的分散式事務服務。

(2)在 Seata 開源之前,Seata 對應的內部版本在阿里經濟體內部一直扮演著分散式一致性中介軟體的角色,幫助經濟體平穩的度過歷年的雙11,對各BU業務進行了有力的支撐。經過多年沉澱與積累,商業化產品先後在阿里雲、金融雲進行售賣。

(3)2019.1 為了打造更加完善的技術生態和普惠技術成果,Seata 正式宣佈對外開源,未來 Seata 將以社群共建的形式幫助其技術更加可靠與完備。

(4)Seata 將為使用者提供了 AT、TCC、SAGA 和 XA 事務模式,為使用者打造一站式的分散式解決方案。

AT 模式

前提

  • 基於支援本地 ACID 事務的關係型資料庫。
  • Java 應用,通過 JDBC 訪問資料庫。

整體機制

兩階段提交協議的演變:

  • 一階段:業務資料和回滾日誌記錄在同一個本地事務中提交,釋放本地鎖和連線資源。
  • 二階段:
    • 提交非同步化,非常快速地完成。
    • 回滾通過一階段的回滾日誌進行反向補償。

寫隔離

  • 一階段本地事務提交前,需要確保先拿到 全域性鎖
  • 拿不到 全域性鎖 ,不能提交本地事務。
  • 全域性鎖 的嘗試被限制在一定範圍內,超出範圍將放棄,並回滾本地事務,釋放本地鎖。

以一個示例來說明:

兩個全域性事務 tx1 和 tx2,分別對 a 表的 m 欄位進行更新操作,m 的初始值 1000。

tx1 先開始,開啟本地事務,拿到本地鎖,更新操作 m = 1000 - 100 = 900。本地事務提交前,先拿到該記錄的 全域性鎖 ,本地提交釋放本地鎖。 tx2 後開始,開啟本地事務,拿到本地鎖,更新操作 m = 900 - 100 = 800。本地事務提交前,嘗試拿該記錄的 全域性鎖 ,tx1 全域性提交前,該記錄的全域性鎖被 tx1 持有,tx2 需要重試等待 全域性鎖

tx1 二階段全域性提交,釋放 全域性鎖 。tx2 拿到 全域性鎖 提交本地事務。

如果 tx1 的二階段全域性回滾,則 tx1 需要重新獲取該資料的本地鎖,進行反向補償的更新操作,實現分支的回滾。

此時,如果 tx2 仍在等待該資料的 全域性鎖

,同時持有本地鎖,則 tx1 的分支回滾會失敗。分支的回滾會一直重試,直到 tx2 的 全域性鎖 等鎖超時,放棄 全域性鎖 並回滾本地事務釋放本地鎖,tx1 的分支回滾最終成功。

因為整個過程 全域性鎖 在 tx1 結束前一直是被 tx1 持有的,所以不會發生 髒寫 的問題。

讀隔離

在資料庫本地事務隔離級別 讀已提交(Read Committed) 或以上的基礎上,Seata(AT 模式)的預設全域性隔離級別是 讀未提交(Read Uncommitted)

如果應用在特定場景下,必需要求全域性的 讀已提交 ,目前 Seata 的方式是通過 SELECT FOR UPDATE 語句的代理。

SELECT FOR UPDATE 語句的執行會申請 全域性鎖 ,如果 全域性鎖 被其他事務持有,則釋放本地鎖(回滾 SELECT FOR UPDATE 語句的本地執行)並重試。這個過程中,查詢是被 block 住的,直到 全域性鎖 拿到,即讀取的相關資料是 已提交 的,才返回。

出於總體效能上的考慮,Seata 目前的方案並沒有對所有 SELECT 語句都進行代理,僅針對 FOR UPDATE 的 SELECT 語句。

工作機制

以一個示例來說明整個 AT 分支的工作過程。

業務表:product

Field Type Key
id bigint(20) PRI
name varchar(100)
since varchar(100)

AT 分支事務的業務邏輯:

update product set name = 'GTS' where name = 'TXC';

一階段

過程:

  1. 解析 SQL:得到 SQL 的型別(UPDATE),表(product),條件(where name = 'TXC')等相關的資訊。
  2. 查詢前映象:根據解析得到的條件資訊,生成查詢語句,定位資料。
select id, name, since from product where name = 'TXC';

得到前映象:

id name since
1 TXC 2014
  1. 執行業務 SQL:更新這條記錄的 name 為 'GTS'。
  2. 查詢後鏡像:根據前映象的結果,通過 主鍵 定位資料。
select id, name, since from product where id = 1;

得到後鏡像:

id name since
1 GTS 2014
  1. 插入回滾日誌:把前後映象資料以及業務 SQL 相關的資訊組成一條回滾日誌記錄,插入到 UNDO_LOG 表中。
{
	"branchId": 641789253,
	"undoItems": [{
		"afterImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "GTS"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"beforeImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "TXC"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"sqlType": "UPDATE"
	}],
	"xid": "xid:xxx"
}
  1. 提交前,向 TC 註冊分支:申請 product 表中,主鍵值等於 1 的記錄的 全域性鎖
  2. 本地事務提交:業務資料的更新和前面步驟中生成的 UNDO LOG 一併提交。
  3. 將本地事務提交的結果上報給 TC。

二階段-回滾

  1. 收到 TC 的分支回滾請求,開啟一個本地事務,執行如下操作。
  2. 通過 XID 和 Branch ID 查詢到相應的 UNDO LOG 記錄。
  3. 資料校驗:拿 UNDO LOG 中的後鏡與當前資料進行比較,如果有不同,說明資料被當前全域性事務之外的動作做了修改。這種情況,需要根據配置策略來做處理,詳細的說明在另外的文件中介紹。
  4. 根據 UNDO LOG 中的前映象和業務 SQL 的相關資訊生成並執行回滾的語句:
update product set name = 'TXC' where id = 1;
  1. 提交本地事務。並把本地事務的執行結果(即分支事務回滾的結果)上報給 TC。

二階段-提交

  1. 收到 TC 的分支提交請求,把請求放入一個非同步任務的佇列中,馬上返回提交成功的結果給 TC。
  2. 非同步任務階段的分支提交請求將非同步和批量地刪除相應 UNDO LOG 記錄。

附錄

回滾日誌表

UNDO_LOG Table:不同資料庫在型別上會略有差別。

以 MySQL 為例:

Field Type
branch_id bigint PK
xid varchar(100)
context varchar(128)
rollback_info longblob
log_status tinyint
log_created datetime
log_modified datetime
-- 注意此處0.7.0+ 增加欄位 context
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

TCC 模式

回顧總覽中的描述:一個分散式的全域性事務,整體是 兩階段提交 的模型。全域性事務是由若干分支事務組成的,分支事務要滿足 兩階段提交 的模型要求,即需要每個分支事務都具備自己的:

  • 一階段 prepare 行為
  • 二階段 commit 或 rollback 行為

根據兩階段行為模式的不同,我們將分支事務劃分為 Automatic (Branch) Transaction ModeManual (Branch) Transaction Mode.

AT 模式(參考連結 TBD)基於 支援本地 ACID 事務關係型資料庫

  • 一階段 prepare 行為:在本地事務中,一併提交業務資料更新和相應回滾日誌記錄。
  • 二階段 commit 行為:馬上成功結束,自動 非同步批量清理回滾日誌。
  • 二階段 rollback 行為:通過回滾日誌,自動 生成補償操作,完成資料回滾。

相應的,TCC 模式,不依賴於底層資料資源的事務支援:

  • 一階段 prepare 行為:呼叫 自定義 的 prepare 邏輯。
  • 二階段 commit 行為:呼叫 自定義 的 commit 邏輯。
  • 二階段 rollback 行為:呼叫 自定義 的 rollback 邏輯。

所謂 TCC 模式,是指支援把 自定義 的分支事務納入到全域性事務的管理中。

Saga 模式

Saga模式是SEATA提供的長事務解決方案,在Saga模式中,業務流程中每個參與者都提交本地事務,當出現某一個參與者失敗則補償前面已經成功的參與者,一階段正向服務和二階段補償服務都由業務開發實現。

理論基礎:Hector & Kenneth 發表論⽂ Sagas (1987)

適用場景:

  • 業務流程長、業務流程多
  • 參與者包含其它公司或遺留系統服務,無法提供 TCC 模式要求的三個介面

優勢:

  • 一階段提交本地事務,無鎖,高效能
  • 事件驅動架構,參與者可非同步執行,高吞吐
  • 補償服務易於實現

缺點:


原文連結:http://seata.io/zh-cn/docs/overview/what-is-seata.html

更多資訊請關注公眾號:「軟體老王」,關注不迷路,軟體老王和他的IT朋友們,分享一些他們的技術見解和生活故事。