Java中的分布式事務(wù)Seata詳解
Seata 是什么?
Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡(jiǎn)單易用的分布式事務(wù)服務(wù)。
Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務(wù)模式,為用戶打造一站式的分布式解決方案。
AT 模式
前提
- 基于支持本地 ACID 事務(wù)的關(guān)系型數(shù)據(jù)庫。
- Java 應(yīng)用,通過 JDBC 訪問數(shù)據(jù)庫。
整體機(jī)制
一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交,釋放本地鎖和連接資源。
二階段:
- 提交異步化,非??焖俚赝瓿?。
- 回滾通過一階段的回滾日志進(jìn)行反向補(bǔ)償。
寫隔離
- 一階段本地事務(wù)提交前,需要確保先拿到全局鎖。
- 拿不到全局鎖,不能提交本地事務(wù)。
- 拿全局鎖的嘗試被限制在一定范圍內(nèi),超出范圍將放棄,并回滾本地事務(wù),釋放本地鎖。
以一個(gè)示例來說明:
兩個(gè)全局事務(wù) tx1 和 tx2,分別對(duì) a 表的 m 字段進(jìn)行更新操作,m 的初始值 1000。
tx1 先開始,開啟本地事務(wù),拿到本地鎖,更新操作 m = 1000 - 100 = 900。本地事務(wù)提交前,先拿到該記錄的全局鎖,本地提交釋放本地鎖。 tx2 后開始,開啟本地事務(wù),拿到本地鎖,更新操作 m = 900 - 100 = 800。本地事務(wù)提交前,嘗試拿該記錄的全局鎖,tx1 全局提交前,該記錄的全局鎖被 tx1 持有,tx2 需要重試等待全局鎖。
tx1 二階段全局提交,釋放全局鎖。tx2 拿到全局鎖提交本地事務(wù)。
如果 tx1 的二階段全局回滾,則 tx1 需要重新獲取該數(shù)據(jù)的本地鎖,進(jìn)行反向補(bǔ)償?shù)母虏僮?,?shí)現(xiàn)分支的回滾。
此時(shí),如果 tx2 仍在等待該數(shù)據(jù)的全局鎖,同時(shí)持有本地鎖,則 tx1 的分支回滾會(huì)失敗。分支的回滾會(huì)一直重試,直到 tx2 的全局鎖等鎖超時(shí),放棄全局鎖并回滾本地事務(wù)釋放本地鎖,tx1 的分支回滾最終成功。
因?yàn)檎麄€(gè)過程全局鎖在 tx1 結(jié)束前一直是被 tx1 持有的,所以不會(huì)發(fā)生臟寫的問題。
讀隔離
在數(shù)據(jù)庫本地事務(wù)隔離級(jí)別讀已提交(Read Committed)或以上的基礎(chǔ)上,Seata(AT 模式)的默認(rèn)全局隔離級(jí)別是讀未提交(Read Uncommitted)。
如果應(yīng)用在特定場(chǎng)景下,必需要求全局的讀已提交,目前 Seata 的方式是通過 SELECT FOR UPDATE 語句的代理。
SELECT FOR UPDATE 語句的執(zhí)行會(huì)申請(qǐng)全局鎖,如果全局鎖被其他事務(wù)持有,則釋放本地鎖(回滾 SELECT FOR UPDATE 語句的本地執(zhí)行)并重試。這個(gè)過程中,查詢是被 block 住的,直到全局鎖拿到,即讀取的相關(guān)數(shù)據(jù)是已提交的,才返回。
出于總體性能上的考慮,Seata 目前的方案并沒有對(duì)所有 SELECT 語句都進(jìn)行代理,僅針對(duì) FOR UPDATE 的 SELECT 語句。
工作機(jī)制
以一個(gè)示例來說明整個(gè) AT 分支的工作過程。
業(yè)務(wù)表: product
Field | Type | Key |
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
AT 分支事務(wù)的業(yè)務(wù)邏輯:
update product set name = 'GTS' where name = 'TXC';
一階段
過程:
- 解析 SQL:得到 SQL 的類型(UPDATE),表(product),條件(where name = ‘TXC’)等相關(guān)的信息。
- 查詢前鏡像:根據(jù)解析得到的條件信息,生成查詢語句,定位數(shù)據(jù)。
select id, name, since from product where name = 'TXC';
得到前鏡像:
id | name | since |
1 | TXC | 2014 |
- 執(zhí)行業(yè)務(wù) SQL:更新這條記錄的 name 為 ‘GTS’。
- 查詢后鏡像:根據(jù)前鏡像的結(jié)果,通過主鍵定位數(shù)據(jù)。
select id, name, since from product where id = 1`;
得到后鏡像:
id | name | since |
1 | GTS | 2014 |
- 插入回滾日志:把前后鏡像數(shù)據(jù)以及業(yè)務(wù) SQL 相關(guān)的信息組成一條回滾日志記錄,插入到 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" }
- 提交前,向 TC 注冊(cè)分支:申請(qǐng)product表中,主鍵值等于 1 的記錄的全局鎖。
- 本地事務(wù)提交:業(yè)務(wù)數(shù)據(jù)的更新和前面步驟中生成的 UNDO LOG 一并提交。
- 將本地事務(wù)提交的結(jié)果上報(bào)給 TC。
二階段-回滾
- 收到 TC 的分支回滾請(qǐng)求,開啟一個(gè)本地事務(wù),執(zhí)行如下操作。
- 通過 XID 和 Branch ID 查找到相應(yīng)的 UNDO LOG 記錄。
- 數(shù)據(jù)校驗(yàn):拿 UNDO LOG 中的后鏡與當(dāng)前數(shù)據(jù)進(jìn)行比較,如果有不同,說明數(shù)據(jù)被當(dāng)前全局事務(wù)之外的動(dòng)作做了修改。這種情況,需要根據(jù)配置策略來做處理,詳細(xì)的說明在另外的文檔中介紹。
- 根據(jù) UNDO LOG 中的前鏡像和業(yè)務(wù) SQL 的相關(guān)信息生成并執(zhí)行回滾的語句:
update product set name = 'TXC' where id = 1;
- 提交本地事務(wù)。并把本地事務(wù)的執(zhí)行結(jié)果(即分支事務(wù)回滾的結(jié)果)上報(bào)給 TC。
二階段-提交
- 收到 TC 的分支提交請(qǐng)求,把請(qǐng)求放入一個(gè)異步任務(wù)的隊(duì)列中,馬上返回提交成功的結(jié)果給 TC。
- 異步任務(wù)階段的分支提交請(qǐng)求將異步和批量地刪除相應(yīng) UNDO LOG 記錄。
附錄
回滾日志表
UNDO_LOG Table:不同數(shù)據(jù)庫在類型上會(huì)略有差別。
以 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 模式
回顧總覽中的描述:一個(gè)分布式的全局事務(wù),整體是兩階段提交的模型。
全局事務(wù)是由若干分支事務(wù)組成的,分支事務(wù)要滿足兩階段提交的模型要求,即需要每個(gè)分支事務(wù)都具備自己的:
- 一階段 prepare 行為
- 二階段 commit 或 rollback 行為
根據(jù)兩階段行為模式的不同,我們將分支事務(wù)劃分為Automatic (Branch) Transaction Mode和Manual (Branch) Transaction Mode.
AT 模式基于支持本地 ACID 事務(wù)的關(guān)系型數(shù)據(jù)庫:
- 一階段 prepare 行為:在本地事務(wù)中,一并提交業(yè)務(wù)數(shù)據(jù)更新和相應(yīng)回滾日志記錄。
- 二階段 commit 行為:馬上成功結(jié)束,自動(dòng)異步批量清理回滾日志。
- 二階段 rollback 行為:通過回滾日志,自動(dòng)生成補(bǔ)償操作,完成數(shù)據(jù)回滾。
相應(yīng)的,TCC 模式,不依賴于底層數(shù)據(jù)資源的事務(wù)支持:
- 一階段 prepare 行為:調(diào)用自定義的 prepare 邏輯。
- 二階段 commit 行為:調(diào)用自定義的 commit 邏輯。
- 二階段 rollback 行為:調(diào)用自定義的 rollback 邏輯。
所謂 TCC 模式,是指支持把自定義的分支事務(wù)納入到全局事務(wù)的管理中。
Saga 模式
Saga模式是SEATA提供的長(zhǎng)事務(wù)解決方案,在Saga模式中,業(yè)務(wù)流程中每個(gè)參與者都提交本地事務(wù),當(dāng)出現(xiàn)某一個(gè)參與者失敗則補(bǔ)償前面已經(jīng)成功的參與者,一階段正向服務(wù)和二階段補(bǔ)償服務(wù)都由業(yè)務(wù)開發(fā)實(shí)現(xiàn)。
理論基礎(chǔ):Hector & Kenneth 發(fā)表論? Sagas (1987)
適用場(chǎng)景:
- 業(yè)務(wù)流程長(zhǎng)、業(yè)務(wù)流程多
- 參與者包含其它公司或遺留系統(tǒng)服務(wù),無法提供 TCC 模式要求的三個(gè)接口
優(yōu)勢(shì):
- 一階段提交本地事務(wù),無鎖,高性能
- 事件驅(qū)動(dòng)架構(gòu),參與者可異步執(zhí)行,高吞吐
- 補(bǔ)償服務(wù)易于實(shí)現(xiàn)
缺點(diǎn):
- 不保證隔離性
到此這篇關(guān)于Java中的分布式事務(wù)Seata詳解的文章就介紹到這了,更多相關(guān)Java分布式事務(wù)Seata內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入理解Java中的構(gòu)造函數(shù)引用和方法引用
java構(gòu)造函數(shù),也叫構(gòu)造方法,是java中一種特殊的函數(shù)。函數(shù)名與相同,無返回值。方法引用是用來直接訪問類或者實(shí)例的已經(jīng)存在的方法或者構(gòu)造方法。下面我們來詳細(xì)了解一下它們吧2019-06-06Java 中HashCode作用_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java 中HashCode作用以及hashcode對(duì)于一個(gè)對(duì)象的重要性,對(duì)java中hashcode的作用相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2017-05-05Servlet3.0實(shí)現(xiàn)文件上傳的方法
本篇文章主要介紹了Servlet實(shí)現(xiàn)文件上傳的方法,所謂文件上傳就是將本地的文件發(fā)送到服務(wù)器中保存。有興趣的可以了解一下。2017-03-03Java實(shí)現(xiàn)數(shù)據(jù)庫圖片上傳功能詳解
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)數(shù)據(jù)庫圖片上傳功能,包含從數(shù)據(jù)庫拿圖片傳遞前端渲染,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-03-03swagger的請(qǐng)求參數(shù)不顯示,@Apimodel的坑點(diǎn)及解決
這篇文章主要介紹了swagger的請(qǐng)求參數(shù)不顯示,@Apimodel的坑點(diǎn)及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11