N

Norlcayn的博客

A text-focused Halo theme

  • 首页
  • 文章
  • 默认分类
  • 关于
主页 Seata
文章

Seata

发表于 2026-01-16 更新于 昨天
作者 Administrator
31~40 分钟 阅读

分布式事务

项目中的下单业务整体流程如下:

whiteboard_exported_image (11)

由于订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,因此下单过程中就会跨多个数据库完成业务。而每个微服务都会执行自己的本地事务:

  • 交易服务:下单事务
  • 购物车服务:清理购物车事务
  • 库存服务:扣减库存事务

整个业务中,各个本地事务是有关联的。因此每个微服务的本地事务,也可以称为分支事务。多个有关联的分支事务一起就组成了全局事务。最终必须保证整个全局事务同时成功或失败

默认情况下,微服务之间的事务管理是独立的。也就是说,如果某个微服务模块发生了回滚,但此时其他微服务模块是无法知道的,并不会同时进行回滚操作

做一个测试,先进入购物车页面:

63bdffbf-f5b9-4126-8679-7585c8604566

目前有4个购物车,然结算下单,进入订单结算页面:

13109ffb-0f7b-4525-a996-489e356ca425

然后将购物车中某个商品的库存修改为0:

3c54a38b-7910-4e67-af8c-6522f461b92c

然后,提交订单,最终因库存不足导致下单失败:

7f3395b8-7de1-4b6d-a221-e5cb0f1ef941

然后去查看购物车列表,发现购物车数据依然被清空了,并未回滚:

bf897bc1-52ba-40a8-91f1-b73e83d85252

认识Seata

解决分布式事务的方案有很多,但实现起来都比较复杂,因此一般会使用开源的框架来解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。官网地址:Apache Seata

其实分布式事务产生的一个重要原因,就是参与事务的多个分支事务互相无感知,**不知道彼此的执行状态**。因此解决分布式事务的思想非常简单:

就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来实现的

Seata也不例外,在Seata的事务管理中有三个重要的角色:

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚

Seata的工作架构如图所示:

36416c48-e253-4f18-a56d-0d416eac43ee

其中,TM和RM可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可。将来TM和RM就会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚

而TC服务则是事务协调中心,是一个独立的微服务,需要单独部署

TC部署

准备数据库表

Seata支持多种存储模式,但考虑到持久化的需要,一般选择基于数据库存储。执行课前资料提供的《seata-tc.sql》,导入数据库表:

1a8c624c-cbbc-4472-a895-857c4a7e2d8f

准备配置文件

课前资料准备了一个seata目录,其中包含了seata运行时所需要的配置文件:

20476c11-f9e0-49fd-9c8c-57439b8618e7

将整个seata文件夹拷贝到虚拟机的/root目录:

a28e57de-b36b-4d6d-b17a-b379eec1f003

Docker部署

需要注意,要确保nacos、mysql都在hm-net网络中。如果某个容器不再hm-net网络,可以参考下面的命令将某容器加入指定网络:

docker network connect [网络名] [容器名]

在虚拟机的/root目录执行下面的命令:

docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.2.20 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2

其中,SEATA_IP需要是宿主机的IP地址,否则后续无法连接到Seata服务

如果镜像下载困难,也可以把课前资料提供的镜像上传到虚拟机并加载:

5471e542-dc12-437c-8098-9cf947f1007c

Seata网页地址:http://192.168.2.20:7099

微服务集成Seata

参与分布式事务的每一个微服务都需要集成Seata,以trade-service为例

引入依赖

了方便各个微服务集成seata,还需要把seata配置共享到nacos,因此trade-service模块不仅仅要引入seata依赖,还要引入nacos依赖:

<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>

改造配置

首先在nacos上添加一个共享的seata配置,命名为shared-seata.yaml:

36201cb0-9b4e-497e-bfb2-5ca70951143f

内容如下:

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.2.20:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: "default"

然后,改造trade-service模块,添加bootstrap.yaml:

655118be-f451-410c-becb-4100169d236a

内容如下:

spring:
  application:
    name: trade-service # 服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.2.20 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置
          - dataId: shared-seata.yaml # 共享seata配置

可以看到这里加载了共享的seata配置

然后改造application.yaml文件,内容如下:

server:
  port: 8085
feign:
  okhttp:
    enabled: true # 开启OKHttp连接池支持
  sentinel:
    enabled: true # 开启Feign对Sentinel的整合
hm:
  swagger:
    title: 交易服务接口文档
    package: com.hmall.trade.controller
  db:
    database: hm-trade

参考上述办法分别改造hm-cart和hm-item两个微服务模块

测试

接下来就是测试的分布式事务的时候了

找到trade-service模块下的com.hmall.trade.service.impl.OrderServiceImpl类中的createOrder方法,也就是下单业务方法

将其上的@Transactional注解改为Seata提供的@GlobalTransactional:

3658f902-664a-41ba-a9be-a68c0918834b

@GlobalTransactional注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务

重启trade-service、item-service、cart-service三个服务。再次测试,发现分布式事务的问题解决了!

@GlobalTransactional 只需要在事务发起方的方法上添加即可,一旦标记了该注解,Seata 会自动创建全局事务,并在整个调用链中传播

XA模式

Seata支持四种不同的分布式事务解决方案:

  • XA
  • TCC
  • AT
  • SAGA

这里以XA模式和AT模式来给大家讲解其实现原理

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持

两阶段提交

XA是规范,目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交

正常情况:

c8ebc05d-c4d5-4631-a408-140d848ab493

异常情况:

7c2df22a-0ecf-436f-b5f0-fbcab03e9620

一阶段:

  • 事务协调者通知每个事务参与者执行本地事务
  • 本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,

二阶段:

  • 事务协调者基于一阶段的报告来判断下一步操作
  • 如果一阶段都成功,则通知所有事务参与者,提交事务
  • 如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务

Seata的XA模型

Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:

5a05eb2f-cf16-4471-ba79-a9b3718bf92a

RM一阶段的工作:

  1. 注册分支事务到TC
  2. 执行分支业务sql但不提交
  3. 报告执行状态到TC

TC二阶段的工作:

  1. TC检测各分支事务执行状态
  2. 如果都成功,通知所有RM提交事务
  3. 如果有失败,通知所有RM回滚事务

RM二阶段的工作:

  • 接收TC指令,提交或回滚事务

优缺点

XA模式的优点是什么?

  • 事务的强一致性,满足ACID原则
  • 常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

实现步骤

首先,要在配置文件中指定要采用的分布式事务模式,在Nacos中的共享shared-seata.yaml配置文件中设置:

seata:
  data-source-proxy-mode: XA

其次,利用@GlobalTransactional标记分布式事务的入口方法:

8b5459b8-78c9-44d9-804a-58db8113bfd3

AT模式(推荐)

AT模式同样是分阶段提交的事务模型,不过弥补了XA模型中资源锁定周期过长的缺陷

Seata的AT模型

基本流程图:

6f0eff4e-8e27-48fd-8164-a15079bb3c40

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

简单理解,就是每个分支事务在执行前,先会保存一份原来的数据,然后直接提交事务。当后续事务出现问题时,按照保存的数据进行恢复

流程梳理

用一个真实的业务来梳理下AT模式的原理

比如,现在有一个数据库表,记录用户余额:

id money
1 100

其中一个分支业务要执行的SQL为:

update tb_account set money = money - 10 where id = 1

AT模式下,当前分支事务执行流程如下:

一阶段:

  1. TM发起并注册全局事务到TC
  2. TM调用分支事务
  3. 分支事务准备执行业务SQL
  4. RM拦截业务SQL,根据where条件查询原始数据,形成快照
{
  "id": 1, "money": 100
}
  1. RM执行业务SQL,提交本地事务,释放数据库锁。此时 money = 90
  2. RM报告本地事务状态给TC

二阶段:

  1. TM通知TC事务结束
  2. TC检查分支事务状态
  3. 如果都成功,则立即删除快照
  4. 如果有分支事务失败,需要回滚。读取快照数据({“id”: 1, “money”: 100}),将快照恢复到数据库。此时数据库再次恢复为100

流程图:

d8371cc1-e8cf-4421-aea3-76bc1ffcf3ec

AT与XA的区别

简述AT模式与XA模式最大的区别是什么?

  • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强一致;AT模式最终一致

可见,AT模式使用起来更加简单,无业务侵入,性能更好。因此企业90%的分布式事务都可以用AT模式来解决

实现步骤

将资料中的seata-at.sql添加到对应的数据库中:

-- 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';

该数据表是添加到业务对应的数据库下,而非seata服务的数据库

然后,修改配置文件,将事务模式改为AT模式:

seata:
data-source-proxy-mode: AT # AT模式
微服务
中间件 后端 SpringCloud 分布式事务 XA模式 AT模式
许可协议:  CC BY 4.0
分享

相关文章

1月 16, 2026

ElasticSearch

前情提要 黑马商城作为一个电商项目,商品的搜索肯定是访问频率最高的页面之一。目前搜索功能是基于数据库的模糊搜索来实现的,存在很多问题 首先,查询效率较低 由于数据库模糊查询不走索引,在数据量较大的时候,查询性能很差。黑马商城的商品表中仅仅有不到9万条数据,基于数据库查询时,搜索接口的表现如图: 改为

1月 16, 2026

RabbitMQ

微服务一旦拆分,必然涉及到服务之间的相互调用,而目前服务之间调用采用的都是基于OpenFeign的调用。这种调用中,调用者发起请求后需要等待服务提供者执行业务返回结果后,才能继续执行后面的业务。也就是说调用者在调用过程中处于阻塞状态,因此称这种调用方式为同步调用,也可以叫同步通讯。但在很多场景下,需

1月 16, 2026

Redis面试篇

Redis主从 单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离 主从集群结构 下图就是一个简单的Redis主从集群结构: 搭建主从集群 在同一个虚拟机中利用3个Docker容器来搭建主从集群,容器信息如下: 容器名 角色 IP 映射端口 r1

下一篇

Sentinel

上一篇

SpringCloud

最近更新

  • Hello Halo
  • 状态机
  • 数据表设计规范
  • 缓存优化
  • 事务失效

热门标签

Halo 异常处理 Redisson 并行 Vue 并发 递归 请求限流 SpringAMQP 类加载器

目录

©2026 Norlcayn的博客. 保留部分权利。

使用 Halo 主题 Chirpy