Apache Seata 1.Seata 簡介 Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能與簡單易用的分布式事務(wù)服務(wù),為用戶提供了 AT、TCC、SAGA 和 XA 幾種不同的事務(wù)模式:
AT模式:無侵入式的分布式事務(wù)解決方案,適合不希望對業(yè)務(wù)進(jìn)行改造的場景,但由于需要添加全局事務(wù)鎖,對影響高并發(fā)系統(tǒng)的性能。該模式主要關(guān)注多DB訪問的數(shù)據(jù)一致性,也包括多服務(wù)下的多DB數(shù)據(jù)訪問一致性問題
TCC模式:高性能的分布式事務(wù)解決方案,適用于對性能要求比較高的場景。該模式主要關(guān)注業(yè)務(wù)拆分,在按照業(yè)務(wù)橫向擴(kuò)展資源時,解決服務(wù)間調(diào)用的一致性問題
Saga模式:長事務(wù)的分布式事務(wù)解決方案,適用于業(yè)務(wù)流程長且需要保證事務(wù)最終一致性的業(yè)務(wù)系統(tǒng)。Saga 模式一階段就會提交本地事務(wù),無鎖,長流程情況下可以保證性能,多用于渠道層、集成層業(yè)務(wù)系統(tǒng),事務(wù)參與者可以是其它公司的服務(wù)也可以是遺留系統(tǒng)的服務(wù),并且對于無法進(jìn)行改造和提供 TCC 要求的接口,也可以使用 Saga 模式
2.Seata 的核心組件 在 Seata 中主要有以下三種角色,其中 TM 和 RM 是作為 Seata 的客戶端與業(yè)務(wù)系統(tǒng)集成在一起,TC 作為 Seata 的服務(wù)端獨立部署:
事務(wù)協(xié)調(diào)器(TC):維護(hù)全局事務(wù)的運行狀態(tài),負(fù)責(zé)協(xié)調(diào)并驅(qū)動全局提交或回滾
事務(wù)管理器(TM):事務(wù)發(fā)起方,控制全局事務(wù)的范圍,負(fù)責(zé)開啟一個全局事務(wù),并最終發(fā)起全局提交或回滾全局的決議
資源管理器(RM):事務(wù)參與方,管理本地事務(wù)正在處理的資源,負(fù)責(zé)向 TC 注冊本地事務(wù)、匯報本地事務(wù)狀態(tài),接收 TC 的命令來驅(qū)動本地事務(wù)的提交或回滾
3.Seata 的 AT 模式原理 AT模式,是seata的默認(rèn)/獨有模式,也是實際項目中比較常用的一種模式,他采用的也是兩階段提交,不過彌補了XA模式中資源鎖定周期過長的缺點,相對于XA來說,性能更好一些,但缺點就是數(shù)據(jù)不是強一致,因為它的數(shù)據(jù)會真實的提交到數(shù)據(jù)庫的,而如果后面做分支事務(wù)有問題的話,回滾靠的是日志來實現(xiàn)最終一致。
基本原理流程圖:
階段一RM的工作:
先會注冊一個分支事務(wù)到事務(wù)協(xié)調(diào)者TC中
記錄一個SQL更新前的快照和一個更新后的快照到undo_log日志表中
執(zhí)行SQL并提交數(shù)據(jù)庫事務(wù)
報告事務(wù)狀態(tài)
階段二RM的工作:
如果此時所有微服務(wù)都執(zhí)行完,并且沒有出現(xiàn)異常情況,事務(wù)協(xié)調(diào)者TC通知RM刪除undo-log記錄。
如果此時中途有微服務(wù)出現(xiàn)異常情況,則TC會通知RM根據(jù)undo-log記錄的對應(yīng)快照恢復(fù)數(shù)據(jù)到更新前。
4.SpringCloud Alibaba 整合 Seata AT 模式 4.1.開發(fā)組件介紹
名稱
版本
HighGoDB
安全版V4.5
HgdbJdbc
42.5.0
JDK
1.8
Java IDE
IntelliJ IDEA
spring-cloud
Hoxton.SR10
spring-cloud-alibaba
2.2.5.RELEASE
SpringBoot
2.2.4.RELEASE
mybatis
7.0.0.Beta2
seata
1.4.2
nacos
2.2.0
4.2.安裝nacos 參考Nacos2.2.0適配瀚高數(shù)據(jù)庫
windows環(huán)境下,在nacos/bin目錄下,進(jìn)入dos輸入命令startup.cmd -m standalone啟動nacos
http://192.168.10.127:8848/nacos/index.html
用戶名:nacos
密碼:nacos
4.3.安裝seata 4.3.1.下載seata-server https://github.com/seata/seata/releases
下載安裝包和源碼包
4.3.2.增加瀚高驅(qū)動支持 安裝包解壓后,seata-server-1.4.2\lib路徑下添加hgdb-pgjdbc-42.5.0.jar
驅(qū)動包下載地址
https://mvnrepository.com/artifact/com.highgo/hgdb-pgjdbc
4.3.3.創(chuàng)建相關(guān)數(shù)據(jù)庫和表 參考postgresql.sql文件,可在4.1.章節(jié)下載的源碼包incubator-seata-1.4.2\script\server\db路徑下找到。
create database seata;CREATE TABLE IF NOT EXISTS public.global_table( xid VARCHAR (128 ) NOT NULL , transaction_id BIGINT , status SMALLINT 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 TIMESTAMP (0 ), gmt_modified TIMESTAMP (0 ), PRIMARY KEY (xid) ); CREATE INDEX ON public.global_table (gmt_modified, status);CREATE INDEX ON public.global_table (transaction_id);CREATE TABLE IF NOT EXISTS public.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 SMALLINT , client_id VARCHAR (64 ), application_data VARCHAR (2000 ), gmt_create TIMESTAMP (6 ), gmt_modified TIMESTAMP (6 ), PRIMARY KEY (branch_id) ); CREATE INDEX ON public.branch_table (xid);CREATE TABLE IF NOT EXISTS public.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 ), gmt_create TIMESTAMP (0 ), gmt_modified TIMESTAMP (0 ), PRIMARY KEY (row_key) ); CREATE INDEX ON public.lock_table (branch_id);
4.3.4.配置相關(guān)
自定義命名空間
為了方便區(qū)分不同的應(yīng)用,在nacos新建命名空間seata,這里創(chuàng)建后的命名空間,這里的命名空間id會在之后的配置中使用到
配置seataServer.properties
Data ID: seataServer.properties
Group: SEATA_GROUP
配置格式 Properties
注意:在這里配置的Data ID 和 Group 需要和后文中配置保持一致,以確保seata能拿到存放在nacos中的配置
配置內(nèi)容,參考配置文件config.txt,可在4.1.章節(jié)下載的源碼包incubator-seata-1.4.2\script\config-center下找到。
transport.type=TCP transport.server=NIO transport.heartbeat=true transport.enableClientBatchSendRequest=false transport.threadFactory.bossThreadPrefix=NettyBoss transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler transport.threadFactory.shareBossWorker=false transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector transport.threadFactory.clientSelectorThreadSize=1 transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread transport.threadFactory.bossThreadSize=1 transport.threadFactory.workerThreadSize=default transport.shutdown.wait=3 service.vgroupMapping.default_tx_group=default service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false client.rm.asyncCommitBufferLimit=10000 client.rm.lock.retryInterval=10 client.rm.lock.retryTimes=30 client.rm.lock.retryPolicyBranchRollbackOnConflict=true client.rm.reportRetryCount=5 client.rm.tableMetaCheckEnable=false client.rm.tableMetaCheckerInterval=60000 client.rm.sqlParserType=druid client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 client.tm.degradeCheck=false client.tm.degradeCheckAllowTimes=10 client.tm.degradeCheckPeriod=2000 store.mode=db store.publicKey= store.file.dir=file_store/data store.file.maxBranchSessionSize=16384 store.file.maxGlobalSessionSize=512 store.file.fileWriteBufferCacheSize=16384 store.file.flushDiskMode=async store.file.sessionReloadReadSize=100 store.db.datasource=druid store.db.dbType=postgresql store.db.driverClassName=org.postgresql.Driver store.db.url=jdbc:postgresql://192.168.100.101:5866/seata store.db.user=sysdba store.db.password=Qwer@1234 store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000 store.redis.mode=single store.redis.single.host=127.0.0.1 store.redis.single.port=6379 store.redis.sentinel.masterName= store.redis.sentinel.sentinelHosts= store.redis.maxConn=10 store.redis.minConn=1 store.redis.maxTotal=100 store.redis.database=0 store.redis.password= store.redis.queryLimit=100 server.recovery.committingRetryPeriod=1000 server.recovery.asynCommittingRetryPeriod=1000 server.recovery.rollbackingRetryPeriod=1000 server.recovery.timeoutRetryPeriod=1000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false client.undo.dataValidation=true client.undo.logSerialization=jackson client.undo.onlyCareUpdateColumns=true server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 client.undo.logTable=undo_log client.undo.compress.enable=true client.undo.compress.type=zip client.undo.compress.threshold=64k log.exceptionRate=100 transport.serialization=seata transport.compressor=none metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898
配置seata-server-1.4.2\conf\file.conf
store { mode = "db" db { datasource = "druid" dbType = "postgresql" driverClassName = "org.postgresql.Driver" url = "jdbc:postgresql://192.168.100.101:5866/seata" user = "sysdba" password = "Qwer@1234" minConn = 5 maxConn = 100 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } }
office
配置seata-server-1.4.2\conf\registry.conf
注意:
namespace 對應(yīng)nacos中自定義的seata命名空間ID
group 對應(yīng)nacos中配置的 Group(SEATA_GROUP)
dataId 對應(yīng)nacos中配置的 Data ID(seataServer.properties)
registry { type = "nacos" nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "31976cce-fc16-4c7f-8c39-74f78ebe50a3" cluster = "default" username = "nacos" password = "nacos" } } config { type = "nacos" nacos { serverAddr = "127.0.0.1:8848" namespace = "31976cce-fc16-4c7f-8c39-74f78ebe50a3" group = "SEATA_GROUP" username = "nacos" password = "nacos" dataId = "seataServer.properties" } }
office
4.3.5.啟動seata cmd啟動seata-server.bat
如圖所示,沒報錯就是啟動成功了。
nacos服務(wù)列表中可以看到注冊的seata服務(wù)。由于我們配置命名空間為seata,因此在seata命名空間里能看到此服務(wù)。
4.4.項目搭建 創(chuàng)建三個項目分別負(fù)責(zé)下單,庫存,賬戶余額進(jìn)行演示分布式事務(wù)。
4.4.1.數(shù)據(jù)庫準(zhǔn)備 在seata庫下分別創(chuàng)建三個模式,建表。
seata_order
CREATE SCHEMA seata_order; CREATE TABLE seata_order.t_order ( id bigserial NOT NULL, user_id bigint DEFAULT NULL, product_id bigint DEFAULT NULL, count int DEFAULT NULL, money NUMERIC(11, 0) DEFAULT NULL, status int DEFAULT NULL, PRIMARY KEY (id ) ); COMMENT ON COLUMN seata_order.t_order.user_id IS '用戶id' ; COMMENT ON COLUMN seata_order.t_order.product_id IS '產(chǎn)品id' ; COMMENT ON COLUMN seata_order.t_order.count IS '數(shù)量' ; COMMENT ON COLUMN seata_order.t_order.money IS '金額' ; COMMENT ON COLUMN seata_order.t_order.status IS '訂單狀態(tài):0:創(chuàng)建中;1:已完結(jié)' ; CREATE TABLE IF NOT EXISTS seata_order.undo_log ( id bigserial NOT NULL, branch_id BIGINT NOT NULL, xid VARCHAR(100) NOT NULL, context VARCHAR(128) NOT NULL, rollback_info bytea NOT NULL, log_status INT NOT NULL, log_created TIMESTAMP NOT NULL, log_modified TIMESTAMP NOT NULL, PRIMARY KEY (id ), UNIQUE (xid, branch_id) );
seata_storage
CREATE SCHEMA seata_storage; CREATE TABLE seata_storage.t_storage ( id bigserial NOT NULL, product_id bigint DEFAULT NULL, total int DEFAULT NULL, used int DEFAULT NULL, residue int DEFAULT NULL, PRIMARY KEY (id ) ); COMMENT ON COLUMN seata_storage.t_storage.product_id IS '產(chǎn)品id' ; COMMENT ON COLUMN seata_storage.t_storage.total IS '庫存' ; COMMENT ON COLUMN seata_storage.t_storage.used IS '已用庫存' ; COMMENT ON COLUMN seata_storage.t_storage.residue IS '剩余庫存' ; INSERT INTO seata_storage.t_storage (id , product_id, total, used, residue) VALUES (1, 1, 100, 0, 100); CREATE TABLE IF NOT EXISTS seata_storage.undo_log ( id bigserial NOT NULL, branch_id BIGINT NOT NULL, xid VARCHAR(100) NOT NULL, context VARCHAR(128) NOT NULL, rollback_info bytea NOT NULL, log_status INT NOT NULL, log_created TIMESTAMP NOT NULL, log_modified TIMESTAMP NOT NULL, PRIMARY KEY (id ), UNIQUE (xid, branch_id) );
seata_account
CREATE SCHEMA seata_account; CREATE TABLE IF NOT EXISTS seata_account.t_account ( id bigserial NOT NULL, user_id bigint DEFAULT NULL, total NUMERIC(10, 0) DEFAULT NULL, used NUMERIC(10, 0) DEFAULT NULL, residue NUMERIC(10, 0) DEFAULT NULL, PRIMARY KEY (id ) ); COMMENT ON COLUMN seata_account.t_account.user_id IS '用戶id' ; COMMENT ON COLUMN seata_account.t_account.total IS '總額度' ; COMMENT ON COLUMN seata_account.t_account.used IS '已用額度' ; COMMENT ON COLUMN seata_account.t_account.residue IS '剩余可用額度' ; INSERT INTO seata_account.t_account (id , user_id, total, used, residue) VALUES (1, 1, 1000, 0, 1000); CREATE TABLE IF NOT EXISTS seata_account.undo_log ( id bigserial NOT NULL, branch_id BIGINT NOT NULL, xid VARCHAR(100) NOT NULL, context VARCHAR(128) NOT NULL, rollback_info bytea NOT NULL, log_status INT NOT NULL, log_created TIMESTAMP NOT NULL, log_modified TIMESTAMP NOT NULL, PRIMARY KEY (id ), UNIQUE (xid, branch_id) );
4.4.2.父工程springcloud-seata-demo
pom.xml
<?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > 2.2.4.RELEASE</version > </parent > <groupId > com.highgo.seata</groupId > <artifactId > springcloud-seata-demo</artifactId > <version > 1.0.1</version > <packaging > pom</packaging > <modules > <module > seata-order</module > <module > seata-storage</module > <module > seata-account</module > </modules > <properties > <java.version > 1.8</java.version > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <spring-cloud.version > Hoxton.SR10</spring-cloud.version > <spring-cloud-alibaba.version > 2.2.5.RELEASE</spring-cloud-alibaba.version > <mybatis.spring.boot.version > 2.1.4</mybatis.spring.boot.version > <seata.version > 1.4.2</seata.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > ${spring-cloud.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-alibaba-dependencies</artifactId > <version > ${spring-cloud-alibaba.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > ${mybatis.spring.boot.version}</version > </dependency > <dependency > <groupId > io.seata</groupId > <artifactId > seata-spring-boot-starter</artifactId > <version > ${seata.version}</version > </dependency > <dependency > <groupId > com.highgo</groupId > <artifactId > HgdbJdbc</artifactId > <version > 6.2.4</version > </dependency > <dependency > <groupId > com.highgo</groupId > <artifactId > hgdb-pgjdbc</artifactId > <version > 42.5.0</version > </dependency > </dependencies > </dependencyManagement > </project >
4.4.3.子工程seata-order
pom.xml
<?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > springcloud-seata-demo</artifactId > <groupId > com.highgo.seata</groupId > <version > 1.0.1</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > seata-order</artifactId > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <configuration > <source > 6</source > <target > 6</target > </configuration > </plugin > </plugins > </build > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > io.seata</groupId > <artifactId > seata-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-seata</artifactId > <exclusions > <exclusion > <groupId > io.seata</groupId > <artifactId > seata-spring-boot-starter</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > com.highgo</groupId > <artifactId > HgdbJdbc</artifactId > </dependency > <dependency > <groupId > com.highgo</groupId > <artifactId > hgdb-pgjdbc</artifactId > </dependency > </dependencies > </project >
application.yml
server: port: 2001 spring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos group: SEATA_GROUP namespace: 31976cce-fc16-4c7f-8c39-74f78ebe50a3 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.postgresql.Driver url: jdbc:postgresql://192.168.100.101:5866/seata?currentSchema=seata_order username: sysdba password: Qwer@1234 seata: enable-auto-data-source-proxy: false registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: ${spring.cloud.nacos.discovery.namespace} config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: ${spring.cloud.nacos.discovery.namespace} data-id: seataServer.properties tx-service-group: default_tx_group # 事務(wù)組的名稱,對應(yīng)service.vgroupMapping.default_tx_group=xxx中配置的default_tx_group service: vgroup-mapping: default_tx_group: default #指定事務(wù)分組至集群映射關(guān)系(等號右側(cè)的集群名需要與Seata-server注冊到Nacos的cluster保持一致) feign: hystrix: enabled: true logging: level: io: seata: info mybatis: mapper-locations: classpath*:mapper/*.xml
config配置類
MyBatisConfig
package com.highgo.seata.order.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.highgo.seata.order.dao") public class MyBatisConfig { }
DataSourceProxyConfig
package com.highgo.seata.order.config; import com.alibaba.druid.pool.DruidDataSource; import io.seata.rm.datasource.DataSourceProxy; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DataSourceProxyConfig { @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; /** * 將阿里云提供的seata專用數(shù)據(jù)庫連接池對象放入IOC容器 */ @Bean public DataSourceProxy dataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driverClassName); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return new DataSourceProxy(druidDataSource); } }
實體類
CommonResult
package com.highgo.seata.order.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T > { private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code,message,null); } }
Order
package com.highgo.seata.order.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; @Data @AllArgsConstructor @NoArgsConstructor public class Order { private Long id; private Long userId; private Long productId; private Integer count; private BigDecimal money; /** * 訂單狀態(tài):0:創(chuàng)建中;1:已完結(jié) */ private Integer status; }
Dao層
OrderDao
package com.highgo.seata.order.dao; import com.highgo.seata.order.domain.Order; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface OrderDao { /** * 創(chuàng)建訂單 */ void create(Order order); /** * 修改訂單金額 */ void update(@Param("userId") Long userId,@Param("status")Integer status); }
OrderMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.highgo.seata.order.dao.OrderDao" > <resultMap id ="BaseResultMap" type ="com.highgo.seata.order.domain.Order" > <id column ="id" property ="id" jdbcType ="BIGINT" > </id > <result column ="user_id" property ="userId" jdbcType ="BIGINT" > </result > <result column ="product_id" property ="productId" jdbcType ="BIGINT" > </result > <result column ="count" property ="count" jdbcType ="INTEGER" > </result > <result column ="money" property ="money" jdbcType ="DECIMAL" > </result > <result column ="status" property ="status" jdbcType ="INTEGER" > </result > </resultMap > <insert id ="create" parameterType ="com.highgo.seata.order.domain.Order" > insert into t_order(user_id,product_id,count,money,status) values (#{userId},#{productId},#{count},#{money},0); </insert > <update id ="update" > update t_order set status =1 where user_id =#{userId} and status=#{status}; </update > </mapper >
service業(yè)務(wù)類
OrderService
package com.highgo.seata.order.service; import com.highgo.seata.order.domain.Order; public interface OrderService { void create(Order order); }
StorageService
package com.highgo.seata.order.service; import com.highgo.seata.order.domain.CommonResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(value = "storage-service") public interface StorageService { @PostMapping("/storage/decrease") CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count); }
AccountService
package com.highgo.seata.order.service; import com.highgo.seata.order.domain.CommonResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import java.math.BigDecimal; @FeignClient(value = "account-service") public interface AccountService { /** * 扣減賬戶余額 */ @PostMapping("/account/decrease") CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }
OrderServiceImpl
package com.highgo.seata.order.service.impl; import com.highgo.seata.order.dao.OrderDao; import com.highgo.seata.order.domain.Order; import com.highgo.seata.order.service.AccountService; import com.highgo.seata.order.service.OrderService; import com.highgo.seata.order.service.StorageService; import io.seata.core.context.RootContext; import io.seata.spring.annotation.GlobalTransactional; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service @Slf4j public class OrderServiceImpl implements OrderService { @Resource private OrderDao orderDao; @Resource private AccountService accountService; @Resource private StorageService storageService; @Override @GlobalTransactional(name = "create-order",rollbackFor = Exception.class) public void create(Order order) { log.info("order-service事務(wù)id---------------------->"+ RootContext.getXID()); log.info("------->下單開始"); //本應(yīng)用創(chuàng)建訂單 orderDao.create(order); //遠(yuǎn)程調(diào)用庫存服務(wù)扣減庫存 log.info("------->order-service中扣減庫存開始"); storageService.decrease(order.getProductId(),order.getCount()); log.info("------->order-service中扣減庫存結(jié)束"); //遠(yuǎn)程調(diào)用賬戶服務(wù)扣減余額 log.info("------->order-service中扣減余額開始"); accountService.decrease(order.getUserId(),order.getMoney()); log.info("------->order-service中扣減余額結(jié)束"); //修改訂單狀態(tài)為已完成 log.info("------->order-service中修改訂單狀態(tài)開始"); orderDao.update(order.getUserId(),0); log.info("------->order-service中修改訂單狀態(tài)結(jié)束"); log.info("------->下單結(jié)束"); } }
controller控制層
OrderController
package com.highgo.seata.order.controller; import com.highgo.seata.order.domain.CommonResult; import com.highgo.seata.order.domain.Order; import com.highgo.seata.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @GetMapping("/create") public CommonResult create(Order order) { orderService.create(order); return new CommonResult(200, "訂單創(chuàng)建完成"); } }
主啟動類
package com.highgo.seata.order; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
4.4.4.子工程seata-storage
pom.xml
<?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > springcloud-seata-demo</artifactId > <groupId > com.highgo.seata</groupId > <version > 1.0.1</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > seata-storage</artifactId > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > io.seata</groupId > <artifactId > seata-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-seata</artifactId > <exclusions > <exclusion > <groupId > io.seata</groupId > <artifactId > seata-spring-boot-starter</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > com.highgo</groupId > <artifactId > HgdbJdbc</artifactId > </dependency > <dependency > <groupId > com.highgo</groupId > <artifactId > hgdb-pgjdbc</artifactId > </dependency > </dependencies > </project >
application.yml
server: port: 2002 spring: application: name: storage-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos group: SEATA_GROUP namespace: 31976cce-fc16-4c7f-8c39-74f78ebe50a3 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.postgresql.Driver url: jdbc:postgresql://192.168.100.101:5866/seata?currentSchema=seata_storage username: sysdba password: Qwer@1234 seata: enable-auto-data-source-proxy: false registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: ${spring.cloud.nacos.discovery.namespace} config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: ${spring.cloud.nacos.discovery.namespace} data-id: seataServer.properties tx-service-group: default_tx_group # 事務(wù)組的名稱,對應(yīng)service.vgroupMapping.default_tx_group=xxx中配置的default_tx_group service: vgroup-mapping: default_tx_group: default #指定事務(wù)分組至集群映射關(guān)系(等號右側(cè)的集群名需要與Seata-server注冊到Nacos的cluster保持一致) feign: hystrix: enabled: true logging: level: io: seata: info mybatis: mapper-locations: classpath*:mapper/*.xml
config配置類
MyBatisConfig
package com.highgo.seata.storage.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.highgo.seata.storage.dao") public class MyBatisConfig { }
DataSourceProxyConfig
package com.highgo.seata.storage.config; import com.alibaba.druid.pool.DruidDataSource; import io.seata.rm.datasource.DataSourceProxy; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DataSourceProxyConfig { @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; /** * 將阿里云提供的seata專用數(shù)據(jù)庫連接池對象放入IOC容器 */ @Bean public DataSourceProxy dataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driverClassName); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return new DataSourceProxy(druidDataSource); } }
實體類
Storage
package com.highgo.seata.storage.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Storage { private Long id; private Long productId; private Integer total; private Integer used; private Integer residue; }
CommonResult
package com.highgo.seata.storage.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T > { private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code,message,null); } }
Dao層
StorageDao
package com.highgo.seata.storage.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface StorageDao { void decrease(@Param("productId") Long productId, @Param("count") Integer count); }
StorageMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.highgo.seata.storage.dao.StorageDao" > <resultMap id ="BaseResultMap" type ="com.highgo.seata.storage.domain.Storage" > <id column ="id" property ="id" jdbcType ="BIGINT" > </id > <result column ="product_id" property ="productId" jdbcType ="BIGINT" > </result > <result column ="total" property ="total" jdbcType ="INTEGER" > </result > <result column ="used" property ="used" jdbcType ="INTEGER" > </result > <result column ="residue" property ="residue" jdbcType ="INTEGER" > </result > </resultMap > <update id ="decrease" > update t_storage set used =used + #{count},residue=residue - #{count} where product_id=#{productId}; </update > </mapper >
service業(yè)務(wù)類
StorageService
package com.highgo.seata.storage.service; public interface StorageService { /** * 扣減庫存 */ void decrease(Long productId, Integer count); }
StorageServiceImpl
package com.highgo.seata.storage.service.impl; import com.highgo.seata.storage.dao.StorageDao; import com.highgo.seata.storage.service.StorageService; import io.seata.core.context.RootContext; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service @Slf4j public class StorageServiceImpl implements StorageService { @Resource private StorageDao storageDao; @Override public void decrease(Long productId, Integer count) { log.info("storage-service事務(wù)id---------------------->"+ RootContext.getXID()); log.info("------->storage-service中扣減庫存開始"); storageDao.decrease(productId,count); log.info("------->storage-service中扣減庫存結(jié)束"); } }
Controller層
StorageController
package com.highgo.seata.storage.controller; import com.highgo.seata.storage.domain.CommonResult; import com.highgo.seata.storage.service.StorageService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("/storage") public class StorageController { @Autowired private StorageService storageService; @PostMapping(value = "/decrease") public CommonResult decrease(Long productId, Integer count) { storageService.decrease(productId,count); return new CommonResult(200, "扣減庫存成功!"); } }
主啟動類
package com.highgo.seata.storage; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class StorageApplication { public static void main(String[] args) { SpringApplication.run(StorageApplication.class, args); } }
4.4.5.子工程seata-account
pom.xml
<?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > springcloud-seata-demo</artifactId > <groupId > com.highgo.seata</groupId > <version > 1.0.1</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > seata-account</artifactId > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > io.seata</groupId > <artifactId > seata-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-seata</artifactId > <exclusions > <exclusion > <groupId > io.seata</groupId > <artifactId > seata-spring-boot-starter</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > com.highgo</groupId > <artifactId > HgdbJdbc</artifactId > </dependency > <dependency > <groupId > com.highgo</groupId > <artifactId > hgdb-pgjdbc</artifactId > </dependency > </dependencies > </project >
application.yml
server: port: 2003 spring: application: name: account-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos group: SEATA_GROUP namespace: 31976cce-fc16-4c7f-8c39-74f78ebe50a3 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.postgresql.Driver url: jdbc:postgresql://192.168.100.101:5866/seata?currentSchema=seata_account username: sysdba password: Qwer@1234 seata: enable-auto-data-source-proxy: false registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: ${spring.cloud.nacos.discovery.namespace} config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: ${spring.cloud.nacos.discovery.namespace} data-id: seataServer.properties tx-service-group: default_tx_group # 事務(wù)組的名稱,對應(yīng)service.vgroupMapping.default_tx_group=xxx中配置的default_tx_group service: vgroup-mapping: default_tx_group: default #指定事務(wù)分組至集群映射關(guān)系(等號右側(cè)的集群名需要與Seata-server注冊到Nacos的cluster保持一致) feign: hystrix: enabled: true logging: level: io: seata: info mybatis: mapper-locations: classpath*:mapper/*.xml
config配置類
MybatisConfig
package com.highgo.seata.account.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.highgo.seata.account.dao") public class MybatisConfig { }
DataSourceProxyConfig
package com.highgo.seata.account.config; import com.alibaba.druid.pool.DruidDataSource; import io.seata.rm.datasource.DataSourceProxy; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DataSourceProxyConfig { @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; /** * 將阿里云提供的seata專用數(shù)據(jù)庫連接池對象放入IOC容器 */ @Bean public DataSourceProxy dataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driverClassName); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return new DataSourceProxy(druidDataSource); } }
實體類
Account
package com.highgo.seata.account.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Account { private Long id; private Long userId; private Integer total; private Integer used; private Integer residue; }
CommonResult
package com.highgo.seata.account.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T > { private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code,message,null); } }
Dao層
AccountDao
package com.highgo.seata.account.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.math.BigDecimal; @Mapper public interface AccountDao { /** * 扣減賬戶余額 */ void decrease(@Param("userId") Long userId,@Param("money") BigDecimal money); }
AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.highgo.seata.account.dao.AccountDao" > <resultMap id ="BaseResultMap" type ="com.highgo.seata.account.domain.Account" > <id column ="id" property ="id" jdbcType ="BIGINT" /> <result column ="user_id" property ="userId" jdbcType ="BIGINT" /> <result column ="total" property ="total" jdbcType ="DECIMAL" /> <result column ="used" property ="used" jdbcType ="DECIMAL" /> <result column ="residue" property ="residue" jdbcType ="DECIMAL" /> </resultMap > <update id ="decrease" > update t_account set residue = residue- #{money},used = used + #{money} where user_id =#{userId} </update > </mapper >
service層
AccountService
package com.highgo.seata.account.service; import org.springframework.web.bind.annotation.RequestParam; import java.math.BigDecimal; public interface AccountService { void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }
AccountServiceImpl
package com.highgo.seata.account.service.impl; import com.highgo.seata.account.dao.AccountDao; import com.highgo.seata.account.service.AccountService; import io.seata.core.context.RootContext; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; @Service @Slf4j public class AccountServiceImpl implements AccountService { @Resource private AccountDao accountDao; @Override public void decrease(Long userId, BigDecimal money) { log.info("account-service事務(wù)id---------------------->"+ RootContext.getXID()); log.info("------->account-service中扣減賬戶余額開始"); // try { // //模擬超時異常,全局事務(wù)回滾 // TimeUnit.SECONDS.sleep(30000); // } catch (Exception e) { // e.printStackTrace(); // } accountDao.decrease(userId, money); log.info("------->account-service中扣減賬戶余額結(jié)束"); } }
Controller層
AccountController
package com.highgo.seata.account.controller; import com.highgo.seata.account.domain.CommonResult; import com.highgo.seata.account.service.AccountService; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.math.BigDecimal; @Slf4j @RestController @RequestMapping("/account") public class AccountController { @Resource private AccountService accountService; @PostMapping("/decrease") public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) { accountService.decrease(userId, money); return new CommonResult(200, "扣減賬戶余額成功!"); } }
主啟動類
package com.highgo.seata.account; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class AccountApplication { public static void main(String[] args) { SpringApplication.run(AccountApplication.class, args); } }
5.場景測試 5.1.服務(wù)啟動 依次啟動Nacos、Seata、所有微服務(wù)
nacos服務(wù)列表
5.2.測試正常流程 服務(wù)seata-order打個斷點
瀏覽器訪問 http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
當(dāng)進(jìn)到斷點時,數(shù)據(jù)庫中可以看到,seata_order、seata_storage、seata_account模式下的undo_log表,各自生成了一條日志記錄,全局事務(wù)xid相同 ,說明在同一個全局事務(wù)中。
查看訂單表、庫存表和余額表,數(shù)據(jù)均已提交
放開斷點,日志記錄表被刪除
日志輸出
5.3.測試回滾流程 服務(wù)seata-account,模擬超時異常
服務(wù)seata-order,打個斷點
瀏覽器訪問 http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
當(dāng)進(jìn)入斷點扣減余額時,訂單和庫存對應(yīng)的日志表,各生成了一條記錄,全局事務(wù)xid是相同的
訂單表和庫存表,訂單已經(jīng)創(chuàng)建,庫存扣減完成
放開斷點,全局事務(wù)回滾
日志輸出