(一)seata1.2 AT及XA模式實例演示

歡迎關注本人公衆號

在這裏插入圖片描述

概述

Seata 是一款開源的分佈式事務解決方案,致力於提供高性能和簡單易用的分佈式事務服務。Seata 將爲用戶提供了 AT、TCC、SAGA 和 XA 事務模式,爲用戶打造一站式的分佈式解決方案。

本文先將官方實例跑起來,看看運行效果,值後在對其原理和源碼進行分析。

下載源碼

進入seata的GitHub主頁,下載seata和seata-samples兩個項目。下載下來後可以用idea打開。
在這裏插入圖片描述
下載完成後,idea導入seata-samples文件夾下的seata-xa和seata文件夾下的 server兩個項目。
在這裏插入圖片描述
在這裏插入圖片描述

AT模式演示

官方文檔寫的是下載seata-server-xxx.zip解壓運行,我這裏不適用這種方法,因爲後續還要閱讀源碼,所以直接運行上一步下載的seata源碼運行。

準備工作

本地安裝mysql8

安裝步驟:mysql8.0.20安裝教程

配置修改

兩個項目均需要修改爲我們自己的mysql,並且設置使用AT模式。
先將server切換爲1.2.0版本
在這裏插入圖片描述
seata-server修改內容:
在這裏插入圖片描述
同時將pom文件中的connection版本改爲8:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>

然後修改seata-xa項目的配置。需要修改以下內容:

在這裏插入圖片描述
application.properties修改內容都是Mysql的地址和用戶名密碼。
pom文件修改的是MySQL驅動的版本,同上。

剩下的幾個DataSourceConfiguration文件都是修改爲AT模式。
在這裏插入圖片描述

建表

我們這裏測試的是AT模式,需建4個表

DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
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;

運行官方實例

先啓動server端服務,直接運行io.seata.server.Server的main方法即可。啓動成功輸出

2020-07-06 11:14:19.108 INFO [main]io.seata.config.FileConfiguration.<init>:121 -The configuration file used is registry.conf
2020-07-06 11:14:19.145 INFO [main]io.seata.config.FileConfiguration.<init>:121 -The configuration file used is file.conf
2020-07-06 11:14:20.765 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ... 

依次啓動account/order/storage/business 4個服務。
在這裏插入圖片描述
啓動成功後,會在server端註冊。這裏seata是使用的springCloud Feign。


2020-07-06 11:14:46.080 INFO [ServerHandlerThread_1_500]io.seata.core.rpc.DefaultServerMessageListenerImpl.onRegRmMessage:127 -RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://rm-2zetd9474ydd1g5955o.mysql.rds.aliyuncs.com:3306/fescar', applicationId='account-xa', transactionServiceGroup='my_test_tx_group'},channel:[id: 0xaa9af4c7, L:/127.0.0.1:8091 - R:/127.0.0.1:50222]
2020-07-06 11:15:00.620 INFO [ServerHandlerThread_1_500]io.seata.core.rpc.DefaultServerMessageListenerImpl.onRegRmMessage:127 -RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://127.0.0.1:3306/test', applicationId='order-xa', transactionServiceGroup='my_test_tx_group'},channel:[id: 0xaef9d3dd, L:/127.0.0.1:8091 - R:/127.0.0.1:50259]
2020-07-06 11:15:07.131 INFO [ServerHandlerThread_1_500]io.seata.core.rpc.DefaultServerMessageListenerImpl.onRegRmMessage:127 -RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://127.0.0.1:3306/test', applicationId='storage-xa', transactionServiceGroup='my_test_tx_group'},channel:[id: 0xf7f0d0cd, L:/127.0.0.1:8091 - R:/127.0.0.1:50281]
2020-07-06 11:15:43.133 INFO [NettyServerNIOWorker_1_16]io.seata.core.rpc.DefaultServerMessageListenerImpl.onRegTmMessage:153 -TM register success,message:RegisterTMRequest{applicationId='account-xa', transactionServiceGroup='my_test_tx_group'},channel:[id: 0xedc81195, L:/127.0.0.1:8091 - R:/127.0.0.1:50341]
2020-07-06 11:15:56.732 INFO [NettyServerNIOWorker_1_16]io.seata.core.rpc.DefaultServerMessageListenerImpl.onRegTmMessage:153 -TM register success,message:RegisterTMRequest{applicationId='order-xa', transactionServiceGroup='my_test_tx_group'},channel:[id: 0x90901ea5, L:/127.0.0.1:8091 - R:/127.0.0.1:50360]
2020-07-06 11:16:04.010 INFO [NettyServerNIOWorker_1_16]io.seata.core.rpc.DefaultServerMessageListenerImpl.onRegTmMessage:153 -TM register success,message:RegisterTMRequest{applicationId='storage-xa', transactionServiceGroup='my_test_tx_group'},channel:[id: 0x7c1fd552, L:/127.0.0.1:8091 - R:/127.0.0.1:50369]

再business服務啓動後,會進行數據初始化:賬戶餘額10000,庫存100

@PostConstruct
    public void initData() {
        jdbcTemplate.update("delete from account_tbl");
        jdbcTemplate.update("delete from order_tbl");
        jdbcTemplate.update("delete from storage_tbl");
        jdbcTemplate.update("insert into account_tbl(user_id,money) values('" + TestDatas.USER_ID + "','10000') ");
        jdbcTemplate.update("insert into storage_tbl(commodity_code,count) values('" + TestDatas.COMMODITY_CODE + "','100') ");
    }

總共有4個服務。賬戶服務,訂單服務,庫存服務,採購業務服務。是目前主流的微服務架構,本文演示的也是微服務架構下分佈式事務問題。

訪問http://127.0.0.1:8084/purchase調用服務。
基於初始化數據,和默認的調用邏輯,purchase 將可以被成功調用 3 次。
每次賬戶餘額扣減 3000,由最初的 10000 減少到 1000。
第 4 次調用,因爲賬戶餘額不足,purchase 調用將失敗。相應的:庫存、訂單、賬戶都回滾。

調用一次以後,數據庫中餘額變化,扣了3000塊,30個庫存,多了一條訂單記錄:

mysql> select * from account_tbl;
+----+---------+-------+
| id | user_id | money |
+----+---------+-------+
|  2 | U100000 |  7000 |
+----+---------+-------+
1 row in set (0.01 sec)

mysql> select * from order_tbl;
+----+---------+----------------+-------+-------+
| id | user_id | commodity_code | count | money |
+----+---------+----------------+-------+-------+
|  3 | U100000 | C100000        |    30 |  3000 |
+----+---------+----------------+-------+-------+
1 row in set (0.00 sec)

mysql> select * from storage_tbl;
+----+----------------+-------+
| id | commodity_code | count |
+----+----------------+-------+
|  2 | C100000        |    70 |
+----+----------------+-------+
1 row in set (0.00 sec)

mysql> select * from undo_log;
Empty set (0.00 sec)

當調用第4此時,賬戶餘額不足,賬戶服務扣減餘額失敗;
下單服務失敗,相應的庫存訂單都需要回滾。
讀者可以自行驗證,自第四次調用開始,都不會成功。DB中的結果不會有任何變化。

在這裏插入圖片描述

AT模式原理初步分析

看一下undoLog表的內容,讀者可以在運行過程中添加斷點查看該表日誌,因爲如果等程序運行完成,該表的日誌會被刪除。
在這裏插入圖片描述
選一條日誌的rollback_info看看

{
	"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
	"xid": "192.168.252.1:8091:2016197280",
	"branchId": 2016197281,
	"sqlUndoLogs": [
		"java.util.ArrayList",
		[
			{
				"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
				"sqlType": "UPDATE",
				"tableName": "storage_tbl",
				"beforeImage": {
					"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
					"tableName": "storage_tbl",
					"rows": [
						"java.util.ArrayList",
						[
							{
								"@class": "io.seata.rm.datasource.sql.struct.Row",
								"fields": [
									"java.util.ArrayList",
									[
										{
											"@class": "io.seata.rm.datasource.sql.struct.Field",
											"name": "id",
											"keyType": "PRIMARY_KEY",
											"type": 4,
											"value": 2
										},
										{
											"@class": "io.seata.rm.datasource.sql.struct.Field",
											"name": "count",
											"keyType": "NULL",
											"type": 4,
											"value": 10
										}
									]
								]
							}
						]
					]
				},
				"afterImage": {
					"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
					"tableName": "storage_tbl",
					"rows": [
						"java.util.ArrayList",
						[
							{
								"@class": "io.seata.rm.datasource.sql.struct.Row",
								"fields": [
									"java.util.ArrayList",
									[
										{
											"@class": "io.seata.rm.datasource.sql.struct.Field",
											"name": "id",
											"keyType": "PRIMARY_KEY",
											"type": 4,
											"value": 2
										},
										{
											"@class": "io.seata.rm.datasource.sql.struct.Field",
											"name": "count",
											"keyType": "NULL",
											"type": 4,
											"value": -20
										}
									]
								]
							}
						]
					]
				}
			}
		]
	]
}

看了這個回覆日誌,就很明顯了。undolog中記錄了兩個快照:beforeImage和afterImage。分別記錄了修改前後的字段值,在需要回滾時,就使用beforeImage中記錄的值來回復原始數據即可。

這跟MySQL本身的undolog有異曲同工之妙。

當然實際分佈式處理比這複雜的多,上面只是將最核心的原理介紹一下,接下來文章會詳細分析seata的原理

XA事務實例演示

在使用XA事務時,我發現 mysql-connector-java 還不能改爲8.X 版本。否則會報錯, 無法創建XAConnection:

Caused by: java.sql.SQLFeatureNotSupportedException
	at com.alibaba.druid.util.MySqlUtils.createXAConnection(MySqlUtils.java:165)
	at io.seata.rm.datasource.util.XAUtils.createXAConnection(XAUtils.java:62)
	at io.seata.rm.datasource.util.XAUtils.createXAConnection(XAUtils.java:41)
	at io.seata.rm.datasource.xa.DataSourceProxyXA.getConnectionProxy(DataSourceProxyXA.java:63)
	at io.seata.rm.datasource.xa.DataSourceProxyXA.getConnection(DataSourceProxyXA.java:49)
	at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:151)
	at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:115)
	at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:78)
	... 63 more

所以將mysql-connector-java的版本還原回去:5.1.48

將三個項目中的數據源全部改爲DataSourceProxyXA實現

@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
    // DataSourceProxy for AT mode
    // return new DataSourceProxy(druidDataSource);

    // DataSourceProxyXA for XA mode
    return new DataSourceProxyXA(druidDataSource);
}

啓動服務,依舊訪問http://127.0.0.1:8084/purchase即可。操作與上面AT一樣,讀者可以自行驗證。

此時由於使用的時XA事務,所以undo_log表用不到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章