一,什么是事務(本地事務)?
指作為單個邏輯工作單元執行的一系列操作,要么完全地執行,要么完全地不執行。
簡單的說,事務就是並發控制的單位,是用戶定義的一個操作序列。
而一個邏輯工作單元要成為事務,就必須滿足ACID屬性。
A:原子性(Atomicity)
事務中的操作要么都不做,要么就全做。
C:一致性(Consistency)
事務執行的結果必須是從數據庫從一個一致性狀態轉換到另一個一致性狀態。
I:隔離性(Isolation)
一個事務的執行不能被其他事務干擾
D:持久性(Durability)
一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。
注:如果對事務不是很懂可以參考這篇文章:什么是事務?事務的四個特性以及事務的隔離級別 - Kevin.ZhangCG - 博客園 (cnblogs.com)
二, 分布式事務
隨着互聯網的快速發展,軟件系統由原來的單體應用轉變為分布式應用,下圖描述了單體應用向微服務的演變:
分布式系統會把一個應用系統拆分為可獨立部署的多個服務,因此需要服務與服務之間遠程協作才能完成事務操作,這種分布式系統環境下由不同的服務之間通過網絡遠程協作完成事務稱之為分布式事務。
三,分布式事務產生的情景
- 跨JVM進程產生分布式事務
典型的場景就是微服務架構:微服務之間通過遠程調用完成事務操作。比如:訂單微服務和庫存微服務,下單的同時訂單微服務請求庫存微服務減少庫存。
- 跨數據庫實例產生分布式事務
單體系統訪問多個數據庫實例當單體系統需要訪問多個數據庫(實例)時就會產生分布式事務。比如:用戶信息和訂單信息分別在兩個MySQL實例存儲,用戶管理系統刪除用戶信息,需要分別刪除用戶信息及用戶的訂單信息,由於數據分布在不同的數據實例,需要通過不同的數據庫鏈接去操作數據,此時產生分布式事務。
- 多服務訪問同一個數據庫實例
訂單微服務和庫存微服務即使訪問同一個數據庫也會產生分布式事務,原因就是跨JVM進程,兩個微服務持有了不同的數據庫鏈接進行數據庫操作,此時產生分布式事務。
四, 分布式事務解決方案之 2PC
常見分布式事務解決方案:
1.seata 阿里分布式事務框架
2. 消息隊列
3.sage
4,XA
但是他們都有一個共同點:都是兩個階段(2PC)。其實這四種解決方案對應的是分布式事務的四種模式:AT,TCC,Sage,XA
4.1 什么是 2PC
2PC 即兩階段提交協議,是將整個事務流程分為兩個階段,准備階段(Prepare phase)、提交階段(commit phase),2 是指兩個階段,P 是指准備階段,C 是指提交階段。
舉例:張三和李四好久不見,老友約起聚餐,飯店老板要求先買單,才能出票。這時張三和李四分別抱怨近況不如意,囊中羞澀,都不願意請客,這時只能AA。只有張三和李四都付款,老板才能出票安排就餐。但由於張三和李四都是鐵公雞,形成了尷尬的一幕:
准備階段:老板要求張三付款,張三付款。老板要求李四付款,李四付款。
提交階段:老板出票,兩人拿票紛紛落座就餐。
例子中形成了一個事務,若張三或李四其中一人拒絕付款,或錢不夠,店老板都不會給出票,並且會把已收款退回。
整個事務過程由事務管理器和參與者組成,店老板就是事務管理器,張三、李四就是事務參與者,事務管理器負責決策整個分布式事務的提交和回滾,事務參與者負責自己本地事務的提交和回滾。
在計算機中部分關系數據庫如 Oracle、MySQL 支持兩階段提交協議,如下圖:
- 准備階段(Prepare phase):事務管理器給每個參與者發送 Prepare 消息,每個數據庫參與者在本地執行事務,並寫本地的 Undo/Redo 日志,此時事務沒有提交。(Undo 日志是記錄修改前的數據,用於數據庫回滾,Redo 日志是記錄修改后的數據,用於提交事務后寫入數據文件)
- 提交階段(commit phase):如果事務管理器收到了參與者的執行失敗或者超時消息時,直接給每個參與者發送回滾(Rollback)消息;否則,發送提交(Commit)消息;參與者根據事務管理器的指令執行提交或者回滾操作,並釋放事務處理過程中使用的鎖資源。注意:必須在最后階段釋放鎖資源。
下圖展示了2PC的兩個階段,分成功和失敗兩個情況說明:
成功情況
失敗情況
五,Seata 方案(AT模式)
Seata 是由阿里中間件團隊發起的開源項目 Fescar,后更名為 Seata,它是一個是開源的分布式事務框架。
傳統 2PC 的問題在 Seata 中得到了解決,它通過對本地關系數據庫的分支事務的協調來驅動完成全局事務,是工作在應用層的中間件。主要優點是性能較好,且不長時間占用連接資源,它以高效並且對業務 0 侵入的方式解決微服務場景下面臨的分布式事務問題,它目前提供 AT 模式(即 2PC)及 TCC 模式的分布式事務解決方案。
Seata 的設計思想如下:
Seata 的設計目標其一是對業務無侵入,因此從業務無侵入的 2PC 方案着手,在傳統 2PC的基礎上演進,並解決 2PC 方案面臨的問題。
Seata 把一個分布式事務理解成一個包含了若干分支事務的全局事務。全局事務的職責是協調其下管轄的分支事務達成一致,要么一起成功提交,要么一起失敗回滾。此外,通常分支事務本身就是一個關系數據庫的本地事務,下圖是全局事務與分支事務的關系圖:
與傳統 2PC 的模型類似,Seata 定義了 3 個組件來協議分布式事務的處理過程:
- Transaction Coordinator(TC):事務協調器,它是獨立的中間件,需要獨立部署運行,它維護全局事務的運行狀態,接收 TM 指令發起全局事務的提交與回滾,負責與 RM 通信協調各各分支事務的提交或回滾。
- Transaction Manager(TM): 事務管理器,TM 需要嵌入應用程序中工作,它負責開啟一個全局事務,並最終向 TC 發起全局提交或全局回滾的指令。
- Resource Manager(RM):控制分支事務,負責分支注冊、狀態匯報,並接收事務協調器 TC 的指令,驅動分支(本地)事務的提交和回滾。
舉例Seata的分布式事務過程:
簡單業務邏輯圖:
第一階段:
第二階段:提交,回滾
提交:
回滾:
Seata實現2PC與傳統2PC的差別
架構層次方面:傳統 2PC 方案的 RM 實際上是在數據庫層,RM 本質上就是數據庫自身,通過 XA 協議實現,而 Seata 的 RM 是以 jar 包的形式作為中間件層部署在應用程序這一側的。
兩階段提交方面:傳統 2PC無論第二階段的決議是 commit 還是 rollback ,事務性資源的鎖都要保持到 Phase2 完成才釋放。而 Seata 的做法是在 Phase1 就將本地事務提交,這樣就可以省去 Phase2 持鎖的時間,整體提高效率。
六,TCC模式
缺點:代碼侵入性較強,需要自己手寫處理代碼
優點:沒有鎖的概念,效率高一點,由於是自己手寫,定制化程度比較高。
Tcc 框架:
概念圖:
簡單業務圖:
七,Seata 服務端的自帶表說明
UNDO_LOG(回滾日志)表:記錄參與者的xid和相關的操作,用於對事務的回滾
`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;
在 db 模式下,我們需要針對全局事務的會話信息創建以下 3 張數據庫表。
全局事務表,對應的表為:global_table
分支事務表,對應的表為:branch_table
全局鎖表,對應的表為:lock_table
---數據庫server 要的表 -- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data CREATE TABLE IF NOT EXISTS "global_table" ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_status_gmt_modified` (`status` , `gmt_modified`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store BranchSession data CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store lock data CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking', `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_status` (`status`), KEY `idx_branch_id` (`branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `distributed_lock` ( `lock_key` CHAR(20) NOT NULL, `lock_value` VARCHAR(20) NOT NULL, `expire` BIGINT, primary key (`lock_key`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
注:看到這里了,感覺有用,就關注一下吧,或者贊助一下吧!你們的贊助就是我創作的最大動力。