SpringCloud Alibaba Seata---處理分布式事務


  前言:不斷學習就是程序員的宿命

一、Seata概述

1、背景

  

  單體應用被拆分成微服務應用,原來的三個模塊被拆分成三個獨立的應用,分別使用不同的數據源,業務操作需要調用三個服務來完成。此時每個服務內部的數據一致性由本地事務來保證,但是全局的數據一致性問題沒法保證。

  Seata是一款開源的分布式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分布式事務服務。

  官網:http://seata.io/zh-cn/

2、一個典型的分布式事務過程(1+3)-分布式事務處理過程的一個ID+三組件模型:

    (1)Transaction ID XID:全局唯一的事務ID

    (2)三組概念:

      ①Transaction Coordinator(TC事務協調器):維護全局和分支事務狀態,驅動全局事務提交或回滾

      ②Transaction Manager(TM事務管理器):控制全局事務的邊界,負責開啟一個全局事務,並最終發起全局提交或全局回滾的決議

      ③Resource Manager(RM資源管理器):控制分支事務,負責分支注冊、狀態匯報,並接受事務協調的指令,驅動分支(本地)事務的提交和回滾

 3、Seata處理過程

 ①TM向TC申請開啟一個全局事務,全局事務創建成功並生成一個全局唯一的XID;

②XID在微服務調用鏈路的上下文傳播

③RM向TC注冊分支事務,將其納入XID對應全局事務的管轄

④TM向TC發起針對XID的全局提交或回滾決議;

⑤TC調度XID下管轄的全部分支事務完成提交或回滾事務

二、Seata分布式交易解決方案

案例需求:下訂單-->扣庫存--->減賬戶余額---->修改訂單狀態

環境准備:3個數據庫(訂單庫、庫存庫、賬戶庫)、3個微服務(訂單、庫存、賬戶服務)

當用戶下單時,會在訂單服務中創建一個訂單,然后通過遠程調用庫存服務來扣減下單商品的庫存。再通過遠程調用賬戶服務來扣減賬戶余額,最后在訂單服務中修改訂單狀態為已完成。該操作跨越三個數據庫,有兩次遠程調用,很明顯會有分布式事務問題。

三、Seata-Server安裝

  下載地址:https://github.com/seata/seata/releases

1、修改file.conf

  主要修改:自定義事務組名稱+事務日志存儲模式為db+數據連接信息(ps:記得備份file.conf)

1.1 service模塊

1.2store模塊

2、數據庫初始化

   新建數據庫seata,執行初始表sql位置:/seata/conf/db_store.sql

 3、修改registry.conf

 4、分別啟動Nacos、seata-server

四、數據庫初始化

①seata_order:存儲訂單數據庫;

②seata_storage:存儲庫存數據;

③seata_account:存儲賬戶信息數據庫;

---seata biz
create database seata_order;
USE seata_order;
CREATE TABLE `t_order`  (
  `int` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) DEFAULT NULL COMMENT '用戶id',
  `product_id` bigint(11) DEFAULT NULL COMMENT '產品id',
  `count` int(11) DEFAULT NULL COMMENT '數量',
  `money` decimal(11, 0) DEFAULT NULL COMMENT '金額',
  `status` int(1) DEFAULT NULL COMMENT '訂單狀態:  0:創建中 1:已完結',
  PRIMARY KEY (`int`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '訂單表' ROW_FORMAT = Dynamic;

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;

create database seata_storage;
USE seata_storage;
DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage`  (
  `int` bigint(11) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(11) DEFAULT NULL COMMENT '產品id',
  `total` int(11) DEFAULT NULL COMMENT '總庫存',
  `used` int(11) DEFAULT NULL COMMENT '已用庫存',
  `residue` int(11) DEFAULT NULL COMMENT '剩余庫存',
  PRIMARY KEY (`int`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '庫存' ROW_FORMAT = Dynamic;
INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100);

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;

CREATE database seata_account;
USE seata_account;
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account`  (
  `id` bigint(11) NOT NULL COMMENT 'id',
  `user_id` bigint(11) DEFAULT NULL COMMENT '用戶id',
  `total` decimal(10, 0) DEFAULT NULL COMMENT '總額度',
  `used` decimal(10, 0) DEFAULT NULL COMMENT '已用余額',
  `residue` decimal(10, 0) DEFAULT NULL COMMENT '剩余可用額度',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '賬戶表' ROW_FORMAT = Dynamic;

INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000);

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;
sql

1、建立對應3個數據庫與對應的回滾日志表

(回滾日志表對應建表語句:/seata/conf/db_undo_log.sql)

 2、初始數據

 五、代碼實現及測試

1、代碼模塊

2001服務為訂單服務驅動業務

2002服務為庫存服務

2003服務為賬戶服務

 代碼地址:

2、分布式事務測試

2.1正常情況測試

數據庫情況:

2.2不使用@GlobalTransactional注解超時異常測試

數據庫情況訂單狀態未支付但用戶已扣錢且庫存已減

2.3使用@GlobalTransactional注解

測試情況:

發現使用@GlobalTransactional注解后,數據庫記錄進行了回滾。實現了分布式事務

六、Seata原理簡介

  官網:http://seata.io/zh-cn/docs/dev/mode/at-mode.html默認為AT模式

(1)一階段加載

  在一階段,Seata會攔截“業務SQL”:

  ①解析SQL語義,找到“業務SQL”要更新的業務數據,在業務數據被更新之前,將其保存成“before image”

  ②執行“業務SQL”更新業務數據,在業務數據更新之后,將其生成“after image”

  ③生成行鎖

以上操作全部在一個數據庫事務內完成,這樣保證了一階段操作的原子性。

(2)二階段提交

二階段如果是順利的話,因為“業務SQL”在一階段已經提交至數據庫,所以Seata框架只需將一階段保存的快照數據和行鎖刪掉,完成數據清理即可。

(3)二階段回滾

  二階段如果回滾的話,Seata就需要回滾一階段已執行的“業務SQL”,還原業務數據

  回滾方式便是用“before image”還原業務數據;但在還原之前首先要校驗臟寫,對比“數據當前業務數據”和“after image”,如果兩份數據完全一致就說明沒有臟寫,可以還原業務數據,如果不一致就說明臟鞋,需要人工處理。

 ps:總結


免責聲明!

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



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