主頁 > 軟體設計 > 分布式事務管理:Seata

分布式事務管理:Seata

2021-02-26 11:37:41 軟體設計

目錄

  • 第一章 Seata的介紹
    • 1.1、分布式事務
    • 1.2、Seata是什么
    • 1.3、Seata的術語
  • 第二章 Seata單機版部署:TC Server
    • 2.1、下載Seata
    • 2.2、解壓Seata
    • 2.3、運行Seata
  • 第三章 Seata的常用模式:AT
    • 3.1、使用前提
    • 3.2、整體機制
    • 3.3、讀寫隔離
      • 3.3.1、寫隔離
      • 3.3.2、讀隔離
    • 3.4、作業機制
      • 3.4.1、一階段
      • 3.4.2、二階段-回滾
      • 3.4.3、二階段-提交
    • 3.5、附錄章節
  • 第四章 單體版多資料源事務管理:AT
    • 4.1、匯入資料
      • 4.1.1、創建賬戶資料庫
      • 4.1.2、創建庫存資料庫
      • 4.1.3、創建訂單資料庫
    • 4.2、匯入工程
    • 4.3、正常測驗
    • 4.4、例外測驗
    • 4.5、添加新表
    • 4.6、添加依賴
    • 4.7、添加配置
    • 4.8、添加注解
    • 4.9、例外測驗
  • 第五章 分布式單資料源事務管理:AT
    • 5.1、匯入資料
      • 5.1.1、重置賬戶資料庫
      • 5.1.2、重置庫存資料庫
      • 5.1.3、重置訂單資料庫
    • 5.2、匯入工程
    • 5.3、正常測驗
    • 5.4、例外測驗
    • 5.5、添加新表
    • 5.6、添加依賴
    • 5.7、添加配置
    • 5.8、添加注解
    • 5.9、例外測驗
  • 第六章 Seata集群版部署:TC Cluster
    • 6.1、關閉單機版
    • 6.2、創建資料庫
    • 6.3、修改存盤模式
    • 6.4、修改注冊中心
    • 6.5、啟動兩個實體
    • 6.6、查看注冊中心
  • 第七章 Seata的常用模式:TCC
  • 第八章 單體版多資料源事務管理:TCC
    • 8.1、匯入資料
      • 8.1.1、重置賬戶資料庫
      • 8.1.2、重置庫存資料庫
      • 8.1.3、重置訂單資料庫
    • 8.2、匯入工程
    • 8.3、正常測驗
    • 8.4、例外測驗
    • 8.5、添加新表
    • 8.6、添加依賴
    • 8.7、添加配置
    • 8.8、添加注解
    • 8.9、賬戶服務
    • 8.10、例外測驗
  • 第九章 分布式單資料源事務管理:TCC
    • 9.1、匯入資料
      • 9.1.1、重置賬戶資料庫
      • 9.1.2、重置庫存資料庫
      • 9.1.3、重置訂單資料庫
    • 9.2、匯入工程
    • 9.3、正常測驗
    • 9.4、例外測驗
    • 9.5、添加新表
    • 9.6、添加依賴
    • 9.7、添加配置
    • 9.8、添加注解
    • 9.9、賬戶服務
    • 9.10、例外測驗


配套資料,免費下載
鏈接:https://pan.baidu.com/s/1-eRFozbFIShqbqNRFD9KDw
提取碼:rt3w
復制這段內容后打開百度網盤手機App,操作更方便哦

第一章 Seata的介紹

1.1、分布式事務

事務是資料庫的概念,資料庫事務(ACID:原子性、一致性、隔離性和持久性),

分布式事務的產生,是由于資料庫的拆分和分布式架構(微服務)帶來的,在常規情況下,我們在一個行程中操作一個資料庫,這屬于本地事務,如果在一個行程中操作多個資料庫,或者在多個行程中操作一個或多個資料庫,就產生了分布式事務;

(1)資料庫分庫分表就產生了分布式事務;

(2)專案拆分服務化也產生了分布式事務;

1.2、Seata是什么

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

Seata為用戶提供了AT、TCC、SAGA和XA事務模式,為用戶打造一站式的分布式解決方案,

四種事務模式中,目前使用的流行度情況是:AT > TCC > Saga、XA,我們可以參看Seata各公司使用串列:https://github.com/seata/seata/issues/1246

因此,我們教學的重點和學習的重點將會放到AT模式和TCC模式的講解上,Seata默認就是AT模式,簡單的一句話來說這兩種模式的區別(后邊會深入講解):

  • AT模式:可以對資料源是mysql、oracle等關系型資料庫的情況進行失敗回滾,
  • TCC模式:不僅可以對資料源是mysql、oracle等關系型資料庫的情況進行失敗回滾,還可以對訊息中間件、非關系型資料庫如:redis、mongodb等資料庫進行失敗回滾,

從上邊直觀的來看,感覺TCC模式更厲害一點,實際上,Seata默認的AT模式,事務失敗回滾并不用程式員自己來做,而是由Seata框架本身來完成的,而TCC模式的事務失敗回滾等操作,全部需要手動實作,因此,AT模式在實際生產環境中用的更多一點,也更方便一點,除了特定場景下的特殊需要,AT模式基本都能滿足,

當然了,這里提前說一下,我們接下來會學習Seata的單機版部署和高可用集群版部署,而正好,我們要學習AT和TCC兩種模式,我們在學習AT模式的時候,使用Seata單機版環境、而在學習TCC模式的時候,使用Seata的高可用集群版的環境,一定注意,這么安排存粹是為了教學方便,實際上AT也能用高可用集群版環境,

1.3、Seata的術語

官方檔案:http://seata.io/zh-cn/

在Seata的架構中,一共有三個角色:

TC (Transaction Coordinator) - 事務協調者

維護全域和分支事務的狀態,驅動全域事務提交或回滾,

TM (Transaction Manager) - 事務管理器

定義全域事務的范圍:開始全域事務、提交或回滾全域事務,

RM (Resource Manager) - 資源管理器

管理分支事務處理的資源,與TC交談以注冊分支事務和報告分支事務的狀態,并驅動分支事務提交或回滾,

其中TC為單獨部署的 Server 服務端,TM和RM為嵌入到應用中的 Client 客戶端,除了以上三種角色外,還有一個全域事務id:Transaction ID XID

在Seata中,一個分布式事務的生命周期如下:

  1. TM 向 TC 申請開啟一個全域事務,全域事務創建成功并生成一個全域唯一的 XID;

  2. XID 在微服務呼叫鏈路的背景關系中傳播;

  3. RM 向 TC 注冊分支事務,將其納入 XID 對應全域事務的管轄;

  4. TM 向 TC 發起針對 XID 的全域提交或回滾決議;

  5. TC 調度 XID 下管轄的全部分支事務完成提交或回滾請求,

第二章 Seata單機版部署:TC Server

2.1、下載Seata

我們先部署單機環境的 Seata TC Server,用于學習或測驗,在生產環境中要部署集群環境;

因為TC需要進行全域事務和分支事務的記錄,所以需要對應的存盤,目前,TC有三種存盤模式( store.mode ):

  • file模式:適合單機模式,全域事務會話資訊在記憶體中讀寫,并持久化本地檔案 root.data,性能較高;( 默認
  • db模式:適合集群模式,全域事務會話資訊通過 db 共享,相對性能差點;
  • redis模式:解決db存盤的性能問題;

我們先采用file模式,最終我們部署單機TC Server如下圖所示:

截止到2021年2月24日,官方最新發布的版本為1.4.1,但是我們不能下載最新的這個版本來進行學習,因為在引入spring-cloud-starter-alibaba-seata依賴以后,內置自帶的版本為1.3.0,因此,我們需要保持版本一致,所以,我們需要下載1.3.0

下載地址:https://github.com/seata/seata/releases/download/v1.3.0/seata-server-1.3.0.zip

2.2、解壓Seata

2.3、運行Seata

雙擊運行:bin\seata-server.bat

第三章 Seata的常用模式:AT

3.1、使用前提

  • 基于支持本地 ACID 事務的關系型資料庫,例如:mysql、oracle
  • Java 應用,通過 JDBC 訪問資料庫,

3.2、整體機制

兩階段提交協議的演變:

  • 一階段:業務資料和回滾日志記錄(一張單獨的資料表)在同一個本地事務中提交,釋放本地鎖和連接資源,
  • 二階段:
    • 提交異步化,非常快速地完成,
    • 回滾通過一階段的回滾日志進行反向補償,

反向補償:簡單說就是給某一個欄位加了10,反向補償就減去10,這樣資料保持不變,新增一條記錄,反向補償就洗掉以前新增的那條記錄,

3.3、讀寫隔離

3.3.1、寫隔離

  • 一階段本地事務提交前,需要確保先拿到 全域鎖
  • 拿不到 全域鎖 ,不能提交本地事務,
  • 全域鎖 的嘗試被限制在一定范圍內,超出范圍將放棄,并回滾本地事務,釋放本地鎖,

以一個示例來說明:

兩個全域事務 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 持有的,所以不會發生 臟寫 的問題,

3.3.2、讀隔離

如果不考慮事務的隔離性,可能會引發讀安全性問題:

  • 臟讀:一個事務讀到了另一個事務未提交的資料
  • 不可重復讀:一個事務讀到了另一個事務已經提交的 update 的資料,導致多次查詢結果不一致
  • 幻讀 / 虛讀:一個事務讀到了另一個事務已經提交的 insert 的資料,導致多次查詢結果不一致
隔離級別中文說明說明
READ UNCOMMITTED讀未提交不能解決以上所有讀問題,效率最高,安全性最低,一般不用
READ COMMITTED讀已提交避免臟讀,不可重復讀和幻讀有可能發生,Oracle默認的隔離級別
REPEATABLE READ可重復讀避免臟讀、不可重復讀,幻讀有可能發生,MySQL默認的隔離級別
SERIALIZABLE串行化可以解決以上所有讀問題,效率最差,安全性最高,一般不用

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

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

SELECT FOR UPDATE 陳述句的執行會申請 全域鎖 ,如果 全域鎖 被其他事務持有,則釋放本地鎖(回滾 SELECT FOR UPDATE 陳述句的本地執行)并重試,這個程序中,查詢是被 block 住的,直到 全域鎖 拿到,即讀取的相關資料是 已提交 的,才回傳,

出于總體性能上的考慮,Seata 目前的方案并沒有對所有 SELECT 陳述句都進行代理,僅針對 FOR UPDATE 的 SELECT 陳述句,

3.4、作業機制

以一個示例來說明整個 AT 分支的作業程序,

業務表:product

FieldTypeKey
idbigint(20)PRI
namevarchar(100)
sincevarchar(100)

AT 分支事務的業務邏輯:

update product set name = 'GTS' where name = 'TXC';

3.4.1、一階段

程序:

  • 1、決議 SQL:得到 SQL 的型別(UPDATE),表(product),條件(where name = ‘TXC’)等相關的資訊,
  • 2、查詢前鏡像:根據決議得到的條件資訊,生成查詢陳述句,定位資料,
select id, name, since from product where name = 'TXC';

得到前鏡像:

idnamesince
1TXC2014
  • 3、執行業務 SQL:更新這條記錄的 name 為 ‘GTS’,
  • 4、查詢后鏡像:根據前鏡像的結果,通過 主鍵 定位資料,
select id, name, since from product where id = 1`;

得到后鏡像:

idnamesince
1GTS2014
  • 5、插入回滾日志:把前后鏡像資料以及業務 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"
}
  • 6、提交前,向 TC 注冊分支:申請 product 表中,主鍵值等于 1 的記錄的 全域鎖
  • 7、本地事務提交:業務資料的更新和前面步驟中生成的 UNDO LOG 一并提交,
  • 8、將本地事務提交的結果上報給 TC,

3.4.2、二階段-回滾

  • 1、收到 TC 的分支回滾請求,開啟一個本地事務,執行如下操作,
  • 2、通過 XID 和 Branch ID 查找到相應的 UNDO LOG 記錄,
  • 3、資料校驗:拿 UNDO LOG 中的后鏡與當前資料進行比較,如果有不同,說明資料被當前全域事務之外的動作做了修改,這種情況,需要根據配置策略來做處理,詳細的說明在另外的檔案中介紹,
  • 4、根據 UNDO LOG 中的前鏡像和業務 SQL 的相關資訊生成并執行回滾的陳述句:
update product set name = 'TXC' where id = 1;
  • 5、提交本地事務,并把本地事務的執行結果(即分支事務回滾的結果)上報給 TC,

3.4.3、二階段-提交

  • 1、收到 TC 的分支提交請求,把請求放入一個異步任務的佇列中,馬上回傳提交成功的結果給 TC,
  • 2、異步任務階段的分支提交請求將異步和批量地洗掉相應 UNDO LOG 記錄,

3.5、附錄章節

回滾日志表

UNDO_LOG Table:不同資料庫在型別上會略有差別,

以 MySQL 為例:

FieldType
branch_idbigint PK
xidvarchar(100)
contextvarchar(128)
rollback_infolongblob
log_statustinyint
log_createddatetime
log_modifieddatetime
-- 注意此處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;

第四章 單體版多資料源事務管理:AT

4.1、匯入資料

4.1.1、創建賬戶資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE IF EXISTS `seata_account`;
CREATE DATABASE `seata_account`;

USE `seata_account`;

DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT 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 '0' COMMENT '剩余額度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='賬戶表';

insert  into `t_account`(`id`,`user_id`,`total`,`used`,`residue`) values (1,1,'1000','0','1000');

SELECT * FROM `seata_account`.`t_account` LIMIT 0, 1000; 

4.1.2、創建庫存資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE IF EXISTS `seata_storage`;
CREATE DATABASE `seata_storage`;

USE `seata_storage`;

DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `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 (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='庫存表';

insert  into `t_storage`(`id`,`product_id`,`total`,`used`,`residue`) values (1,1,100,0,100);

SELECT * FROM `seata_storage`.`t_storage` LIMIT 0, 1000; 

4.1.3、創建訂單資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE `seata_order`;
CREATE DATABASE `seata_order`;

USE `seata_order`;

DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `user_id` bigint(11) 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 '狀態',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單表';

SELECT * FROM `seata_order`.`t_order` LIMIT 0, 1000; 

4.2、匯入工程

在桌面重新創建一個檔案夾,名字無所謂,拷貝配套資料中的單體版\single-app-seata-at-study到此檔案夾,然后使用idea打開即可,

single-app-seata-at-study已經實作了基本的代碼流程,非常簡單,相信你一定能看懂:

single-app-seata-at-study是一個純粹的Spring Boot單體應用,連接著三個資料源:

唯一需要你注意的是,打開application.yaml,查看連接資料源的賬戶密碼是否正確,如下:

4.3、正常測驗

請啟動當前工程,然后輸入下單地址測驗:http://localhost:9000/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

4.4、例外測驗

修改com.caochenlei.service.impl.AccountServiceImpl代碼添加例外,代碼如下:

@Override
public void decrease(Long userId, BigDecimal money) {
    int i = 1 / 0; //模擬例外出錯
    accountMapper.decrease(userId, money);
}

請重啟當前工程,然后輸入下單地址測驗:http://localhost:9000/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

我們發現訂單下單失敗,訂單資料庫中多了一條訂單記錄,實際上這條記錄不應該有,而且賬戶對應的余額和庫存并沒有減少,這個問題是十分可怕的,因為沒有事務的支持,不能做到要不全部執行,要不全部失敗,

4.5、添加新表

賬戶資料庫添加回滾日志表,sql陳述句如下:

USE `seata_account`;

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;

庫存資料庫添加回滾日志表,sql陳述句如下:

USE `seata_storage`;

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;

訂單資料庫添加回滾日志表,sql陳述句如下:

USE `seata_order`;

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;

最終完成以后的效果如下圖:

4.6、添加依賴

pom.xml中新增依賴:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>

4.7、添加配置

application.yaml中新增配置:

server:
  port: 9000

management:
  endpoints:
    web:
      exposure:
        include: '*'

spring:
  application:
    name: single-app-seata-at-study
  datasource:
    dynamic:
      primary: ordere-ds
      datasource:
        #賬戶資料源(account-ds自定義的)
        account-ds:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useUnicode=true&useSSL=false
          username: root #資料庫用戶,請根據實際填寫
          password: 123456 #資料庫密碼,請根據實際填寫
        #庫存資料源(storage-ds自定義的)
        storage-ds:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_storage?characterEncoding=utf8&useUnicode=true&useSSL=false
          username: root #資料庫用戶,請根據實際填寫
          password: 123456 #資料庫密碼,請根據實際填寫
        #訂單資料源(ordere-ds自定義的)
        ordere-ds:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useUnicode=true&useSSL=false
          username: root #資料庫用戶,請根據實際填寫
          password: 123456 #資料庫密碼,請根據實際填寫
      seata: true #開啟seata支持
      seata-mode: at #選擇seata模式:at、xa

#seata配置
seata:
  application-id: myapp #應用的編號
  tx-service-group: myapp-group #事務組名稱
  service:
    vgroup-mapping:
      myapp-group: guangzhou #當前事務組使用 guangzhou 機房的seata服務
    group-list:
      guangzhou: 127.0.0.1:8091 #主機房,部署seata tc server/seata tc cluster
      shanghai: 127.0.0.1:8091 #備用機房,部署seata tc server/seata tc cluster
  config:
    type: file #組態檔使用檔案存盤方式
  registry:
    type: file #注冊中心使用檔案存盤方式

事務分組與高可用,最佳實踐1:TC的異地多機房容災如下,更多最佳實踐請訪問:http://seata.io/zh-cn/docs/user/txgroup/transaction-group-and-ha.html

  • 假定TC集群部署在兩個機房:guangzhou機房(主)和shanghai機房(備)各兩個實體
  • 一整套微服務架構專案:projectA
  • projectA內有微服務:serviceA、serviceB、serviceC 和 serviceD

其中,projectA所有微服務的事務分組tx-transaction-group設定為:projectA,projectA正常情況下使用guangzhou的TC集群(主)

那么正常情況下,client端的配置如下所示:

seata.tx-service-group=projectA
seata.service.vgroup-mapping.projectA=Guangzhou

假如此時guangzhou集群分組整個down掉,或者因為網路原因projectA暫時無法與Guangzhou機房通訊,那么我們將配置中心中的Guangzhou集群分組改為Shanghai,如下:

seata.service.vgroup-mapping.projectA=Shanghai

并推送到各個微服務,便完成了對整個projectA專案的TC集群動態切換,

4.8、添加注解

修改com.caochenlei.service.impl.OrderServiceImpl開啟全域事務管理,代碼如下:

@DS("order-ds")
@Slf4j
@Service
@GlobalTransactional //開啟seata全域事務注解,支持放在類上、方法上
public class OrderServiceImpl implements OrderService {
    ...
    ...
}

4.9、例外測驗

請重啟當前工程,然后輸入下單地址測驗:http://localhost:9000/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

我們發現雖然下單失敗了,但是并沒有往訂單資料庫插入訂單資訊,賬戶余額和庫存也沒有減少,這符合我們的業務邏輯,多資料源的事務管理完美解決,

注意:測驗完畢,請關閉當前工程,防止影響其他專案,

第五章 分布式單資料源事務管理:AT

5.1、匯入資料

5.1.1、重置賬戶資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE IF EXISTS `seata_account`;
CREATE DATABASE `seata_account`;

USE `seata_account`;

DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT 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 '0' COMMENT '剩余額度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='賬戶表';

insert  into `t_account`(`id`,`user_id`,`total`,`used`,`residue`) values (1,1,'1000','0','1000');

SELECT * FROM `seata_account`.`t_account` LIMIT 0, 1000; 

5.1.2、重置庫存資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE IF EXISTS `seata_storage`;
CREATE DATABASE `seata_storage`;

USE `seata_storage`;

DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `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 (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='庫存表';

insert  into `t_storage`(`id`,`product_id`,`total`,`used`,`residue`) values (1,1,100,0,100);

SELECT * FROM `seata_storage`.`t_storage` LIMIT 0, 1000; 

5.1.3、重置訂單資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE `seata_order`;
CREATE DATABASE `seata_order`;

USE `seata_order`;

DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `user_id` bigint(11) 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 '狀態',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單表';

SELECT * FROM `seata_order`.`t_order` LIMIT 0, 1000; 

5.2、匯入工程

在桌面重新創建一個檔案夾,名字無所謂,拷貝配套資料中的分布式\distributed-seata-at-study到此檔案夾,然后使用idea打開即可,

distributed-seata-at-study已經實作了基本的代碼流程,非常簡單,相信你一定能看懂:

distributed-seata-at-study是一個典型的微服務應用,一共有三個服務,每個服務都實作了基本的代碼邏輯,并且都對應一個資料源,架構如下圖:

唯一需要你注意的是,打開application.yaml,查看連接資料源的賬戶密碼是否正確,并且我們需要你啟動nacos服務注冊中心,如下:

service-account

service-storage

service-order

5.3、正常測驗

確保啟動nacos注冊中心,

請啟動當前工程,然后輸入下單地址測驗:http://localhost:9003/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

5.4、例外測驗

打開工程service-account修改com.caochenlei.service.impl.AccountServiceImpl代碼添加例外,代碼如下:

@Override
public void decrease(Long userId, BigDecimal money) {
    int i = 1 / 0; //模擬例外出錯
    accountMapper.decrease(userId, money);
}

請重啟當前工程,然后輸入下單地址測驗:http://localhost:9003/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

我們發現訂單下單失敗,訂單資料庫中多了一條訂單記錄,實際上這條記錄不應該有,而且賬戶對應的余額和庫存并沒有減少,這個問題是十分可怕的,因為沒有事務的支持,不能做到要不全部執行,要不全部失敗,

5.5、添加新表

賬戶資料庫添加回滾日志表,sql陳述句如下:

USE `seata_account`;

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;

庫存資料庫添加回滾日志表,sql陳述句如下:

USE `seata_storage`;

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;

訂單資料庫添加回滾日志表,sql陳述句如下:

USE `seata_order`;

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;

最終完成以后的效果如下圖:

5.6、添加依賴

service-orderpom.xml中新增依賴:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

5.7、添加配置

service-orderapplication.yaml中新增配置:

server:
  port: 9003

management:
  endpoints:
    web:
      exposure:
        include: '*'

spring:
  application:
    name: service-order
  cloud:
    nacos:
      discovery:
        server-addr: http://localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useUnicode=true&useSSL=false
    username: root #資料庫用戶,請根據實際填寫
    password: 123456 #資料庫密碼,請根據實際填寫

#防止feign呼叫超時對測驗結果有影響
feign:
  client:
    config:
      default:
        connect-timeout: 5000
        read-timeout: 5000

#seata配置
seata:
  application-id: myapp #應用的編號
  tx-service-group: myapp-group #事務組名稱
  service:
    vgroup-mapping:
      myapp-group: guangzhou #當前事務組使用 guangzhou 機房的seata服務
    group-list:
      guangzhou: 127.0.0.1:8091 #主機房,部署seata tc server/seata tc cluster
      shanghai: 127.0.0.1:8091 #備用機房,部署seata tc server/seata tc cluster
  config:
    type: file #組態檔使用檔案存盤方式
  registry:
    type: file #注冊中心使用檔案存盤方式

5.8、添加注解

打開service-order修改com.caochenlei.service.impl.OrderServiceImpl開啟全域事務管理,代碼如下:

@Slf4j
@Service
@GlobalTransactional //開啟seata全域事務注解,支持放在類上、方法上
public class OrderServiceImpl implements OrderService {
    ...
    ...
}

5.9、例外測驗

請重啟當前工程,然后輸入下單地址測驗:http://localhost:9003/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

我們發現雖然下單失敗了,但是并沒有往訂單資料庫插入訂單資訊,賬戶余額和庫存也沒有減少,這符合我們的業務邏輯,分布式下的事務管理完美解決,

注意:測驗完畢,請關閉當前工程,防止影響其他專案,

第六章 Seata集群版部署:TC Cluster

6.1、關閉單機版

關閉單機版的命令列視窗,

6.2、創建資料庫

首先初始化資料庫:CREATE DATABASE seata; USE seata;

獲取運行腳本地址:https://github.com/seata/seata/tree/develop/script/server/db

-- -------------------------------- 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_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- 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 = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

6.3、修改存盤模式

找到seata\conf\file.conf,把存盤模式修改為db并修改資料源連接,修改后保存,如下:

## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "db"

  ## file store property
  file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
  }

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata?characterEncoding=utf8&useUnicode=true&useSSL=false"
    user = "root"
    password = "123456"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
    host = "127.0.0.1"
    port = "6379"
    password = ""
    database = "0"
    minConn = 1
    maxConn = 10
    queryLimit = 100
  }
}

6.4、修改注冊中心

找到seata\conf\registry.conf,把注冊中心修改為nacos并修改登錄配置,修改后保存,如下:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = "public"
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}
...
...
...
...

6.5、啟動兩個實體

啟動第一個實體:C:\DevTools\seata\bin>seata-server.bat -p 18901 -n 1

啟動第二個實體:C:\DevTools\seata\bin>seata-server.bat -p 28901 -n 2

  • -p:Seata TC Server 監聽的埠;
  • -n:Server node,在多個 TC Server 時,需區分各自節點,用于生成不同區間的 transactionId 事務編號,以免沖突;

6.6、查看注冊中心

打開注冊中心:http://localhost:8848/nacos/,登錄賬戶:nacos,登錄密碼:nacos

第七章 Seata的常用模式:TCC

回顧總覽中的描述:一個分布式的全域事務,整體是 兩階段提交 的模型,全域事務是由若干分支事務組成的,分支事務要滿足 兩階段提交 的模型要求,即需要每個分支事務都具備自己的:

  • 一階段 prepare 行為
  • 二階段 commit 或 rollback 行為

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

AT 模式(參考鏈接 TBD)基于 支持本地 ACID 事務關系型資料庫

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

相應的,TCC 模式,不依賴于底層資料資源的事務支持:

  • 一階段 prepare 行為:呼叫 自定義 的 prepare 邏輯,
  • 二階段 commit 行為:呼叫 自定義 的 commit 邏輯,
  • 二階段 rollback 行為:呼叫 自定義 的 rollback 邏輯,

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

我們其實可以理解,TCC模式所有階段的代碼都是自己實作的,所以它能夠更加靈活的回滾各種關系型資料庫、非關系型資料庫、訊息中間件等,

第八章 單體版多資料源事務管理:TCC

8.1、匯入資料

8.1.1、重置賬戶資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE IF EXISTS `seata_account`;
CREATE DATABASE `seata_account`;

USE `seata_account`;

DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT 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 '0' COMMENT '剩余額度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='賬戶表';

insert  into `t_account`(`id`,`user_id`,`total`,`used`,`residue`) values (1,1,'1000','0','1000');

SELECT * FROM `seata_account`.`t_account` LIMIT 0, 1000; 

8.1.2、重置庫存資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE IF EXISTS `seata_storage`;
CREATE DATABASE `seata_storage`;

USE `seata_storage`;

DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `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 (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='庫存表';

insert  into `t_storage`(`id`,`product_id`,`total`,`used`,`residue`) values (1,1,100,0,100);

SELECT * FROM `seata_storage`.`t_storage` LIMIT 0, 1000; 

8.1.3、重置訂單資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE `seata_order`;
CREATE DATABASE `seata_order`;

USE `seata_order`;

DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `user_id` bigint(11) 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 '狀態',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單表';

SELECT * FROM `seata_order`.`t_order` LIMIT 0, 1000; 

8.2、匯入工程

在桌面重新創建一個檔案夾,名字無所謂,拷貝配套資料中的單體版\single-app-seata-tcc-study到此檔案夾,然后使用idea打開即可,

single-app-seata-tcc-study已經實作了基本的代碼流程,非常簡單,相信你一定能看懂:

single-app-seata-tcc-study是一個純粹的Spring Boot單體應用,連接著三個資料源:

唯一需要你注意的是,打開application.yaml,查看連接資料源的賬戶密碼是否正確,如下:

8.3、正常測驗

請啟動當前工程,然后輸入下單地址測驗:http://localhost:9000/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

8.4、例外測驗

修改com.caochenlei.service.impl.OrderServiceImpl代碼添加例外,代碼如下:

@DS("order-ds")
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private AccountService accountService;
    @Resource
    private StorageService storageService;

    @Override
    public void create(Order order) {
        // 1、新建訂單
        log.info("----->新建訂單開始");
        orderMapper.create(order);
        log.info("----->新建訂單結束");

        // 2、扣減余額
        log.info("----->扣減余額開始");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("----->扣減余額結束");
        
        int i = 1 / 0; //模擬例外出錯

        // 3、扣減庫存
        log.info("----->扣減庫存開始");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("----->扣減庫存結束");
    }
}

請重啟當前工程,然后輸入下單地址測驗:http://localhost:9000/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

我們發現訂單下單失敗,訂單資料庫中多了一條訂單記錄,實際上這條記錄不應該有,同時,賬戶的余額也減少了,但是,庫存并沒有減少,這個問題是十分可怕的,因為沒有事務的支持,不能做到要不全部執行,要不全部失敗,

8.5、添加新表

賬戶資料庫添加回滾日志表,sql陳述句如下:

USE `seata_account`;

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;

庫存資料庫添加回滾日志表,sql陳述句如下:

USE `seata_storage`;

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;

訂單資料庫添加回滾日志表,sql陳述句如下:

USE `seata_order`;

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;

最終完成以后的效果如下圖:

8.6、添加依賴

pom.xml中新增依賴:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.3.2</version>
</dependency>

8.7、添加配置

application.yaml中新增配置:

server:
  port: 9000

management:
  endpoints:
    web:
      exposure:
        include: '*'

spring:
  application:
    name: single-app-seata-tcc-study
  datasource:
    dynamic:
      primary: ordere-ds
      datasource:
        #賬戶資料源(account-ds自定義的)
        account-ds:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useUnicode=true&useSSL=false
          username: root #資料庫用戶,請根據實際填寫
          password: 123456 #資料庫密碼,請根據實際填寫
        #庫存資料源(storage-ds自定義的)
        storage-ds:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_storage?characterEncoding=utf8&useUnicode=true&useSSL=false
          username: root #資料庫用戶,請根據實際填寫
          password: 123456 #資料庫密碼,請根據實際填寫
        #訂單資料源(ordere-ds自定義的)
        ordere-ds:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useUnicode=true&useSSL=false
          username: root #資料庫用戶,請根據實際填寫
          password: 123456 #資料庫密碼,請根據實際填寫
      seata: true #開啟seata支持
      seata-mode: at #選擇seata模式:at、xa,不支持tcc,我們需要手動編碼實作tcc第二階段

#seata配置
seata:
  application-id: myapp #應用的編號
  tx-service-group: myapp-group #事務組名稱
  service:
    vgroup-mapping:
      myapp-group: default #當前事務組使用 cluster: default
  config:
    type: file #組態檔使用檔案存盤方式
  registry:
    type: nacos #注冊中心使用nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: public
      cluster: default
      username: nacos
      password: nacos

8.8、添加注解

修改com.caochenlei.service.impl.OrderServiceImpl開啟全域事務管理,代碼如下:

@DS("order-ds")
@Slf4j
@Service
@GlobalTransactional //開啟seata全域事務注解,支持放在類上、方法上
public class OrderServiceImpl implements OrderService {
    ...
    ...
}

8.9、賬戶服務

修改介面:com.caochenlei.service.AccountService

@LocalTCC//此注解標識TCC為本地模式,即該事務是本地呼叫
public interface AccountService {
    //第一階段:嘗試扣減余額
    @TwoPhaseBusinessAction(name = "decreaseMoney", commitMethod = "commitDecreaseMoney", rollbackMethod = "rollbackDecreaseMoney")
    void decrease(@BusinessActionContextParameter(paramName = "userId") Long userId,
                  @BusinessActionContextParameter(paramName = "money") BigDecimal money);

    //第二階段:提交處理方法
    boolean commitDecreaseMoney(BusinessActionContext context);

    //第二階段:回滾處理方法
    boolean rollbackDecreaseMoney(BusinessActionContext context);
}

修改實作:com.caochenlei.service.impl.AccountServiceImpl

@DS("account-ds")
@Service
public class AccountServiceImpl implements AccountService {
    @Resource
    private AccountMapper accountMapper;

    @Override
    public void decrease(Long userId, BigDecimal money) {
        accountMapper.decrease(userId, money);
    }

    @Override
    public boolean commitDecreaseMoney(BusinessActionContext context) {
        return true;//可以直接回傳true,即空確認
    }

    @Override
    public boolean rollbackDecreaseMoney(BusinessActionContext context) {
        //TODO 這里可以實作中間件、非關系型資料庫的回滾操作

        //通過業務動作背景關系獲取指定引數的引數值
        String userId = context.getActionContext("userId").toString();
        String money = context.getActionContext("money").toString();

        //手動進行資料庫回滾,把減去的余額加回去
        accountMapper.increase(new Long(userId), new BigDecimal(money));

        //我們手動輸出一句話,代表回滾使用我們的
        System.out.println("資料回滾了,這可真的好");

        return true;
    }
}

修改映射:com.caochenlei.mapper.AccountMapper

@Mapper
public interface AccountMapper {
    //扣減余額
    @Update("update t_account set used=used+#{money},residue=residue-#{money} where user_id=#{userId};")
    int decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);

    //加回余額
    @Update("update t_account set used=used-#{money},residue=residue+#{money} where user_id=#{userId};")
    int increase(@Param("userId") Long userId, @Param("money") BigDecimal money);
}

8.10、例外測驗

請重啟當前工程,然后輸入下單地址測驗:http://localhost:9000/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

我們發現雖然下單失敗了,但是并沒有往訂單資料庫插入訂單資訊,賬戶余額和庫存也沒有減少,這符合我們的業務邏輯,可以通過這種方式實作中間件、非關系型資料庫的回滾操作,

而賬戶余額的回滾操作則是使用的是TCC模式下,我們自定義的第二階段回滾方法,

注意:測驗完畢,請關閉當前工程,防止影響其他專案,正常情況下,每一個服務都需要配置seata,我這里偷懶了,大家要注意一下!

第九章 分布式單資料源事務管理:TCC

9.1、匯入資料

9.1.1、重置賬戶資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE IF EXISTS `seata_account`;
CREATE DATABASE `seata_account`;

USE `seata_account`;

DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT 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 '0' COMMENT '剩余額度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='賬戶表';

insert  into `t_account`(`id`,`user_id`,`total`,`used`,`residue`) values (1,1,'1000','0','1000');

SELECT * FROM `seata_account`.`t_account` LIMIT 0, 1000; 

9.1.2、重置庫存資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE IF EXISTS `seata_storage`;
CREATE DATABASE `seata_storage`;

USE `seata_storage`;

DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `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 (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='庫存表';

insert  into `t_storage`(`id`,`product_id`,`total`,`used`,`residue`) values (1,1,100,0,100);

SELECT * FROM `seata_storage`.`t_storage` LIMIT 0, 1000; 

9.1.3、重置訂單資料庫

資料庫環境為mysql 5.7.33,請重新匯入運行以下sql陳述句:

DROP DATABASE `seata_order`;
CREATE DATABASE `seata_order`;

USE `seata_order`;

DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `user_id` bigint(11) 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 '狀態',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單表';

SELECT * FROM `seata_order`.`t_order` LIMIT 0, 1000; 

9.2、匯入工程

在桌面重新創建一個檔案夾,名字無所謂,拷貝配套資料中的分布式\distributed-seata-tcc-study到此檔案夾,然后使用idea打開即可,

distributed-seata-tcc-study已經實作了基本的代碼流程,非常簡單,相信你一定能看懂:

distributed-seata-tcc-study是一個典型的微服務應用,一共有三個服務,每個服務都實作了基本的代碼邏輯,并且都對應一個資料源,架構如下圖:

唯一需要你注意的是,打開application.yaml,查看連接資料源的賬戶密碼是否正確,并且我們需要你啟動nacos服務注冊中心,如下:

service-account

service-storage

service-order

9.3、正常測驗

確保啟動nacos注冊中心,

請啟動當前工程,然后輸入下單地址測驗:http://localhost:9003/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

9.4、例外測驗

打開工程service-order修改com.caochenlei.service.impl.OrderServiceImpl代碼添加例外,代碼如下:

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private AccountService accountService;
    @Resource
    private StorageService storageService;

    @Override
    public void create(Order order) {
        // 1、新建訂單
        log.info("----->新建訂單開始");
        orderMapper.create(order);
        log.info("----->新建訂單結束");

        // 2、扣減余額
        log.info("----->扣減余額開始");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("----->扣減余額結束");
        
        int i = 1 / 0; //模擬例外出錯

        // 3、扣減庫存
        log.info("----->扣減庫存開始");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("----->扣減庫存結束");
    }
}

請重啟當前工程,然后輸入下單地址測驗:http://localhost:9003/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

我們發現訂單下單失敗,訂單資料庫中多了一條訂單記錄,實際上這條記錄不應該有,同時,賬戶的余額也減少了,但是,庫存并沒有減少,這個問題是十分可怕的,因為沒有事務的支持,不能做到要不全部執行,要不全部失敗,

9.5、添加新表

賬戶資料庫添加回滾日志表,sql陳述句如下:

USE `seata_account`;

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;

庫存資料庫添加回滾日志表,sql陳述句如下:

USE `seata_storage`;

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;

訂單資料庫添加回滾日志表,sql陳述句如下:

USE `seata_order`;

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;

最終完成以后的效果如下圖:

9.6、添加依賴

service-orderservice-accountpom.xml中新增依賴:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

9.7、添加配置

service-orderservice-accountapplication.yaml中新增配置:

...
...
#seata配置
seata:
  application-id: myapp #應用的編號
  tx-service-group: myapp-group #事務組名稱
  service:
    vgroup-mapping:
      myapp-group: default #當前事務組使用 cluster: default
  config:
    type: file #組態檔使用檔案存盤方式
  registry:
    type: nacos #注冊中心使用nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: public
      cluster: default
      username: nacos
      password: nacos

9.8、添加注解

打開service-order修改com.caochenlei.service.impl.OrderServiceImpl開啟全域事務管理,代碼如下:

@Slf4j
@Service
@GlobalTransactional //開啟seata全域事務注解,支持放在類上、方法上
public class OrderServiceImpl implements OrderService {
    ...
    ...
}

9.9、賬戶服務

打開service-account修改介面:com.caochenlei.service.AccountService

@LocalTCC//此注解標識TCC為本地模式,即該事務是本地呼叫
public interface AccountService {
    //第一階段:嘗試扣減余額
    @TwoPhaseBusinessAction(name = "decreaseMoney", commitMethod = "commitDecreaseMoney", rollbackMethod = "rollbackDecreaseMoney")
    void decrease(@BusinessActionContextParameter(paramName = "userId") Long userId,
                  @BusinessActionContextParameter(paramName = "money") BigDecimal money);

    //第二階段:提交處理方法
    boolean commitDecreaseMoney(BusinessActionContext context);

    //第二階段:回滾處理方法
    boolean rollbackDecreaseMoney(BusinessActionContext context);
}

打開service-account修改實作:com.caochenlei.service.impl.AccountServiceImpl

@Service
public class AccountServiceImpl implements AccountService {
    @Resource
    private AccountMapper accountMapper;

    @Override
    public void decrease(Long userId, BigDecimal money) {
        accountMapper.decrease(userId, money);
    }

    @Override
    public boolean commitDecreaseMoney(BusinessActionContext context) {
        return true;//可以直接回傳true,即空確認
    }

    @Override
    public boolean rollbackDecreaseMoney(BusinessActionContext context) {
        //TODO 這里可以實作中間件、非關系型資料庫的回滾操作

        //通過業務動作背景關系獲取指定引數的引數值
        String userId = context.getActionContext("userId").toString();
        String money = context.getActionContext("money").toString();

        //手動進行資料庫回滾,把減去的余額加回去
        accountMapper.increase(new Long(userId), new BigDecimal(money));

        //我們手動輸出一句話,代表回滾使用我們的
        System.out.println("資料回滾了,這可真的好");

        return true;
    }
}

打開service-account修改映射:com.caochenlei.mapper.AccountMapper

@Mapper
public interface AccountMapper {
    //扣減余額
    @Update("update t_account set used=used+#{money},residue=residue-#{money} where user_id=#{userId};")
    int decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);

    //加回余額
    @Update("update t_account set used=used-#{money},residue=residue+#{money} where user_id=#{userId};")
    int increase(@Param("userId") Long userId, @Param("money") BigDecimal money);
}

9.10、例外測驗

請重啟service-orderservice-account工程

然后輸入下單地址測驗:http://localhost:9003/order/create?userId=1&productId=1&count=10&money=100&status=1

請打開資料庫表,查看下單之后資料的變化,依次是:訂單資料庫表、賬戶資料庫表、庫存資料庫表

我們發現雖然下單失敗了,但是并沒有往訂單資料庫插入訂單資訊,賬戶余額和庫存也沒有減少,這符合我們的業務邏輯,分布式下的事務管理完美解決,

而賬戶余額的回滾操作則是使用的是TCC模式下,我們自定義的第二階段回滾方法,

注意:測驗完畢,請關閉當前工程,防止影響其他專案,正常情況下,每一個服務都需要配置seata,我這里偷懶了,大家要注意一下!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/263873.html

標籤:其他

上一篇:Redis資料型別(String、List、Hash、Set、Sorted Set)

下一篇:頭條HR都急瘋了,今年金三銀四都沒人跳槽,工資都開到了40w了.....(我的KPI咋整?)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more