精彩看點:【深入淺出Seata原理及實戰】「入門基礎專題」探索Seata服務的AT模式下的分布式開發實戰指南(2)

2023-01-14 11:16:40 來源:51CTO博客

承接上文

上一篇文章說到了Seata 為用戶提供了 AT、TCC、SAGA 和 XA 事務模式,為用戶打造一站式的分布式解決方案。那么接下來我們將要針對于AT模式下進行分布式事務開發的原理進行介紹以及實戰。

Seata AT模式

在AT、TCC、SAGA 和 XA 這四種事務模式中使用最多,最方便的就是 AT 模式。與其他事務模式相比,AT 模式可以應對大多數的業務場景,且基本可以做到無業務入侵,開發人員能夠有更多的精力關注于業務邏輯開發。


【資料圖】

使用AT模式的前提

任何應用想要使用Seata的 AT 模式對分布式事務進行控制,必須滿足以下 2 個前提:

必須使用支持本地 ACID 事務特性的關系型數據庫,例如 MySQL、Oracle 等;應用程序必須是使用 JDBC 對數據庫進行訪問的 JAVA 應用。

Seata安裝使用

下載地址

Seata服務進行下載的地址:??https://seata.io/zh-cn/blog/download.html,訪問之后可以看到下面的資源中,可以直接進行下載,如下圖所示。??

但是由于官方維護的稍微緩慢,所以并不是最新的版本,如果你想要下載較新的版本,可以去官方的Git倉庫中進行下載對應的版本文件包。地址為:??https://github.com/seata/seata/releases,可以看到下面的最新版本已經到了1.6.1了??

我們選擇下載對應的可執行包即可。

創建UNDO_LOG表

SEATA AT模式需要針對業務中涉及的各個數據庫表,分別創建一個UNDO_LOG(回滾日志)表。不同數據庫在創建 UNDO_LOG 表時會略有不同,以 MySQL 為例,其 UNDO_LOG 表的創表語句如下:

-- 注意此處0.3.0+ 增加唯一索引 ux_undo_logCREATE 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服務

Seata Server 目錄中包含以下子目錄:
bin:用于存放Seata Server可執行命令。conf:用于存放Seata Server的配置文件。lib:用于存放Seata Server依賴的各種 Jar 包。logs:用于存放Seata Server的日志。
Seata Server的執行腳本
??seata-server.sh??:主要是為Linux和Mac系統準備的啟動腳本。執行??sh seata-server.sh??啟動服務。seata-server.bat:主要是為Windows系統準備的啟動腳本。執行??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

AT 模式的工作機制

Seata的AT模式工作時大致可以分為以兩個階段,下面我們就結合一個實例來對 AT 模式的工作機制進行介紹。

整體機制

兩階段提交協議的演變:

一階段:業務數據回滾日志記錄在同一個本地事務中提交,釋放本地鎖和連接資源。二階段:提交異步化,非常快速地完成。回滾通過一階段的回滾日志進行反向補償。
AT模式一階段

Seata AT模式一階段的工作流程如下圖所示

業務數據和回滾日志記錄在同一個本地事務中提交,釋放本地鎖和連接資源。

第一子階段-獲取SQL的基本信息

Seata攔截并解析業務SQL,得到SQL 的操作類型(INSERT/UPDATE/DELETE)、表名(tableXXX)、判斷條件(where condition = value)等相關信息。

第二子階段-查詢并備份【執行之前】的數據快照

根據得到的業務SQL信息,生成“前鏡像查詢語句”。

select  *  from tableXX where condition=value;

執行“前鏡像查詢語句”,得到即將執行操作的數據,并將其保存為“前鏡像數據(beforeImage)”。

第三子階段-執行業務操作的SQL語句

執行業務SQL,例如(update tableXX set parameter = "value" where condition = value;),將這條記錄的進行修改。

第四子階段-查詢業務操作之后的數據,并且保存下來

查詢后鏡像:根據“前鏡像數據”的主鍵(id : X),生成“后鏡像查詢語句”。

select  *  from tableXX where condition=value;

執行“后鏡像查詢語句”,得到執行業務操作后的數據,并將其保存為“后鏡像數據(afterImage)”。

第五子階段-插入保存回滾日志記錄到undo_log表中

將前后鏡像數據和業務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"}
提交前需要獲取申請本地鎖
提交前,向TC注冊分支:申請TableXXX表中,id主鍵等于N的記錄的全局鎖 。需要確保先拿到全局鎖 。拿不到全局鎖 ,不能提交本地事務拿到全局鎖,會被限制在一定范圍內,超出范圍將放棄,并回滾本地事務,釋放本地鎖
示例說明:

兩個全局事務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。

AT模式二階段-回滾操作
收到TC的分支回滾請求,開啟一個本地事務。通過XID和Branch ID查找到相應的UNDO LOG 記錄。數據校驗:拿 UNDO LOG 中的后鏡與當前數據進行比較,如果有不同,說明數據被當前全局事務之外的動作做了修改。這種情況,需要根據配置策略來做處理,詳細的說明在另外的文檔中介紹。根據 UNDO LOG 中的前鏡像和業務SQL的相關信息生成并執行回滾的語句:
update TableXXX set parameter = "XXX" where condition = value;
提交本地事務,并把本地事務的執行結果(即分支事務回滾的結果)上報給 TC。
AT模式二階段-提交操作
收到TC的分支提交請求,把請求放入一個異步任務的隊列中,馬上返回提交成功的結果給 TC。異步任務階段的分支提交請求將異步和批量地刪除相應 UNDO LOG 記錄。

標簽: 查詢語句 隔離級別 全局事務

上一篇:焦點觀察:原生GIT版本服務器配置和測試
下一篇:【世界熱聞】復盤逝去的年華,展望全新的未來