上一篇文章說到了Seata 為使用者提供了 AT、TCC、SAGA 和 XA 事務模式,為使用者打造一站式的分散式解決方案。那麼接下來我們將要針對於AT模式下進行分散式事務開發的原理進行介紹以及實戰。
在AT、TCC、SAGA 和 XA 這四種事務模式中使用最多,最方便的就是 AT 模式。與其他事務模式相比,AT 模式可以應對大多數的業務場景,且基本可以做到無業務入侵,開發人員能夠有更多的精力關注於業務邏輯開發。
任何應用想要使用Seata的 AT 模式對分散式事務進行控制,必須滿足以下 2 個前提:
Seata服務進行下載的地址:https://seata.io/zh-cn/blog/download.html,存取之後可以看到下面的資源中,可以直接進行下載,如下圖所示。
但是由於官方維護的稍微緩慢,所以並不是最新的版本,如果你想要下載較新的版本,可以去官方的Git倉庫中進行下載對應的版本檔案包。地址為:https://github.com/seata/seata/releases,可以看到下面的最新版本已經到了1.6.1了
我們選擇下載對應的可執行包即可。
SEATA AT模式需要針對業務中涉及的各個資料庫表,分別建立一個UNDO_LOG(回滾紀錄檔)表。不同資料庫在建立 UNDO_LOG 表時會略有不同,以 MySQL 為例,其 UNDO_LOG 表的創表語句如下:
-- 注意此處0.3.0+ 增加唯一索引 ux_undo_log
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,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
下載伺服器軟體包後,將其解壓縮。主要通過指令碼進行啟動Seata服務
sh seata-server.sh
啟動服務。cmd seata-server.bat
啟動服務。其中引數的選擇範圍如下所示
--host, -h(簡略指令)該地址向註冊中心公開,其他服務可以通過該ip存取seata-server,預設: 0.0.0.0
--port, -p(簡略指令) 監聽的埠,預設值為8091
--storeMode, -m(簡略指令)紀錄檔儲存模式 : file(檔案)、db(資料庫),預設為:file
--help 幫助指令
例如執行shell指令碼
sh seata-server.sh -p 8091 -h 127.0.0.1 -m file
Seata的AT模式工作時大致可以分為以兩個階段,下面我們就結合一個範例來對 AT 模式的工作機制進行介紹。
兩階段提交協定的演變:
Seata AT模式一階段的工作流程如下圖所示
業務資料和回滾紀錄檔記錄在同一個本地事務中提交,釋放本地鎖和連線資源。
Seata攔截並解析業務SQL,得到SQL 的操作型別(INSERT/UPDATE/DELETE)、表名(tableXXX)、判斷條件(where condition = value)等相關資訊。
根據得到的業務SQL資訊,生成「前映象查詢語句」。
select * from tableXX where condition=value;
執行「前映象查詢語句」,得到即將執行操作的資料,並將其儲存為「前映象資料(beforeImage)」。
執行業務SQL,例如(update tableXX set parameter = 'value' where condition = value;),將這條記錄的進行修改。
查詢後映象:根據「前映象資料」的主鍵(id : X),生成「後映象查詢語句」。
select * from tableXX where condition=value;
執行「後映象查詢語句」,得到執行業務操作後的資料,並將其儲存為「後映象資料(afterImage)」。
將前後映象資料和業務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"
}
兩個全域性事務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 語句。
業務資料的更新和前面步驟中生成的UNDO LOG一併提交,將本地事務提交的結果上報給TC。
收到TC的分支回滾請求,開啟一個本地事務。
通過XID和Branch ID查詢到相應的UNDO LOG 記錄。
資料校驗:拿 UNDO LOG 中的後鏡與當前資料進行比較,如果有不同,說明資料被當前全域性事務之外的動作做了修改。這種情況,需要根據設定策略來做處理,詳細的說明在另外的檔案中介紹。
根據 UNDO LOG 中的前映象和業務SQL的相關資訊生成並執行回滾的語句:
update TableXXX set parameter = 'XXX' where condition = value;
收到TC的分支提交請求,把請求放入一個非同步任務的佇列中,馬上返回提交成功的結果給 TC。
非同步任務階段的分支提交請求將非同步和批次地刪除相應 UNDO LOG 記錄。
本文來自部落格園,作者:洛神灬殤,轉載請註明原文連結:https://www.cnblogs.com/liboware/p/17050674.html,任何足夠先進的科技,都與魔法無異。