目录
一、分布式事务面试题
1.多个数据库之间如何处理分布式事务?
2.若拿出如下场景,阁下将如何应对?
3.阿里巴巴的Seata-AT模式如何做到对业务的无侵入?
4.对于分布式事务问题,你知道的解决方案有哪些?请你谈谈?
二、分布式事务问题如何产生?请先看业务
分布式事务之前:
分布式事务改变后:
三、Seata简介
Seata的发展历程:
官网及源码
使用
Seata的分布式交易解决方案
四、Seata工作流程简介:
分布式事务的理解:
Seata对分布式事务的协调和控制就是1+3
分布式事务的执行流程-总结:(重点)
五、各事务模式(重点)
Seata AT 模式(常用)
适用场景:
前提
整体机制
整体机制的理解
一阶段过程;
二阶段异常回滚:
二阶段正常提交:
写隔离
以一个示例来说明:
读隔离
工作机制示例:
一阶段
二阶段-回滚
二阶段-提交
回滚日志表
Seata TCC 模式
适用场景:
Seata Saga 模式
适用场景:
Seata XA 模式
适用场景:
六、Seata-Server2.0.0安装
1.下载地址
2.下载版本
3.各种seata参数官网参考
关注属性(详细描述见全属性)
全属性
公共部分
server 端
client 端
4.Seata新手部署指南
5.mysql8.0数据库里面建库+建表
建库—创建seata库
建表—在seata库创建表
运行结果
6.更改配置
7.先启动Nacos2.2.3端口号8848
8.再启动seata-server-2.0.0
七、Seata案例实战-数据库和表准备(AT)
分布式事务案例—业务说明
1.创建3个业务数据库DATABASE
建库SQL
2. 按照上述3库分别建对应的undo log回滚日志表
undo_log建表sql
3.按照上述3库分别建对应业务表
t_order脚本SQL
t_account脚本SQL
t_storage脚本SQL
最终SQL(全)
建seata_order库+建t_order表+undo_log表
建seata_storage库+建t_storage 表+undo_log表
建seata_account库+建t_account 表+undo_log表
最终效果:
八、Seata系例实战-微服务编码落地实现(AT)
1.Mybaits一键生成
config.properties
generatorConfig.xml
双击执行一键生成
2.修改公共cloud-api-commons新增库存和账户两个Feign服务接口
3.新建订单Order微服务
(1).建Module
(2).改POM
(3).写YML
(4).主启动类
SpringBootApplication是一个组合注解,主要用于标记Spring Boot的主配置类,用于启动Spring Boot应用程序。它由以下三个注解组成:
(5).业务类
1).entities
2).OrderMapper
3).Service接口及实现
OrderService
OrderServiceImpl(重点)
4).Controller
4.新建库存Storage微服务
(1).建Module
(2).改POM
(3).写YML
(4).主启动类
(5).业务类
1).entities
2).StorageMapper
3).Service接口及实现
4).Controller
5.新建账户Account微服务
(1).建Module
(2).改POM
(3).写YML
(4).主启动类
(5).业务类
1).entities
2).AccountMapper
3).Service接口及实现
4).Controller
九、Seata案例实战-测试
服务启动情况
数据库初始化情况
1.正常下单
2).此时我们没有在订单模块添加@GlobalTransactional
3).正常下单,第1次
故障现象
导致原因
4).正常下单,第2次
数据库情况
2.超时异常出错,没有@GlobalTransactional
1).添加超时方法
2).故障情况
3.超时异常解决,添加@GlobalTransactional
前提条件
查看Seata后台:
全局事务ID
全局锁
数据回滚
业务中
回滚后
全局事务,正常扣除操作
总结
一、分布式事务面试题
1.多个数据库之间如何处理分布式事务?2.若拿出如下场景,阁下将如何应对?3.阿里巴巴的Seata-AT模式如何做到对业务的无侵入?4.对于分布式事务问题,你知道的解决方案有哪些?请你谈谈?
1.多个数据库之间如何处理分布式事务?

2.若拿出如下场景,阁下将如何应对?

冻结库存 是指在ERP系统中, 将部分库存暂时锁定,使其不能参与正常的出库和入库操作 。这种操作通常用于防止库存被意外或错误地使用,确保在特定时间段内库存的稳定性和可用性。冻结库存的方法包括设置库存冻结、使用库存锁定功能、创建保留订单和利用批次管理。
3.阿里巴巴的Seata-AT模式如何做到对业务的无侵入?
4.对于分布式事务问题,你知道的解决方案有哪些?请你谈谈?
- TCC(Try-Confirm-Cancel)又被称补偿事务
- 类似2PC的柔性分布式解决方案,2PC改良版
二、分布式事务问题如何产生?请先看业务
一次业务操作需要 跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。但是关系型数据库提供的能力是基于 单机事务的,一旦遇到分布式事务场景,就需要通过更多其他技术手段来解决问题。
分布式事务之前:
- 单机单库没这个问题
- 表结构的关系从1:1->1:N -> N:N
分布式事务改变后:

三、Seata简介
Seata的发展历程:
阿里巴巴作为国内最早一批进行应用分布式(微服务化)改造的企业,很早就遇到微服务架构下的分布式事务问题。2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案:2014 年,阿里中间件团队发布 TXC(Taobao Transaction Constructor),为集团内应用提供分布式事务服务。2016 年,TXC 在经过产品化改造后,以 GTS(Global Transaction Service) 的身份登陆阿里云,成为当时业界唯一一款云上分布式事务产品。在阿云里的公有云、专有云解决方案中,开始服务于众多外部客户。2019 年起,基于 TXC 和 GTS 的技术积累,阿里中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一起建设这个分布式事务解决方案。2019 年 fescar(全称fast easy commit and rollback) 被重命名为了seata(simple extensiable autonomous transaction architecture)。TXC、GTS、Fescar 以及 seata 一脉相承,为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。Seata最初是阿里巴巴开发的,但现在已经捐赠给了Apache基金会,成为Apache项目的一部分。
- 单个数据库只能对本身进行事务的控制, 而seata是分布式事务框架,可以对多个数据库进行全局的事务控制,保证全局事务的运行正常的框架。
- 当事物发生超时或异常时,seata会对所管理数据库,统一进行数据回滚,保证数据的正确性。
官网及源码
使用
Seata的分布式交易解决方案

四、Seata工作流程简介:
分布式事务的理解:
- 分布式事务是由一批分支事务组成的全局事务,通常分支事务只是本地事务。
- 纵观整个分布式事务的管理,就是全局事务ID的传递和变更,要让开发者无感知。
Seata对分布式事务的协调和控制就是1+3
1+3:1个XID+TC+TM+RM
- 1个XID :XID是全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中。
- TC (Transaction Coordinator) - 事务协调者
- TM (Transaction Manager) - 事务管理器
- RM (Resource Manager) - 资源管理器
- TC(Transaction Coordinator)事务协调器:就是Seata,负责维护全局事务和分支事务的状态,驱动全局事务提交或回滚。
- TM(Transaction Manager) 事务管理器:标注全局@GlobalTransactional启动入口动作的微服务模块(比如订单模块),它是事务的发起者,负责定义全局事务的范围,并根据TC维护的全局事务和分支事务状态,做出开始事务、提交事务、回滚事务的决议。
- RM(Resource Manager)资源管理器:就是mysql数据库本身,可以是多个RM,负责管理分支事务上的资源,像TC注册分支事务,汇报分支事务状态,驱动分支事务的提交和回滚。

分布式事务的执行流程-总结:(重点)

@GlobalTransactional
public void doBusiness() {
// 本地事务
serviceA.doSomething();
// 本地事务
serviceB.doSomethingElse();
}
@GlobalTransactional
public void globalMethod() {
// 调用另一个标记为@GlobalTransactional的方法
nestedMethod();
// 业务逻辑...
}
@GlobalTransactional
public void nestedMethod() {
// 业务逻辑...
}
五、各事务模式(重点)
- Seata AT 模式
- Seata TCC 模式
- Seata Saga 模式
- Seata XA 模式
Seata AT 模式(常用)
AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。
适用场景:
- 适用场景:主要用于CRUD(创建、读取、更新、删除)操作较多的业务场景,尤其是当业务逻辑直接操作数据库,并且可以容忍短暂的数据不一致时。AT模式通过记录数据的前后镜像来实现撤销(回滚)操作,适合于大部分单纯依赖于单个数据库事务的微服务场景。
- 优点:简单易用,不需要改动业务代码,自动完成分布式事务的提交和回滚。
- 缺点:不适合跨多种存储资源的事务,且在高并发场景下性能可能受影响。
前提
- 基于支持本地 ACID 事务的关系型数据库。
- Java 应用,通过 JDBC 访问数据库。
整体机制
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
整体机制的理解
一阶段过程;
1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,2 执行“业务 SQL”更新业务数据,在业务数据更新之后,3 其保存成“after image”,最后生成行锁。

二阶段异常回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用undo_log中的“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据, 如果不一致就说明有脏写,出现脏写就需要转人工处理。

二阶段正常提交:
因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将 一阶段保存的快照数据和行锁删掉,完成数据清理即可。

写隔离
- 一阶段本地事务提交前,需要确保先拿到 全局锁 。
- 拿不到 全局锁 ,不能提交本地事务。
- 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
以一个示例来说明:


读隔离

工作机制示例:
Field | Type | Key |
id
|
bigint(20)
|
PRI
|
name
|
varchar(100)
| |
since
|
varchar(100)
|
update product set name = 'GTS' where name = 'TXC';
一阶段
- 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
select id, name, since from product where name = 'TXC';
id
|
name
|
since
|
1
|
TXC
|
2014
|
3.执行业务 SQL:更新这条记录的 name 为 'GTS'。
4.查询后镜像:根据前镜像的结果,通过 主键 定位数据。
select id, name, since from product where id = 1;
id
|
name
|
since
|
1
|
GTS
|
2014
|
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。
二阶段-回滚
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。
二阶段-提交
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
回滚日志表
Field
|
Type
|
branch_id
|
bigint PK
|
xid
|
varchar(100)
|
context
|
varchar(128)
|
rollback_info
|
longblob
|
log_status
|
tinyint
|
log_created
|
datetime
|
log_modified
|
datetime
|
-- 注意此处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;
Seata TCC 模式
TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。
适用场景:
- 适用场景:适用于需要显式控制事务边界的复杂业务流程,特别是在业务操作可以明确分为尝试(Try)、确认(Confirm)和取消(Cancel)三个阶段的情况下。TCC模式适合于执行时间较长,需要人工干预或第三方服务参与的分布式事务。
- 优点:灵活性高,可以精细控制事务的每个阶段,适用于复杂的业务逻辑。
- 缺点:需要用户显式地实现Try、Confirm、Cancel三个操作,增加了开发的复杂度。
Seata Saga 模式
Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
适用场景:
- 适用场景:适用于长事务场景,其中业务流程包含一系列的本地事务,这些本地事务需要按照一定的顺序执行。SAGA模式通过定义一系列的事务步骤和相对应的补偿操作(回滚操作)来管理事务,适合于微服务架构下的复杂业务流程。
- 优点:适合长事务处理,可以保证分布式事务的最终一致性。
- 缺点:需要定义每个步骤的补偿操作,对业务侵入性较高。
Seata XA 模式
XA 模式是从 1.2 版本支持的事务模式。XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。
适用场景:
- 适用场景:适用于长事务场景,其中业务流程包含一系列的本地事务,这些本地事务需要按照一定的顺序执行。SAGA模式通过定义一系列的事务步骤和相对应的补偿操作(回滚操作)来管理事务,适合于微服务架构下的复杂业务流程。
- 优点:适合长事务处理,可以保证分布式事务的最终一致性。
- 缺点:需要定义每个步骤的补偿操作,对业务侵入性较高。
六、Seata-Server2.0.0安装
1.下载地址2.下载版本3.各种seata参数官网参考4.Seata新手部署指南5.mysql8.0数据库里面建库+建表6.更改配置7.先启动Nacos2.2.3端口号88488.再启动seata-server-2.0.0
1.下载地址
Seata Java Download | Apache Seata
2.下载版本
Release v2.0.0(Not Apache release) · apache/incubator-seata · GitHub

3.各种seata参数官网参考
参数配置 | Apache Seata
关注属性(详细描述见全属性)
server 端
|
client 端
|
registry.type
|
registry.type
|
config.type
|
config.type
|
#store.mode=db 需要以下配置
|
service.vgroupMapping.my_test_tx_group
|
store.db.driverClassName
|
service.default.grouplist
|
store.db.url
|
service.disableGlobalTransaction
|
store.db.user
| |
store.db.password
| |
#store.mode=redis 需要以下配置
| |
store.redis.host
| |
store.redis.port
| |
store.redis.database
| |
store.redis.password
| |
#store.mode=raft 需要以下配置
| |
server.raft.group
| |
server.raft.server-addr
| |
server.raft.snapshot-interval
|
全属性
公共部分
key
|
desc
|
remark
|
change record
|
transport.type
|
socket 通信方式
|
TCP、UNIX_DOMAIN_SOCKET,默认 TCP
| |
transport.server
|
socket 通道类型
|
NIO、NATIVE(根据操作系统类型和 socket 通信方式选择 KQueue 或 Epoll,注意 Windows 只支持 NIO,选择这种方式会抛出异常)
| |
transport.threadFactory.bossThreadSize
|
Netty 通信模型 Boss group 线程数
|
默认 1
| |
transport.threadFactory.workerThreadSize
|
Netty 通信模型 Worker group 线程数
|
可配置线程数或选择特定线程工作模式下的线程数,线程的默认工作模式有 4 种:Auto(2*CPU 核数 + 1)、Pin(CPU 核数,适用于计算密集型任务)、BusyPin(CPU 核数 + 1,适用于计算密集型且内存比较有限的场景)、Default(2*CPU 核数,适用于 IO 密集型任务),默认值为 Default 模式
| |
transport.shutdown.wait
|
服务端 Netty 线程池关闭前等待服务下线时间
|
默认 3 秒
| |
transport.serialization
|
client 和 server 通信编解码方式
|
seata(ByteBuf)、protobuf、kryo、hessian、fst,默认 seata
| |
transport.compressor
|
client 和 server 通信数据压缩方式
|
none、gzip、zip、sevenz、bzip2、lz4、deflater、zstd,默认 none
|
1.2.0 之前:gzip
1.2.0:zip、sevenz、bzip2
1.3.0:lz4
1.4.1:deflater
1.5.1:zstd
|
transport.heartbeat
|
client 和 server 通信心跳检测开关
|
默认 true 开启
| |
registry.type
|
注册中心类型
|
默认 file,支持 file 、nacos 、redis、eureka、zk、consul、etcd3、sofa、custom
|
1.6.0 版本 Sever 端支持可同时注册到多个注册中心,以逗号分隔注册中心名
|
config.type
|
配置中心类型
|
默认 file,支持 file、nacos 、apollo、zk、consul、etcd3、springcloud、custom
|
server 端
key
|
desc
|
remark
|
change record
|
transport.enableTcServerBatchSendResponse
|
TC 批量发送回复消息开关
|
默认 false
|
1.5.1 版本新增,建议为 true,可解决 client 批量消息时的线头阻塞问题
|
transport.rpcRmRequestTimeout
|
RM 二阶段下发请求超时时间
|
默认 15 秒
| |
transport.rpcTmRequestTimeout
|
TM 二阶段下发请求超时时间
|
默认 30 秒
| |
transport.rpcTcRequestTimeout
|
TC 二阶段下发请求超时时间
|
默认 15 秒
|
1.5.1 版本新增
|
server.undo.logSaveDays
|
undo 保留天数
|
默认 7 天,log_status=1(附录 3)和未正常清理的 undo
| |
server.undo.logDeletePeriod
|
undo 清理线程间隔时间
|
默认 86400000,单位毫秒
| |
server.maxCommitRetryTimeout
|
二阶段提交重试超时时长
|
单位 ms,s,m,h,d,对应毫秒,秒,分,小时,天,默认毫秒。默认值-1 表示无限重试。公式: timeout>=now-globalTransactionBeginTime,true 表示超时则不再重试(注: 达到超时时间后将不会做任何重试,有数据不一致风险,除非业务自行可校准数据,否者慎用)
| |
server.maxRollbackRetryTimeout
|
二阶段回滚重试超时时长
|
同 commit
| |
server.recovery.committingRetryPeriod
|
二阶段提交未完成状态全局事务重试提交线程间隔时间
|
默认 1000,单位毫秒
| |
server.recovery.asynCommittingRetryPeriod
|
二阶段异步提交状态重试提交线程间隔时间
|
默认 1000,单位毫秒
| |
server.recovery.rollbackingRetryPeriod
|
二阶段回滚状态重试回滚线程间隔时间
|
默认 1000,单位毫秒
| |
server.recovery.timeoutRetryPeriod
|
超时状态检测重试线程间隔时间
|
默认 1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器
| |
server.rollbackRetryTimeoutUnlockEnable
|
二阶段回滚超时后是否释放锁
|
默认 false
| |
server.distributedLockExpireTime
|
Sever 端事务管理全局锁超时时间
|
默认 10000,单位毫秒
|
1.5.1 版本新增
|
server.server.xaerNotaRetryTimeout
|
防止 XA 分支事务悬挂的重试超时时间
|
默认 60000,单位毫秒
|
1.5.1 版本新增
|
server.session.branchAsyncQueueSize
|
分支事务 Session 异步删除线程池队列大小
|
默认 5000
|
1.5.1 版本新增
|
server.session.enableBranchAsyncRemove
|
分支事务 Session 异步删除开关
|
默认 false
|
1.5.1 版本新增
|
server.enableParallelRequestHandle
|
对于批量请求消息的并行处理开关
|
默认 true
|
1.5.2 版本新增
|
server.enableParallelHandleBranch
|
二阶段并行下发开关
|
默认 false
|
2.0.0 版本新增
|
server.applicationDataLimitCheck
|
是否开启应用数据大小检查
|
默认 false
| |
server.applicationDataLimit
|
应用数据大小限制
|
默认 64000
| |
server.raft.group
|
raft 存储模式下的 group,client 的事务分组对应的值要与之对应,如 service.vgroup-mapping.default_tx_group=default
|
default
|
2.0.0 版本新增
|
server.raft.server-addr
|
raft 集群列表如 192.168.0.111:9091,192.168.0.112:9091,192.168.0.113:9091
|
2.0.0 版本新增
| |
server.raft.snapshot-interval
|
间隔多久做一次内存快照,每做一次快照将暂停状态机,但是能提高停机恢复速度
|
默认 600 秒
|
2.0.0 版本新增
|
server.raft.apply-batch
|
任务累积批次后提交至 leader
|
默认 32
|
2.0.0 版本新增
|
server.raft.max-append-bufferSize
|
raft 日志存储缓冲区最大大小
|
默认 256K
|
2.0.0 版本新增
|
server.raft.max-replicator-inflight-msgs
|
在启用 pipeline 请求情况下,最大 in-flight 请求数
|
默认 256
|
2.0.0 版本新增
|
server.raft.disruptor-buffer-size
|
内部 disruptor buffer 大小,如果是写入吞吐量较高场景,需要适当调高该值,
|
默认 16384
|
2.0.0 版本新增
|
server.raft.election-timeout-ms
|
超过多久没有 leader 的心跳开始重选举
|
默认 1000 毫秒
|
2.0.0 版本新增
|
server.raft.reporter-enabled
|
raft 自身的监控是否开启
|
默认 false
|
2.0.0 版本新增
|
server.raft.reporter-initial-delay
|
监控输出间隔
|
默认 60 秒
|
2.0.0 版本新增
|
server.raft.serialization
|
序列化方式,目前仅支持 jackson
|
默认 jackson
|
2.0.0 版本新增
|
server.raft.compressor
|
raftlog 和 snapshot 的压缩方式,支持 gzip, zstd, lz4
|
none
|
2.0.0 版本新增
|
server.raft.sync
|
raftlog 同步刷盘
|
true
|
2.0.0 版本新增
|
store.mode
|
事务会话信息存储方式
|
file 本地文件(不支持 HA),db 数据库、redis、raft 支持 HA
|
1.5.1 版本改用 lock 和 session 分离存储,2.0.0 开始支持 raft 模式
|
store.lock.mode
|
事务锁信息存储方式
|
file 本地文件(不支持 HA),db 数据库,redis(支持 HA);配置为空时,取 store.mode 配置项值,raft 模式不允许指定
|
1.5.1 版本新增,session 和 lock 可分离存储
|
store.session.mode
|
事务回话信息存储方式
|
file 本地文件(不支持 HA),db 数据库,redis(支持 HA);配置为空时,取 store.mode 配置项值。raft 模式不允许单独指定
|
1.5.1 版本新增,session 和 lock 可分离存储
|
store.publicKey
|
db 或 redis 存储密码解密公钥
|
1.4.2 版本支持
| |
store.file.dir
|
file 模式文件存储文件夹名
|
默认 sessionStore
| |
store.file.maxBranchSessionSize
|
file 模式文件存储分支 session 最大字节数
|
默认 16384(16kb),单位 byte
| |
store.file.maxGlobalSessionSize
|
file 模式文件存储全局 session 最大字节数
|
默认 512b,单位 byte
| |
store.file.fileWriteBufferCacheSize
|
file 模式文件存储 buffer 最大缓存大小
|
默认 16384(16kb),单位 byte,写入 session 等数据量大于该值时会抛出异常
| |
store.file.flushDiskMode
|
file 模式文件存储刷盘策略
|
默认 async,可选 sync
| |
store.file.sessionReloadReadSize
|
file 模式文件存储 Server 节点重启后从备份文件中恢复的 session 或 lock key 上限个数
|
默认 100
| |
store.db.datasource
|
db 模式数据源类型
|
dbcp、druid、hikari;无默认值,store.mode=db 时必须指定
| |
store.db.dbType
|
db 模式数据库类型
|
mysql、oracle、db2、sqlserver、sybaee、h2、sqlite、access、postgresql、oceanbase;无默认值,store.mode=db 时必须指定。
| |
store.db.driverClassName
|
db 模式数据库驱动
|
store.mode=db 时必须指定
| |
store.db.url
|
db 模式数据库 url
|
store.mode=db 时必须指定,在使用 mysql 作为数据源时,建议在连接参数中加上
rewriteBatchedStatements=true
(详细原因请阅读附录 7)
| |
store.db.user
|
db 模式数据库账户
|
store.mode=db 时必须指定
| |
store.db.password
|
db 模式数据库账户密码
|
store.mode=db 时必须指定
| |
store.db.minConn
|
db 模式数据库初始连接数
|
默认 1
| |
store.db.maxConn
|
db 模式数据库最大连接数
|
默认 20
| |
store.db.maxWait
|
db 模式获取连接时最大等待时间
|
默认 5000,单位毫秒
| |
store.db.globalTable
|
db 模式全局事务表名
|
默认 global_table
| |
store.db.branchTable
|
db 模式分支事务表名
|
默认 branch_table
| |
store.db.lockTable
|
db 模式全局锁表名
|
默认 lock_table
| |
store.db.queryLimit
|
db 模式查询全局事务一次的最大条数
|
默认 100
| |
store.db.distributedLockTable
|
db 模式 Sever 端事务管理全局锁存储表名
|
默认 distributed_lock,多 Sever 集群下保证同时只有一个 Sever 处理提交或回滚
|
1.5.1 版本新增
|
store.redis.mode
|
redis 模式
|
默认 single,可选 sentinel
|
1.4.2 版本新增 sentinel 模式
|
store.redis.single.host
|
单机模式下 redis 的 host,兼容 1.4.2 之前的版本,该配置为空时选取 store.redis.host 作为配置项
|
1.4.2 版本新增
| |
store.redis.single.port
|
单机模式下 redis 的 port,兼容 1.4.2 之前的版本,该配置为空时选取 store.redis.port 作为配置项
|
1.4.2 版本新增
| |
store.redis.sentinel.masterName
|
sentinel 模式下 redis 的主库名称
|
1.4.2 版本新增
| |
store.redis.sentinel.sentinelHosts
|
sentinel 模式下 sentinel 的 hosts
|
多 hosts 以逗号分隔
|
1.4.2 版本新增
|
store.redis.host
|
redis 模式 ip
|
默认 127.0.0.1
|
1.4.2 版本弃用
|
store.redis.port
|
redis 模式端口
|
默认 6379
|
1.4.2 版本弃用
|
store.redis.maxConn
|
redis 模式最大连接数
|
默认 10
| |
store.redis.minConn
|
redis 模式最小连接数
|
默认 1
| |
store.redis.database
|
redis 模式默认库
|
默认 0
| |
store.redis.password
|
redis 模式密码(无可不填)
|
默认 null
| |
store.redis.queryLimit
|
redis 模式一次查询最大条数
|
默认 100
| |
store.redis.type
|
redis 模式主要使用的方式: lua, pippline
|
pippline
| |
metrics.enabled
|
是否启用 Metrics
|
默认 true 开启,在 False 状态下,所有与 Metrics 相关的组件将不会被初始化,使得性能损耗最低
| |
metrics.registryType
|
指标注册器类型
|
Metrics 使用的指标注册器类型,默认为内置的 compact(简易)实现,这个实现中的 Meter 仅使用有限内存计数,性能高足够满足大多数场景;目前只能设置一个指标注册器实现
| |
metrics.exporterList
|
指标结果 Measurement 数据输出器列表
|
默认 prometheus,多个输出器使用英文逗号分割,例如"prometheus,jmx",目前仅实现了对接 prometheus 的输出器
| |
metrics.exporterPrometheusPort
|
prometheus 输出器 Client 端口号
|
默认 9898
|
client 端
key
|
desc
|
remark
|
change record
|
seata.enabled
|
是否开启 spring-boot 自动装配
|
true、false,(SSBS)专有配置,默认 true(附录 4)
| |
seata.enableAutoDataSourceProxy=true
|
是否开启数据源自动代理
|
true、false,seata-spring-boot-starter(SSBS)专有配置,SSBS 默认会开启数据源自动代理,可通过该配置项关闭.
| |
seata.useJdkProxy=false
|
是否使用 JDK 代理作为数据源自动代理的实现方式
|
true、false,(SSBS)专有配置,默认 false,采用 CGLIB 作为数据源自动代理的实现方式
| |
transport.enableClientBatchSendRequest
|
客户端事务消息请求是否批量合并发送
|
默认 true,false 单条发送
| |
transport.enableTmClientChannelCheckFailFast
|
客户端 TM 快速失败检查
|
默认 true,false 不检测
| |
transport.enableRmClientChannelCheckFailFast
|
客户端 RM 快速失败检查
|
默认 true,false 不检测
| |
client.log.exceptionRate
|
日志异常输出概率
|
默认 100,目前用于 undo 回滚失败时异常堆栈输出,百分之一的概率输出,回滚失败基本是脏数据,无需输出堆栈占用硬盘空间
| |
service.vgroupMapping.my_test_tx_group
|
事务群组(附录 1)
|
my_test_tx_group 为分组,配置项值为 TC 集群名
| |
service.default.grouplist
|
TC 服务列表(附录 2)
|
仅注册中心为 file 时使用
| |
service.disableGlobalTransaction
|
全局事务开关
|
默认 false。false 为开启,true 为关闭
| |
client.tm.degradeCheck
|
降级开关
|
默认 false。业务侧根据连续错误数自动降级不走 seata 事务(详细介绍请阅读附录 6)
| |
client.tm.degradeCheckAllowTimes
|
升降级达标阈值
|
默认 10
| |
client.tm.degradeCheckPeriod
|
服务自检周期
|
默认 2000,单位 ms.每 2 秒进行一次服务自检,来决定
| |
client.rm.reportSuccessEnable
|
是否上报一阶段成功
|
true、false,从 1.1.0 版本开始,默认 false.true 用于保持分支事务生命周期记录完整,false 可提高不少性能
| |
client.rm.asyncCommitBufferLimit
|
异步提交缓存队列长度
|
默认 10000。 二阶段提交成功,RM 异步清理 undo 队列
| |
client.rm.lock.retryInterval
|
校验或占用全局锁重试间隔
|
默认 10,单位毫秒
| |
client.rm.lock.retryTimes
|
校验或占用全局锁重试次数
|
默认 30
| |
client.rm.lock.retryPolicyBranchRollbackOnConflict
|
分支事务与其它全局回滚事务冲突时锁策略
|
默认 true,优先释放本地锁让回滚成功
| |
client.rm.reportRetryCount
|
一阶段结果上报 TC 重试次数
|
默认 5 次
|
1.4.1 版本新增
|
client.rm.tableMetaCheckEnable
|
自动刷新缓存中的表结构
|
默认 false
|
1.5.1 版本新增
|
client.rm.tableMetaCheckerInterval
|
定时刷新缓存中表结构间隔时间
|
默认 60 秒
| |
client.rm.sagaBranchRegisterEnable
|
是否开启 saga 分支注册
|
Saga 模式中分支状态存储在状态机本地数据库中,可通过状态机进行提交或回滚,为提高性能可考虑不用向 TC 注册 Saga 分支,但需考虑状态机的可用性,默认 false
| |
client.rm.sagaJsonParser
|
saga 模式中数据序列化方式
|
默认 fastjson,可选 jackson
|
1.5.1 版本新增
|
client.rm.tccActionInterceptorOrder
|
tcc 拦截器顺序
|
默认 Ordered.HIGHEST_PRECEDENCE + 1000,保证拦截器在本地事务拦截器之前执行,也可自定义 tcc 和业务开发的拦截器执行顺序
|
1.5.1 版本新增
|
client.rm.applicationDataLimitCheck
|
客户端应用数据是否开启限制
|
默认 false
| |
client.rm.applicationDataLimit
|
客户端应用数据上报限制
|
默认 64000
| |
client.tm.commitRetryCount
|
一阶段全局提交结果上报 TC 重试次数
|
默认 1 次,建议大于 1
| |
client.tm.rollbackRetryCount
|
一阶段全局回滚结果上报 TC 重试次数
|
默认 1 次,建议大于 1
| |
client.tm.defaultGlobalTransactionTimeout
|
全局事务超时时间
|
默认 60 秒,TM 检测到分支事务超时或 TC 检测到 TM 未做二阶段上报超时后,发起对分支事务的回滚
|
1.4.0 版本新增
|
client.tm.interceptorOrder
|
TM 全局事务拦截器顺序
|
默认 Ordered.HIGHEST_PRECEDENCE + 1000,保证拦截器在本地事务拦截器之前执行,也可自定义全局事务和业务开发的拦截器执行顺序
|
1.5.1 版本新增
|
client.undo.dataValidation
|
二阶段回滚镜像校验
|
默认 true 开启,false 关闭
| |
client.undo.logSerialization
|
undo 序列化方式
|
默认 jackson
| |
client.undo.logTable
|
自定义 undo 表名
|
默认 undo_log
| |
client.undo.onlyCareUpdateColumns
|
只生成被更新列的镜像
|
默认 true
| |
client.undo.compress.enable
|
undo log 压缩开关
|
默认 true
|
1.4.1 版本新增
|
client.undo.compress.type
|
undo log 压缩算法
|
默认 zip,可选 NONE(不压缩)、GZIP、ZIP、SEVENZ、BZIP2、LZ4、DEFLATER、ZSTD
|
1.4.1 版本新增
|
client.undo.compress.threshold
|
undo log 压缩阈值
|
默认值 64k,压缩开关开启且 undo log 大小超过阈值时才进行压缩
|
1.4.1 版本新增
|
client.rm.sqlParserType
|
sql 解析类型
|
默认 druid,可选 antlr
|
4.Seata新手部署指南
- client
存放client端sql脚本 (包含 undo_log表) ,参数配置
- config-center
各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件
- server
数据库官网地址: https://github.com/apache/incubator-seata/tree/2.x/script

5.mysql8.0数据库里面建库+建表
使用seata前,需根据官网要求创建默认库、表
建库—创建seata库
create database seata;
use seata;
建表—在seata库创建表
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
运行结果

6.更改配置
参考模版配置:application.example.yml
seata支持的参数配置:nacos, consul, apollo, zk, etcd3seata支持的注册中心:support: nacos, eureka, redis, zk, consul, etcd3, sofaseata存储方式:file 、 db 、 redis 、 raftsecurity.ignore忽略的静态资源: 指定后缀 css、js、png、jpeg等
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: file
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: file
store:
# support: file 、 db 、 redis 、 raft
mode: file
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${log.home:${user.home}/logs/seata}
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP #后续自己在nacos里面新建,不想新建SEATA_GROUP,就写DEFAULT_GROUP
username: nacos
password: nacos
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP #后续自己在nacos里面新建,不想新建SEATA_GROUP,就写DEFAULT_GROUP
namespace:
cluster: default
username: nacos
password: nacos
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
user: root
password: 123isok
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
7.先启动Nacos2.2.3端口号8848
在nacos-server2.2.3\bin下执行cmd启动命令:startup.cmd -m standalone访问地址: http://localhost:8848/nacos默认账号密码:nacos
8.再启动seata-server-2.0.0
在seata-server-2.0.0\bin下执行cmd启动命令: seata-server.bat访问地址: http://localhost:7091默认账号密码:seata


七、Seata案例实战-数据库和表准备(AT)
备注:需先启动nacos后启动seata成功
分布式事务案例—业务说明
这里我们创建三个服务,一个订单服务,一个库存服务,一个账户服务。当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

1.创建3个业务数据库DATABASE
- seata order:存储订单的数据库;
- seata storage:存储库存的数据库;
- seata account:存储账户信息的数据库;
建库SQL
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;
2. 按照上述3库分别建对应的undo log回滚日志表
订单-库存-账户3个库下都需要建各自的undo log回滚日志表
undo_log建表sql
AT模式专用,其他模式不需要
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

3.按照上述3库分别建对应业务表
- t_order脚本SQL
- t_account脚本SQL
- t_storage脚本SQL
t_order脚本SQL
CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`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 '订单状态: 0:创建中; 1:已完结'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
SELECT * FROM t_order;
t_account脚本SQL
CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY 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 '剩余可用额度'
)ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO t_account(`id`,`user_id`,`total`,`used`,`residue`)VALUES('1','1','1000','0','1000');
SELECT * FROM t_account;
t_storage脚本SQL
CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`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 '剩余库存'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_storage(`id`,`product_id`,`total`,`used`,`residue`)VALUES('1','1','100','0','100');
SELECT * FROM t_storage;
最终SQL(全)
建seata_order库+建t_order表+undo_log表建seata_storage库+建t_storage 表+undo_log表建seata_account库+建t_account 表+undo_log表
建seata_order库+建t_order表+undo_log表
#order
CREATE DATABASE seata_order;
USE seata_order;
CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`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 '订单状态: 0:创建中; 1:已完结'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
SELECT * FROM t_order;
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
建seata_storage库+建t_storage 表+undo_log表
#storage
CREATE DATABASE seata_storage;
USE seata_storage;
CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`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 '剩余库存'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_storage(`id`,`product_id`,`total`,`used`,`residue`)VALUES('1','1','100','0','100');
SELECT * FROM t_storage;
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
建seata_account库+建t_account 表+undo_log表
#account
create database seata_account;
use seata_account;
CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY 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 '剩余可用额度'
)ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO t_account(`id`,`user_id`,`total`,`used`,`residue`)VALUES('1','1','1000','0','1000');
SELECT * FROM t_account;
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
最终效果:

八、Seata系例实战-微服务编码落地实现(AT)
业务需求1.Mybaits一键生成2.修改公共cloud-api-commons新增库存和账户两个Feign服务接口3.新建订单Order微服务4.新建库存Storage微服务5.新建账户Account微服务
1.Mybaits一键生成
利用mybaitis 根据表生成对象实体类、mapper、mapper.xml
config.properties
分别执行seata_order、seata_storage、seata_account
# seata_order
#jdbc.driverClass = com.mysql.cj.jdbc.Driver
#jdbc.url = jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#jdbc.user = root
#jdbc.password =123456
# seata_storage
#jdbc.driverClass = com.mysql.cj.jdbc.Driver
#jdbc.url = jdbc:mysql://localhost:3306/seata_storage?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#jdbc.user = root
#jdbc.password =123456
# seata_account
#jdbc.driverClass = com.mysql.cj.jdbc.Driver
#jdbc.url = jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#jdbc.user = root
#jdbc.password =123456
generatorConfig.xml
分别进行生成 t_orde、 t_storage、t_account
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<properties resource="config.properties"/>
<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
<property name="caseSensitive" value="true"/>
</plugin>
<jdbcConnection driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.url}"
userId="${jdbc.user}"
password="${jdbc.password}">
</jdbcConnection>
<javaModelGenerator targetPackage="${package.name}.entities" targetProject="src/main/java"/>
<sqlMapGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java"/>
<javaClientGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java" type="XMLMAPPER"/>
<table tableName="t_pay" domainObjectName="Pay">
<generatedKey column="id" sqlStatement="JDBC"/>
</table>
<!-- seata_order -->
<!--<table tableName="t_order" domainObjectName="Order">
<generatedKey column="id" sqlStatement="JDBC"/>
</table>-->
<!--seata_storage-->
<!--<table tableName="t_storage" domainObjectName="Storage">
<generatedKey column="id" sqlStatement="JDBC"/>
</table>-->
<!--seata_account-->
<!--<table tableName="t_account" domainObjectName="Account">
<generatedKey column="id" sqlStatement="JDBC"/>
</table>-->
</context>
</generatorConfiguration>
双击执行一键生成

2.修改公共cloud-api-commons新增库存和账户两个Feign服务接口
package com.atguigu.cloud.apis;
import com.atguigu.cloud.resp.ResultData;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "seata-storage-service")
public interface StorageFeignApi
{
//扣减库存
@PostMapping(value = "/storage/decrease")
ResultData decrease(@RequestParam("productId")Long productId,@RequestParam("count") Integer count);
}
package com.atguigu.cloud.apis;
import com.atguigu.cloud.resp.ResultData;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "seata-account-service")
public interface AccountFeignApi
{
//扣减账号余额
@PostMapping("/account/decrease")
ResultData decrease(@RequestParam("userId")Long userId,@RequestParam("money") Long money);
}
3.新建订单Order微服务
(1).建Module(2).改POM(3).写YML(4).主启动类(5).业务类
(1).建Module
新建子模块 seata-order-service2001
(2).改POM
主要依赖是:nacos、seata、openfeign、loadbalancer、以及(cloud-api-commons)公共类的依赖 ,剩余的为springboot基础依赖、druid连接池等。
<?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>cloud2024</artifactId>
<groupId>com.atguigu.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-order-service2001</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- nacos 服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--alibaba-seata 分布式事务-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--openfeign 模块调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--loadbalancer 需要支持负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--cloud-api-commons 公共类-->
<dependency>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--SpringBoot集成druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!--mybatis和springboot整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--Mysql数据库驱动8 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
<!--通用Mapper4-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(3).写YML
默认配置、nacos配置、数据库配置、mybatis、seata、日志配置
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务注册中心地址
# ==========applicationName + druid-mysql8 driver===================
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123isok
# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.cloud.entities
configuration:
map-underscore-to-camel-case: true #可以将数据库中的带下划线字段映射到实体类的驼峰命名属性上。
# ========================seata===================
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
application: seata-server
tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,可多个
service:
vgroup-mapping:
default_tx_group: default # 事务组与TC服务集群的映射关系 default_tx_group要与tx-service-group参数一致
data-source-proxy-mode: AT #可写可不写默认就是AT模式
logging:
level:
io:
seata: info


public void afterPropertiesSet() throws Exception {
if (0 == this.vgroupMapping.size()) {
this.vgroupMapping.put("default_tx_group", "default");
this.vgroupMapping.put("my_test_tx_group", "default");
}
if (0 == this.grouplist.size()) {
this.grouplist.put("default", "127.0.0.1:8091");
}
}
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
application: seata-server
tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,可多个
service:
vgroup-mapping:
default_tx_group: default # 事务组与TC服务集群的映射关系 default_tx_group要与tx-service-group参数一致
data-source-proxy-mode: AT #可写可不写默认就是AT模式
详细过度版(了解即可,太详细也不好维护)
#seata:
# registry: # seata注册配置
# type: nacos # seata注册类型
# nacos:
# application: seata-server #seata应用名称
# server-addr: 127.0.0.1:8848
# namespace: ""
# group: SEATA_GROUP
# cluster: default
# config: # seata配置抓取
# nacos:
# server-addr: 127.0.0.1:8848
# namespace: ""
# group: SEATA_GROUP
# username: nacos
# password: nacos
# tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称
# service:
# vgroup-mapping:
# default_tx_group: default # 事务群组的映射配置关系
# data-source-proxy-mode: AT
# application-id: seata-server
(4).主启动类
package com.atguigu.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.atguigu.cloud.mapper")//import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient//服务器注册与发现
@EnableFeignClients//注解添加到主类或配置类上,可以启用Feign客户端的自动发现和创建。Spring Cloud会自动扫描指定包及其子包中的所有标记了@FeignClient的接口,并为每个接口创建一个动态代理实现,该实现会根据配置的参数和方法调用,自动构造HTTP请求
public class SeataOrderMainApp2001
{
public static void main(String[] args) {
SpringApplication.run(SeataOrderMainApp2001.class,args);
}
}
SpringBootApplication是一个组合注解,主要用于标记Spring Boot的主配置类,用于启动Spring Boot应用程序。它由以下三个注解组成:
@SpringBootConfiguration:这个注解相当于@Configuration,表明该类是一个配置类,用于定义Bean和配置Spring容器 12。@EnableAutoConfiguration:开启自动配置功能,根据项目中的依赖自动配置Spring应用,如数据库连接、Web服务器等,减少手动配置的工作量 12。 @ComponentScan:组件扫描器,自动扫描当前包及其子包中带有特定注解(如@Component、@Service、@Repository、@Controller等)的类,并将其注册为Spring Bean
(5).业务类
entitiesOrderMapperService接口及实现Controller
1).entities
Order实体类实现Serializable接口添加注解@ToString
package com.atguigu.cloud.entities;
import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;
/**
* 表名:t_order
*/
@Table(name = "t_order")
@ToString
public class Order implements Serializable {
@Id
@GeneratedValue(generator = "JDBC")
private Long id;
/**
* 用户id
*/
@Column(name = "user_id")
private Long userId;
/**
* 产品id
*/
@Column(name = "product_id")
private Long productId;
/**
* 数量
*/
private Integer count;
/**
* 金额
*/
private Long money;
/**
* 订单状态: 0:创建中; 1:已完结
*/
private Integer status;
/**
* @return id
*/
public Long getId() {
return id;
}
/**
* @param id
*/
public void setId(Long id) {
this.id = id;
}
/**
* 获取用户id
*
* @return userId - 用户id
*/
public Long getUserId() {
return userId;
}
/**
* 设置用户id
*
* @param userId 用户id
*/
public void setUserId(Long userId) {
this.userId = userId;
}
/**
* 获取产品id
*
* @return productId - 产品id
*/
public Long getProductId() {
return productId;
}
/**
* 设置产品id
*
* @param productId 产品id
*/
public void setProductId(Long productId) {
this.productId = productId;
}
/**
* 获取数量
*
* @return count - 数量
*/
public Integer getCount() {
return count;
}
/**
* 设置数量
*
* @param count 数量
*/
public void setCount(Integer count) {
this.count = count;
}
/**
* 获取金额
*
* @return money - 金额
*/
public Long getMoney() {
return money;
}
/**
* 设置金额
*
* @param money 金额
*/
public void setMoney(Long money) {
this.money = money;
}
/**
* 获取订单状态: 0:创建中; 1:已完结
*
* @return status - 订单状态: 0:创建中; 1:已完结
*/
public Integer getStatus() {
return status;
}
/**
* 设置订单状态: 0:创建中; 1:已完结
*
* @param status 订单状态: 0:创建中; 1:已完结
*/
public void setStatus(Integer status) {
this.status = status;
}
}
2).OrderMapper
package com.atguigu.cloud.service;
import com.atguigu.cloud.entities.Order;
import tk.mybatis.mapper.common.Mapper;
public interface OrderMapper extends Mapper<Order> {
}
resources文件夹下新建mapper文件夹后添加
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.atguigu.cloud.mapper.OrderMapper">
<resultMap id="BaseResultMap" type="com.atguigu.cloud.entities.Order">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="user_id" jdbcType="BIGINT" property="userId" />
<result column="product_id" jdbcType="BIGINT" property="productId" />
<result column="count" jdbcType="INTEGER" property="count" />
<result column="money" jdbcType="DECIMAL" property="money" />
<result column="status" jdbcType="INTEGER" property="status" />
</resultMap>
</mapper>
3).Service接口及实现
OrderService
package com.atguigu.cloud.service;
import com.atguigu.cloud.entities.Order;
public interface OrderService {
/**
* 创建订单
* @param order
*/
void create(Order order);
}
OrderServiceImpl(重点)
package com.atguigu.cloud.service.impl;
import com.atguigu.cloud.apis.AccountFeignApi;
import com.atguigu.cloud.apis.StorageFeignApi;
import com.atguigu.cloud.entities.Order;
import com.atguigu.cloud.mapper.OrderMapper;
import com.atguigu.cloud.service.OrderService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
/**
* 下订单->减库存->扣余额->改(订单)状态
*/
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private OrderMapper orderMapper;
@Resource//订单微服务通过OpenFeign去调用库存微服务
private StorageFeignApi storageFeignApi;
@Resource//订单微服务通过OpenFeign去调用账户微服务
private AccountFeignApi accountFeignApi;
@Override
@GlobalTransactional(name = "zzyy-create-order",rollbackFor = Exception.class)//AT
// @GlobalTransactional@Transactional(rollbackFor = Exception.class)//XA
public void create(Order order) {
//xid 全局事务id的检查
String xid = RootContext.getXID();
//1.新建订单
log.info("===================================开始新建订单 xid_order:"+xid);
//订单状态status 0:创建中;1:已完结
order.setStatus(0);
int result = orderMapper.insert(order);
//插入订单成功后获得插入mysql的实体对象
Order orderFromDB=null;
if(result>0)
{
//从mysql查询刚插入的记录
orderFromDB = orderMapper.selectOne(order);
//orderFromDB = orderMapper.selectByPrimaryKey(order.getId());
log.info("-------->新建订单成功,orderFromDB info:"+orderFromDB);
System.out.println();
//2.扣减库存
log.info("--------->订单微服务开始调用Stroage库存,做抵扣count");
storageFeignApi.decrease(orderFromDB.getProductId(),orderFromDB.getCount());
log.info("-------->订单微服务结束调用Storage库存,做扣减完成");
System.out.println();
//3.扣减账号余款
log.info("--------->订单为服务器开始调用Account账号,做扣减money");
accountFeignApi.decrease(orderFromDB.getUserId(),orderFromDB.getMoney());
log.info("--------->订单为服务器结束调用Account账号,做扣减完成");
//4.修改订单状态
//订单状态status 0:创建中;1:已完结
log.info("--------->修改订单状态");
orderFromDB.setStatus(1);
//创建查询条件
Example whereCondition = new Example(Order.class);
Example.Criteria criteria = whereCondition.createCriteria();
criteria.andEqualTo("userId",orderFromDB.getUserId());
criteria.andEqualTo("status",0);
//更新操作:根据Example特定的查询条件,找到相应的记录,并基于传入的实体对象来更新记录中的字段值。
int updateResult = orderMapper.updateByExampleSelective(orderFromDB, whereCondition);
log.info("--------->修改订单状态完成 "+updateResult);
log.info("---------> orderFromDB info: "+orderFromDB);
}
System.out.println();
log.info("====================================结束新建订单 xdi_order: "+xid);
}
}
4).Controller
package com.atguigu.cloud.controller;
import com.atguigu.cloud.entities.Order;
import com.atguigu.cloud.resp.ResultData;
import com.atguigu.cloud.service.OrderService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Resource
private OrderService orderService;
/**
* 创建订单
* @param order
* @return
*/
@GetMapping("/order/create")
public ResultData create(Order order)
{
orderService.create(order);
return ResultData.success(order);
}
}
4.新建库存Storage微服务
(1).建Module(2).改POM(3).写YML(4).主启动类(5).业务类
(1).建Module
建子模块 seata-storage-service2002
(2).改POM
主要依赖是: nacos、seata、openfeign、loadbalancer、以及公共类 的依赖,剩余的为springboot基础依赖、druid连接池等。与子模块 seata-order-service2001 pom相同
<?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>cloud2024</artifactId>
<groupId>com.atguigu.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-storage-service2002</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--alibaba-seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--cloud_commons_utils-->
<dependency>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--SpringBoot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!--mybatis和springboot整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--Mysql数据库驱动8 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
<!--通用Mapper4-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(3).写YML
spring:
application:
name: seata-storage-service
datasource:
druid:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_storage?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123isok
server:
port: 2002
spring:
application:
name: seata-storage-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务注册中心地址
# ==========applicationName + druid-mysql8 driver===================
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_storage?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123isok
# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.cloud.entities
configuration:
map-underscore-to-camel-case: true #可以将数据库中的带下划线字段映射到实体类的驼峰命名属性上。
# ========================seata===================
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
application: seata-server
tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,可多个
service:
vgroup-mapping:
default_tx_group: default # 事务组与TC服务集群的映射关系 default_tx_group要与tx-service-group参数一致
data-source-proxy-mode: AT #可写可不写默认就是AT模式
logging:
level:
io:
seata: info
(4).主启动类
package com.atguigu.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.atguigu.cloud.mapper")//import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient//服务器注册与发现
@EnableFeignClients
public class SeataStorageMainApp2002
{
public static void main(String[] args) {
SpringApplication.run(SeataStorageMainApp2002.class,args);
}
}
(5).业务类
通过mybatis_generator2024自动生成实体类entities、storageMapper1).entities2).StorageMapper3).Service接口及实现4).Controller
1).entities
Storage实体类实现Serializable接口添加注解@ToString
package com.atguigu.cloud.entities;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
/**
* 表名:t_storage
*/
@Table(name = "t_storage")
@ToString
public class Storage implements Serializable{
@Id
@GeneratedValue(generator = "JDBC")
private Long id;
/**
* 产品id
*/
@Column(name = "product_id")
private Long productId;
/**
* 总库存
*/
private Integer total;
/**
* 已用库存
*/
private Integer used;
/**
* 剩余库存
*/
private Integer residue;
/**
* @return id
*/
public Long getId() {
return id;
}
/**
* @param id
*/
public void setId(Long id) {
this.id = id;
}
/**
* 获取产品id
*
* @return productId - 产品id
*/
public Long getProductId() {
return productId;
}
/**
* 设置产品id
*
* @param productId 产品id
*/
public void setProductId(Long productId) {
this.productId = productId;
}
/**
* 获取总库存
*
* @return total - 总库存
*/
public Integer getTotal() {
return total;
}
/**
* 设置总库存
*
* @param total 总库存
*/
public void setTotal(Integer total) {
this.total = total;
}
/**
* 获取已用库存
*
* @return used - 已用库存
*/
public Integer getUsed() {
return used;
}
/**
* 设置已用库存
*
* @param used 已用库存
*/
public void setUsed(Integer used) {
this.used = used;
}
/**
* 获取剩余库存
*
* @return residue - 剩余库存
*/
public Integer getResidue() {
return residue;
}
/**
* 设置剩余库存
*
* @param residue 剩余库存
*/
public void setResidue(Integer residue) {
this.residue = residue;
}
}
2).StorageMapper
StorageMapper
package com.atguigu.cloud.mapper;
import com.atguigu.cloud.entities.Storage;
import org.apache.ibatis.annotations.Param;
import tk.mybatis.mapper.common.Mapper;
public interface StorageMapper extends Mapper<Storage> {
/**
* 扣减库存
*/
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.atguigu.cloud.mapper.StorageMapper">
<resultMap id="BaseResultMap" type="com.atguigu.cloud.entities.Storage">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="product_id" jdbcType="BIGINT" property="productId" />
<result column="total" jdbcType="INTEGER" property="total" />
<result column="used" jdbcType="INTEGER" property="used" />
<result column="residue" jdbcType="INTEGER" property="residue" />
</resultMap>
<update id="decrease">
UPDATE
t_storage
SET
used = used + #{count},
residue = residue - #{count}
WHERE product_id = #{productId}
</update>
</mapper>
3).Service接口及实现
package com.atguigu.cloud.service;
public interface StorageService {
/**
* 扣减库存
* @param productId
* @param count
*/
void decrease(Long productId,Integer count);
}
package com.atguigu.cloud.mapper.impl;
import com.atguigu.cloud.mapper.StorageMapper;
import com.atguigu.cloud.service.StorageService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
@Resource
private StorageMapper storageMapper;
@Override
public void decrease(Long productId, Integer count) {
log.info("------->storage-service中扣减库存开始");
storageMapper.decrease(productId,count);
log.info("------->storage-service中扣减库存成功");
}
}
4).Controller
package com.atguigu.cloud.controller;
import com.atguigu.cloud.resp.ResultData;
import com.atguigu.cloud.service.StorageService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StorageController {
@Resource
private StorageService storageService;
@RequestMapping("/storage/decrease")
public ResultData decrease(Long productId,Integer count)
{
storageService.decrease(productId,count);
return ResultData.success("扣减库存成功!");
}
}
5.新建账户Account微服务
(1).建Module(2).改POM(3).写YML(4).主启动类(5).业务类
(1).建Module
建子模块 seata-account-service2003
(2).改POM
主要依赖是:nacos、seata、openfeign、loadbalancer、以及(cloud-api-commons)公共类的依赖,剩余的为springboot基础依赖、druid连接池等。与子模块 seata-order-service2001 pom相同
<?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>cloud2024</artifactId>
<groupId>com.atguigu.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-account-service2003</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--alibaba-seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--cloud_commons_utils-->
<dependency>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--SpringBoot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!--mybatis和springboot整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--Mysql数据库驱动8 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
<!--通用Mapper4-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(3).写YML
spring:
application:
name: seata-account-service
datasource:
druid:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123isok
server:
port: 2003
spring:
application:
name: seata-account-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务注册中心地址
# ==========applicationName + druid-mysql8 driver===================
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123isok
# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.cloud.entities
configuration:
map-underscore-to-camel-case: true #可以将数据库中的带下划线字段映射到实体类的驼峰命名属性上。
# ========================seata===================
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
application: seata-server
tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,可多个
service:
vgroup-mapping:
default_tx_group: default # 事务组与TC服务集群的映射关系 default_tx_group要与tx-service-group参数一致
data-source-proxy-mode: AT #可写可不写默认就是AT模式
logging:
level:
io:
seata: info
(4).主启动类
package com.atguigu.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.atguigu.cloud.mapper") //import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
public class SeataAccountMainApp2003
{
public static void main(String[] args) {
SpringApplication.run(SeataAccountMainApp2003.class,args);
}
}
(5).业务类
通过mybatis_generator2024自动生成实体类entities、accountMapper1).entities2).AccountMapper3).Service接口及实现4).Controller
1).entities
Account实体类
- 实现Serializable接口
- 添加注解@ToString
package com.atguigu.cloud.entities;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
/**
* 表名:t_account
*/
@Table(name = "t_account")
@ToString
public class Account implements Serializable {
/**
* id
*/
@Id
@GeneratedValue(generator = "JDBC")
private Long id;
/**
* 用户id
*/
@Column(name = "user_id")
private Long userId;
/**
* 总额度
*/
private Long total;
/**
* 已用账户余额
*/
private Long used;
/**
* 剩余可用额度
*/
private Long residue;
/**
* 获取id
*
* @return id - id
*/
public Long getId() {
return id;
}
/**
* 设置id
*
* @param id id
*/
public void setId(Long id) {
this.id = id;
}
/**
* 获取用户id
*
* @return userId - 用户id
*/
public Long getUserId() {
return userId;
}
/**
* 设置用户id
*
* @param userId 用户id
*/
public void setUserId(Long userId) {
this.userId = userId;
}
/**
* 获取总额度
*
* @return total - 总额度
*/
public Long getTotal() {
return total;
}
/**
* 设置总额度
*
* @param total 总额度
*/
public void setTotal(Long total) {
this.total = total;
}
/**
* 获取已用账户余额
*
* @return used - 已用账户余额
*/
public Long getUsed() {
return used;
}
/**
* 设置已用账户余额
*
* @param used 已用账户余额
*/
public void setUsed(Long used) {
this.used = used;
}
/**
* 获取剩余可用额度
*
* @return residue - 剩余可用额度
*/
public Long getResidue() {
return residue;
}
/**
* 设置剩余可用额度
*
* @param residue 剩余可用额度
*/
public void setResidue(Long residue) {
this.residue = residue;
}
}
2).AccountMapper
package com.atguigu.cloud.mapper;
import com.atguigu.cloud.entities.Account;
import org.apache.ibatis.annotations.Param;
import tk.mybatis.mapper.common.Mapper;
public interface AccountMapper extends Mapper<Account> {
/**
* 本次消费金额
*/
void decrease(@Param("userId")Long userId,@Param("money") Long money);
}
resources文件夹下新建mapper文件夹后添加
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.atguigu.cloud.mapper.AccountMapper">
<resultMap id="BaseResultMap" type="com.atguigu.cloud.entities.Account">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="user_id" jdbcType="BIGINT" property="userId" />
<result column="total" jdbcType="DECIMAL" property="total" />
<result column="used" jdbcType="DECIMAL" property="used" />
<result column="residue" jdbcType="DECIMAL" property="residue" />
</resultMap>
<update id="decrease">
update t_account
set
residue = residue - #{money},used = used + #{money}
where user_id = #{userId}
</update>
</mapper>
3).Service接口及实现
package com.atguigu.cloud.service;
public interface AccountService {
/**
* 扣减账户余额
* @param userId
* @param money
*/
void decrease(Long userId, Long money);
}
package com.atguigu.cloud.service.impl;
import com.atguigu.cloud.mapper.AccountMapper;
import com.atguigu.cloud.service.AccountService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class AccountServiceImpl implements AccountService
{
@Resource
AccountMapper accountMapper;
@Override
public void decrease(Long userId, Long money) {
log.info("------->account-service中扣减账户余额开始");
accountMapper.decrease(userId,money);
//设置超时
// myTimeOut();
//设置异常
// int age = 10/0;
log.info("------->account-service中扣减账户余额结束");
}
/**
* 模拟超时异常,全局事务回滚
* openFeign 默认读取数据超时时间是60秒,设置65秒则颐一定超时
*/
private static void myTimeOut()
{
try{
TimeUnit.SECONDS.sleep(65);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
4).Controller
package com.atguigu.cloud.controller;
import com.atguigu.cloud.resp.ResultData;
import com.atguigu.cloud.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AccountController
{
@Resource
AccountService accountService;
@RequestMapping("/account/decrease")
public ResultData decrease(@RequestParam("userId")Long userId,@RequestParam("money")Long money)
{
accountService.decrease(userId,money);
return ResultData.success("扣减账户余额成功!");
}
}
九、Seata案例实战-测试
服务启动情况数据库初始化情况1.正常下单2.超时异常出错,没有@GlobalTransactional3.超时异常解决,添加@GlobalTransactional
服务启动情况
- 启动Nacos
- 启动Seata
- 启动订单微服务2001
- 启动库存微服务2002
- 启动账户微服务2003
数据库初始化情况



1.正常下单
1).下订单->减库存->扣余额->改(订单)状态

2).此时我们没有在订单模块添加@GlobalTransactional
访问地址:
- http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
- 1号用户花费100块钱买了10个1号产品
3).正常下单,第1次
故障现象导致原因解决方案
故障现象


导致原因
springboot+springboot与springalibaba版本不兼容。参考官网对版本进行相应的调整springcloud alibaba 版本声明官网地址: 版本发布说明-阿里云Spring Cloud Alibaba官网
Spring Cloud Alibaba Version
|
Spring Cloud Version
|
Spring Boot Version
|
2023.0.1.0*
|
Spring Cloud 2023.0.1
|
3.2.4
|
2023.0.0.0-RC1
|
Spring Cloud 2023.0.0
|
3.2.0
|
Spring Cloud Alibaba Version
|
Sentinel Version
|
Nacos Version
|
RocketMQ Version
|
Seata Version
|
2023.0.1.0
|
1.8.6
|
2.3.2
|
5.1.4
|
2.0.0
|
2023.0.0.0-RC1
|
1.8.6
|
2.3.0
|
5.1.4
|
2.0.0
|
<spring.boot.version>3.2.4</spring.boot.version>
<spring.cloud.version>2023.0.1</spring.cloud.version>
<spring.cloud.alibaba.version>2023.0.1.0</spring.cloud.alibaba.version>
4).正常下单,第2次

数据库情况



2.超时异常出错,没有@GlobalTransactional
1).添加超时方法
seata-account-service2003微服务中的AccountserviceImpl添加超时方法myTimeOut();
package com.atguigu.cloud.service.impl;
import com.atguigu.cloud.mapper.AccountMapper;
import com.atguigu.cloud.service.AccountService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class AccountServiceImpl implements AccountService
{
@Resource
AccountMapper accountMapper;
@Override
public void decrease(Long userId, Long money) {
log.info("------->account-service中扣减账户余额开始");
accountMapper.decrease(userId,money);
myTimeOut();//设置超时
// int age = 10/0;//设置异常
log.info("------->account-service中扣减账户余额结束");
}
/**
* 模拟超时异常,全局事务回滚
* openFeign 默认读取数据超时时间是60秒,设置65秒则颐一定超时
*/
private static void myTimeOut()
{
try{
TimeUnit.SECONDS.sleep(65);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
2).故障情况



3.超时异常解决,添加@GlobalTransactional
前提条件
AccountServiceImpl保留超时方法OrderServiceImpl添加@GlobalTransactional
@Override@GlobalTransactional(name = "zzyy-create-order",rollbackFor = Exception.class) //ATpublic void create(Order order)
{
。。。。。。
}

查看Seata后台:
备注先运行地址,否则查看不到全局事务:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
全局事务ID

tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,全集事务名称:zzyy-create-order@GlobalTransactional(name = " zzyy-create-order",rollbackFor = Exception.class)//AT
全局锁

globalTransactional全局事务id:TransactionalId,分支事务id:branchId。
数据回滚
下单后数据库3个库数据并没有任何改变,被回滚了

业务中

回滚后
order记录都添加不进来,全部回退。

全局事务,正常扣除操作
去除 以下设置的超时与异常方法,则全局是事务正常进行。myTimeOut();//设置超时// int age = 10/0;//设置异常
