Seata簡單介紹及其原理(一)


Seata 是什么?

Seata 是一款開源的分布式事務解決方案,致力於提供高性能和簡單易用的分布式事務服務。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 需要重試等待 全局鎖 。

Write-Isolation: Commit

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

Write-Isolation: Rollback

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

此時,如果 tx2 仍在等待該數據的 全局鎖,同時持有本地鎖,則 tx1 的分支回滾會失敗。分支的回滾會一直重試,直到 tx2 的 全局鎖 等鎖超時,放棄 全局鎖 並回滾本地事務釋放本地鎖,tx1 的分支回滾最終成功。

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

讀隔離

在數據庫本地事務隔離級別 讀已提交(Read Committed) 或以上的基礎上,Seata(AT 模式)的默認全局隔離級別是 讀未提交(Read Uncommitted) 。

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

Read Isolation: 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 行為

Overview of a global transaction

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

AT 模式(參考鏈接 TBD)基於 支持本地 ACID 事務 的 關系型數據庫:

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

相應的,TCC 模式,不依賴於底層數據資源的事務支持:

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

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

Saga 模式

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

Saga模式示意圖

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

適用場景:

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

優勢:

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

缺點:

  • 不保證隔離性


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM