Seata
Seata是阿里巴巴开源的分布式事务解决方案,旨在为微服务架构提供高性能和简单易用的分布式事务服务。它的核心设计思想,是把一个复杂的分布式事务拆解为多个本地事务,并由一个中心化的协调者来统一管理。
Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata核心角色
TC (Transaction Coordinator) - 事务协调者
- 决策者
维护全局和分支事务的状态,协调全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
- 执行者
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
- 管理本地事务
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
架构图

Seata执行流程
简化流程:
- TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
- XID在微服务调用链路的上下文中传播。
- RM向TC注册分支事务,接着执行这个分支事务并提交(重点:RM在第一阶段就已经执行了本地事务提交/回滚),最后将执行结果汇报给TC。
- TM根据TC中所有的分支事务的执行情况,发起全局提交或回滚决议。
- TC调度XID下管辖的全部分支事务完成提交或回滚请求。
以 订单服务 调用 库存服务 完成下单扣库存为例:
第一步、预备动作:开启全局事务
- 入口请求到达 :用户请求进入订单服务的下单方法,该方法被
@GlobalTransactional注解标记。 - TM 申请全局事务 :订单服务内置的 TM(事务管理器) 向独立的 TC(事务协调者) 申请开启一个全局事务。
- TC 颁发 XID :TC 生成一个全局唯一的 XID ,并返回给 TM。此时,全局事务状态机已就绪。
第二步、第一阶段:分支事务执行与注册
核心特征 :每个本地事务执行完立即提交,不等待最终指令。资源(数据库连接、本地锁)在此阶段结束即释放。
步骤 2.1:订单服务执行本地事务
- 拦截 SQL :订单服务(此时扮演 RM )执行
INSERT INTO order ...。 - 记录前镜像 :Seata 数据源代理先查询出准备修改的数据(若为插入则为空)。
- 执行并记录后镜像:执行插入 SQL,再查询插入后的数据行。
- 组装 UndoLog:将前镜像(空)、后镜像(新数据)组装成一条回滚日志。
- 同库事务提交 :
- 核心原子操作 :将 业务数据 和 UndoLog 日志 写入同一个本地数据库事务 中并立即提交。
- 结果:订单数据落库,数据库连接释放,本地行锁释放。
- 注册并上报 :RM 向 TC 注册分支事务(关联全局 XID),并报告"分支 1 执行成功"。
步骤 2.2:库存服务执行本地事务
- RPC 调用传递 XID :订单服务通过 Feign/Dubbo 调用库存服务,HTTP 请求头中携带了 XID。
- 库存服务拦截请求:库存服务的 Seata 拦截器解析出 XID,将其绑定到当前线程。
- 执行 SQL :执行
UPDATE stock SET count = count - 1 ...。 - 记录镜像 :
- 前镜像 :查询当前库存数量(例如
100)。 - 后镜像 :执行 SQL 后查询库存数量(例如
99)。
- 前镜像 :查询当前库存数量(例如
- 同库事务提交 :
- 将
UPDATE语句和包含100->99镜像的 UndoLog 在同一个事务中提交。 - 数据库连接释放,该行锁释放。
- 将
- 注册并上报:库存 RM 向 TC 注册分支事务,并报告"分支 2 执行成功"。
第三步、第二阶段:全局决议与异步收尾
核心特征 :TC 根据所有分支结果通知 各 RM,RM 异步 执行收尾工作(删除日志或回滚),不阻塞主业务流程。
场景 A:一切顺利(全局提交)
- TM 发起提交:订单服务方法执行结束,TM 告知 TC:"XID=123 可以全局提交了"。
- TC 通知 RM :TC 检索该 XID 下的所有分支,异步 向订单库 RM 和库存库 RM 发送
Commit指令。 - RM 清理日志 :各 RM 收到指令后,仅做一件事 ------删除第一阶段写入的那条
UndoLog记录。 - 流程结束:此时若删除日志失败也无妨,后台线程会定时清理过期 UndoLog,不影响业务数据正确性。
场景 B:库存不足(全局回滚)
- TM 发起回滚:库存服务抛出异常,订单服务捕获异常后,TM 告知 TC:"XID=123 需要全局回滚"。
- TC 通知 RM :TC 向订单库 RM 和库存库 RM 发送
Rollback指令。 - RM 执行反向补偿 (以订单库为例):
- RM 读取第一步写入的
UndoLog记录。 - 前镜像校验 :对比当前数据库数据与 UndoLog 中的后镜像 是否一致,防止脏写。
- 生成反向 SQL :根据前后镜像,生成一条
DELETE FROM order WHERE id = ...语句。 - 执行补偿并提交 :执行删除操作,提交本地事务,删除
UndoLog。
- RM 读取第一步写入的
- 结果:订单记录被物理删除,数据恢复如初。
Seata 事务解决方案
Seata 提供了四种模式来应对不同业务场景。你的选择,本质上是在"对代码的侵入程度 "和"对性能与灵活性的要求"之间做权衡。
AT 模式:默认的无侵入方案
AT 模式的核心是将资源锁定周期从全局事务的整个周期缩短到本地事务的执行瞬间 。它通过数据源代理拦截SQL,在本地事务中同时记录undo_log(包含数据修改前后的快照),并立即提交事务,从而释放数据库连接和本地锁。在第二阶段,Seata TC 会根据全局事务的最终决议,通知各RM异步删除 日志,或基于undo_log生成反向SQL进行数据补偿。
TCC 模式:高性能的手动补偿方案
TCC 模式将控制权完全交给开发者,通过编码实现三个核心阶段:
-
Try:检查并预留业务资源(如冻结库存)。
-
Confirm:执行真正的业务逻辑,确认提交。
-
Cancel:执行补偿逻辑,释放 Try 阶段预留的资源。
这种方案性能最高,但开发复杂,需自行处理接口的幂等性 和空回滚问题。
Saga 模式:长流程的异步事件方案
Saga 模式专为长流程、跨系统的场景设计。它将一个长事务拆解为多个独立的本地事务,并为每个正向操作定义一个逆向的补偿操作。当流程中某一步失败时,Saga 会驱动一个补偿流程,按相反顺序执行已成功节点的补偿操作,使系统恢复原状。它天然支持异步和事件驱动,吞吐量高。
XA 模式:传统强一致方案
XA 模式是传统的两阶段提交协议的实现,追求严格的 ACID 强一致性。其代价是在整个分布式事务期间,数据库资源(如数据行)会被持续锁定,导致并发性能显著下降。
SpringCloud整合Seata框架
第一步:部署TC协调者服务器。
Seata服务器下载地址:https://github.com/apache/incubator-seata/releases
1. 在MySql中创建Seata库
打开下载好的Seata压缩包,打开D:\cloud\seata-server-2.0.0\seata\script\server\db目录,执行目录下的mysql.sql。
- 我们需要提前创建好库
seata。 - 执行完成脚本后,默认创建四个数据库。
sql
global_table
branch_table
lock_table
distributed_lock
2. 在Nacos配置中心添加Seata外部配置文件
采用Nacos作为配置中心:
-
在nacos的命名空间下,新建配置
Data ID为seata-server.properties,Group为DEFAULT_GROUP(默认选项)的配置。 -
打开文件
D:\cloud\seata-server-2.0.0\seata\script\config-center\config.txt,复制配置到nacos。 -
特点注意:
store.mode=file:file改为db。store.db.datasource=druid:druid改为hikari。
yaml
#使用数据库存储会话与锁信息
store.mode=db
store.lock.mode=db
store.session.mode=db
sql
# 使用 HikariCP 高性能连接池,默认为druid
store.db.datasource=hikari
# mysql
store.db.dbType=mysql
# 驱动
store.db.driverClassName=com.mysql.jdbc.Driver
# 数据库地址
store.db.url=jdbc:mysql://192.168.58.129:3306/seata?useUnicode=true&rewriteBatchedStatements=true
# 数据库用户名
store.db.user=root
# 数据库密码
store.db.password=123456
3. 修改seata配置文件yaml,连接到Nacos配置和注册中心
配置中心
路径:D:\cloud\seata-server-2.0.0\seata\conf\application.yaml
- 原始配置
yaml
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
yaml
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
# nacos 配置中心地址
server-addr: 127.0.0.1:8848
# 名称空间
namespace: d0247d5f-d454-47cd-ad12-e59c70bd207c
# 用户名
username:
# 密码
password:
context-path:
# 在nacos配置的文件名称
data-id: seataServer.properties
注册中心
- 原始配置:
yaml
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: file
yaml
registry:
# 注册中心nacos
type: nacos
nacos:
# 我seata服务器名称
application: seata-server
server-addr: 127.0.0.1:8848
# 分组需要与其他微服务分组保持一致
group: DEV-01
# 名称空间要保持一致
namespace: d0247d5f-d454-47cd-ad12-e59c70bd207c
cluster: default
username:
password:
3. 启动服务
启动服务 :D:\cloud\seata-server-2.0.0\seata\bin\seata-server.bat
第二步:注册RM分支事务到TC服务器。
1. lqb-user模块添加Seata依赖项
- 排除
alibaab.seata自带Seata场景启动器。
pom
<!-- seata:分布式事务-->
<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>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
2. 在Nacos中配置创建Seata配置文件
- 我们当前是
lqb_bank模块,因此模块名称事务为bank_tx_group
yaml
seata:
registry:
type: nacos
nacos:
# 我们配置Seata TC协调者服务器的名称
application: seata-server
server-addr: localhost:8848
# 必须要再强调一遍,如果我们的cloud项目,配置了名称空间和分组
# 则必须同时在seata TC协调者服务器中设置
# 也必须在分支事务即当前模块配置
# 切记切记
namespace: d0247d5f-d454-47cd-ad12-e59c70bd207c
group: DEV-01
# 配置事务分组映射:我们当前是lqb_user模块,因此模块名称事务为user_tx_group
tx-service-group: user_tx_group
service:
vgroup-mapping:
user_tx_group: default
# AT模式
data-source-proxy-mode: AT
- 事务分组映射:对应的是
Nacos中的seata-server.properties配置文件,即第一步中的2小节。
yaml
# 配置事务分组映射:必须与nacos中的seata-server.properties,保持一致
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
data-source-proxy-mode: AT
tx-service-group(user_tx_group) :是你的分机名。vgroup-mapping:是公司的交换机(映射表)。default:是具体的办公室(TC 集群)。
如果没有映射(直接连):
- 你的代码里必须写:
seata-server-address: 192.168.1.100:8091。- 麻烦点:如果这台服务器挂了,或者你换了新服务器,你得去修改几十个微服务的配置文件,逐一重启。
有了映射(间接连):
- 你的代码只写:
tx-service-group: default_tx_group。- 好处 :如果服务器换了,你只需要在
Nacos配置中心改一下映射关系(把default_tx_group指向新的cluster名字),微服务不需要重启就能通过 Nacos 自动发现新的 TC 地址。
- 好处 :如果服务器换了,你只需要在
3. 加载nacos远程配置文件
yaml
spring:
# spring下的config标签,与cloud同级
config:
# 使用import导入nacos配置
import:
# 导入分布式事务配置
- nacos:user_seata.yaml
- 需要导入配置中心的依赖,和配置中心的地址。
4. 在业务数据库中创建undo_log表(注意:AT模式)
- 我们使用了 Seata 的 AT 模式 (
data-source-proxy-mode: AT)。
该模式的工作原理是:在更新业务数据前,先将数据的"前后快照"保存到一张名为undo_log的表中,以便在事务失败时回滚。 - 因此我们还需要在lqb_user模块业务
lqb_user数据库中创建这张表undo_log。 - https://github.com/apache/incubator-seata/blob/2.x/script/client/at/db/mysql.sql
sql
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`);
- 如果你使用的是
XA模式,则不需要创建该表。直接忽略该步即可。
第三步:业务代码添加注解。
- 正常情况下
@GlobalTransactional事务注解在Service层方法上加。 - 因为是笔记,所以加在了Controller层。
java
@GlobalTransactional
@PutMapping("update/{id}")
public User updateById(@PathVariable Long id) {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
Wrapper<User> updateWrapper = wrapper.eq(User::getId, id);
wrapper.set(User::getUsername, RandomUtil.getPositiveInt() + id.toString());
int i= 1/0;
userService.update(updateWrapper);
return userService.getById(id);
}
-
查看执行情况:触发异常 ,执行回滚
Load [io.seata.rm.datasource.undo.UndoExecutorHolder] class fail: no service files found in 'META-INF/seata/'.
xid 192.168.195.1:8091:6846238502089228298 branch 6846238502089228299, undo_log deleted with GlobalFinished
branch rollback success, xid:192.168.195.1:8091:6846238502089228298, branchId:6846238502089228299
Branch Rollbacked result: PhaseTwo_Rollbacked
branch rollback result:BranchRollbackResponse{xid='192.168.195.1:8091:6846238502089228298', branchId=6846238502089228299, branchStatus=PhaseTwo_Rollbacked, resultCode=Success, msg='null'}
write message:BranchRollbackResponse{xid='192.168.195.1:8091:6846238502089228298', branchId=6846238502089228299, branchStatus=PhaseTwo_Rollbacked, resultCode=Success, msg='null'}, channel:[id: 0x7a5b698d, L:/192.168.195.1:54566 - R:/192.168.195.1:8091],active?true,writable?true,isopen?true
io.seata.core.rpc.netty.TmNettyRemotingClient@fca63a6 msgId:35, body:GlobalRollbackResponse{globalStatus=Rollbacked, resultCode=Success, msg='null'}
transaction end, xid = 192.168.195.1:8091:6846238502089228298
unbind 192.168.195.1:8091:6846238502089228298
[192.168.195.1:8091:6846238502089228298] rollback status: Rollbacked
Failed to complete request: java.lang.RuntimeException: try to proceed invocation error
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: try to proceed invocation error] with root cause