谈谈你对 Seata 的理解?8 年 Java 开发:从业务踩坑到源码级解析(附实战代码)

谈谈你对 Seata 的理解?8 年 Java 开发:从业务踩坑到源码级解析(附实战代码)

作为一名摸爬滚打 8 年的 Java 开发,从单体项目的本地事务(@Transactional一把梭),到微服务拆分后 "订单创建成功、库存没减" 的血案,再到现在复杂中台的分布式事务治理,Seata 几乎是绕不开的坎。今天不聊空洞的理论,只从实战角度跟大家掰扯:Seata 到底是什么?解决了什么业务痛点?怎么用才不踩坑?

一、从业务痛点说起:为什么需要 Seata?

在聊 Seata 之前,先回想一个你肯定遇到过的场景 ------电商下单

用户下单时,需要调用 3 个服务:

  1. 订单服务:创建订单(状态 "待支付");

  2. 库存服务:扣减对应商品库存;

  3. 积分服务:给用户加下单积分。

如果用单体架构,一个@Transactional就能保证这 3 步要么全成、要么全败。但微服务拆分后,这 3 个操作分布在 3 个独立的服务、3 个独立的数据库里 ------ 问题来了:

  • 万一订单创建成功,库存扣减时数据库宕机了怎么办?

  • 库存扣完了,积分服务加积分超时了怎么办?

结果就是 "数据不一致":订单有了但库存没少(超卖风险)、库存少了但积分没加(用户投诉)。这时候,分布式事务的需求就冒出来了 ------ 而 Seata,就是阿里开源的、专门解决微服务分布式事务的框架。

8 年开发的第一个结论:Seata 不是 "银弹",而是 "对症药" ------ 它解决的是 "微服务跨库操作的数据一致性问题",前提是你真的遇到了这类问题(小项目单体架构没必要上 Seata,反而增加复杂度)。

二、Seata 核心:4 种模式 + 3 大组件,一篇讲透

Seata 的核心设计很清晰:用 "3 大组件" 支撑 "4 种事务模式",不同模式对应不同业务场景。先搞懂这俩,才算真的理解 Seata。

1. 先搞懂 3 大组件:谁在干活?

Seata 的分布式事务靠 3 个角色协同完成,类比 "剧组拍戏" 很好理解:

组件 角色定位 举个例子(下单场景)
TC(协调者) 事务 "导演":负责协调所有参与者提交 / 回滚 独立部署的 Seata-Server,决定订单、库存、积分事务最终是提交还是回滚
TM(管理器) 事务 "发起人":开启 / 结束事务 订单服务:调用@GlobalTransactional注解的方法,发起分布式事务
RM(资源器) 事务 "参与者":执行本地事务 + 上报状态 订单库、库存库、积分库:执行本地 SQL,并向 TC 上报 "执行成功 / 失败"

工作流程简化

  1. TM(订单服务)向 TC 申请开启全局事务,TC 生成全局事务 ID(XID);
  2. TM 调用 RM1(订单库)执行 "创建订单",RM1 执行本地 SQL,生成 undo log,向 TC 上报 "执行成功";
  3. TM 调用 RM2(库存库)执行 "扣减库存",RM2 同理,上报状态;
  4. TM 调用 RM3(积分库)执行 "加积分",RM3 同理,上报状态;
  5. 若所有 RM 都成功,TC 通知所有 RM 提交事务;若有一个 RM 失败,TC 通知所有 RM 回滚事务。

2. 再选对 4 种模式:不同业务用不同方案

Seata 提供 4 种事务模式,8 年开发的经验是:90% 的业务用 AT 模式就够了,复杂场景再考虑 TCC/SAGA

模式 核心原理 适用场景 优点 缺点
AT 基于 SQL 解析 + undo log,自动提交 / 回滚 简单业务(下单、支付),支持 MySQL/Oracle 等 零侵入(不用改业务代码),易用 依赖关系型数据库,不支持非 SQL 操作
TCC 手动写 Try(预留资源)/Confirm(确认)/Cancel(取消) 复杂业务(资金扣减、库存预留) 灵活,支持非 DB 操作 开发成本高(需写 3 个方法)
SAGA 长事务拆分,正向流程 + 反向补偿 长事务(物流、审批流) 支持超长时间事务 一致性弱(最终一致),需写补偿方法
TXC 基于阿里云 PolarDB,强一致性 阿里云 PolarDB 用户 强一致,性能好 依赖特定数据库,通用性差

重点讲 AT 模式 (最常用):

比如 "扣减库存" 操作,AT 模式会做 3 件事:

  1. SQL 解析:拦截 "update stock set num=num-1 where id=1",生成前镜像(更新前的 num 值,比如 10)和后镜像(更新后的 num 值,比如 9);

  2. 生成 undo log :把前镜像、后镜像、SQL 信息存到undo_log表,作为回滚依据;

  3. 两阶段提交

    • 第一阶段:RM 执行本地 SQL,上报 "成功",持有本地锁;

    • 第二阶段:若所有 RM 都成功,TC 通知 RM 删除 undo log、释放锁(提交);若有失败,TC 通知 RM 用 undo log 回滚(比如把 num 从 9 改回 10)。

这也是 AT 模式 "零侵入" 的原因 ------ 开发者不用关心回滚逻辑,Seata 自动搞定。

三、实战:Spring Cloud Alibaba 整合 Seata(附代码 + 避坑)

光说不练假把式,结合 "订单 - 库存" 场景,手把手教你搭 Seata 环境(用 Seata 1.6.1 版本,Spring Cloud Alibaba 2022.0.0.0 版本)。

1. 第一步:搭建 TC 服务(Seata-Server)

TC 是核心协调者,必须先启动:

  1. 下载 Seata-Server :从Seata 官网下载 1.6.1 版本,解压;

  2. 修改配置文件

    打开conf/application.yml,配置注册中心(用 Nacos,微服务常用)和数据库(存储全局事务信息):

    yaml 复制代码
    seata:
      registry:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848  # 你的Nacos地址
          group: SEATA_GROUP
          application: seata-server
      config:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
      store:
        mode: db  # 事务信息存数据库(生产用db,不要用file)
        db:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&serverTimezone=UTC
          user: root
          password: 123456
  3. 建 TC 数据库表 :在seata库中执行官网 SQL,创建global_table(全局事务表)、branch_table(分支事务表)等;

  4. 启动 Seata-Server

    进入bin目录,执行:

    yaml 复制代码
    # Windows
    seata-server.bat -p 8091 -h 127.0.0.1
    # Linux
    sh seata-server.sh -p 8091 -h 127.0.0.1

    启动后,在 Nacos 的 "服务列表" 能看到seata-server,说明 TC 就绪。

2. 第二步:配置 RM 和 TM(微服务端)

以 "订单服务(TM)" 和 "库存服务(RM)" 为例,整合 Seata。

(1)加依赖(pom.xml)

两个服务都要加 Seata 和 Spring Cloud Alibaba 的依赖:

xml 复制代码
<!-- Seata starter -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!-- 排除旧版本Seata,统一用1.6.1 -->
        <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>1.6.1</version>
</dependency>
<!-- Nacos注册中心(微服务必备) -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)改配置(application.yml)

两个服务的配置类似,重点是 "Seata 事务组" 和 "数据源代理":

yaml 复制代码
spring:
  application:
    name: order-service  # 订单服务名(库存服务改名为stock-service)
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848  # Nacos地址
    alibaba:
      seata:
        tx-service-group: my_seata_group  # 事务组名,所有服务必须一致
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/order_db?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456

# Seata配置
seata:
  service:
    vgroup-mapping:
      my_seata_group: default  # 事务组映射到TC的集群名(默认是default)
    grouplist:
      default: 127.0.0.1:8091  # TC服务地址
  tx-service-group: my_seata_group
  data-source-proxy-mode: AT  # 数据源代理模式,AT模式必须配
(3)建 undo_log 表(关键!)

AT 模式需要在每个 RM 的数据库(订单库order_db、库存库stock_db)中创建undo_log表,用于存储回滚信息:

r 复制代码
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
(4)写业务代码
  • 订单服务(TM) :发起分布式事务,调用库存服务;

    scss 复制代码
    @Service
    public class OrderService {
        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private RestTemplate restTemplate;  // 调用库存服务
    
        // 全局事务注解:加在TM的方法上!
        @GlobalTransactional(rollbackFor = Exception.class)
        public void createOrder(Long userId, Long productId, Integer count) {
            // 1. 本地事务:创建订单
            Order order = new Order();
            order.setUserId(userId);
            order.setProductId(productId);
            order.setCount(count);
            order.setStatus(0);  // 0:待支付
            orderMapper.insert(order);
            System.out.println("订单创建成功:" + order.getId());
    
            // 2. 远程调用:扣减库存(调用库存服务)
            String url = "http://stock-service/deduct?productId=" + productId + "&count=" + count;
            // 故意抛异常测试回滚:int i = 1/0;
            restTemplate.postForObject(url, null, String.class);
        }
    }
  • 库存服务(RM) :执行本地事务(扣减库存);

    java 复制代码
    @Service
    public class StockService {
        @Autowired
        private StockMapper stockMapper;
    
        // 本地事务:不用加@GlobalTransactional,Seata自动拦截
        public void deduct(Long productId, Integer count) {
            // 扣减库存
            int rows = stockMapper.deductStock(productId, count);
            if (rows == 0) {
                throw new RuntimeException("库存不足!");
            }
            System.out.println("库存扣减成功:商品ID=" + productId);
        }
    }
(5)测试效果
  1. 初始数据:库存库stock_db中,商品 ID=1 的库存是 10;
  2. 正常调用:调用订单服务的createOrder(1L, 1L, 2),订单创建成功,库存从 10 变为 8;
  3. 测试回滚:在订单服务的createOrder中加int i = 1/0(故意抛异常),再次调用 ------ 订单不会创建,库存还是 10,说明分布式事务回滚成功!

四、8 年开发总结:Seata 落地的 8 个避坑点

  1. 数据源代理必须配对 :AT 模式要设data-source-proxy-mode: AT,TCC 模式不用配 ------ 之前忘配这个,导致 undo log 没生成,回滚失败;
  2. undo_log 表不能少 :每个 RM 的数据库都要建,字段不能错(尤其是rollback_info是 longblob 类型);
  3. 事务组名要一致 :所有服务的tx-service-group必须相同,否则 TC 找不到对应的 RM;
  4. @GlobalTransactional 加对地方 :只能加在 TM 的方法上(比如订单服务的createOrder),加在 RM 的方法上无效;
  5. TC 要高可用:生产环境不能单节点部署 TC,要集群化(用 Nacos 做注册中心,多启动几个 Seata-Server);
  6. 避免长事务:AT 模式会持有数据库锁,长事务会导致锁竞争加剧,性能下降 ------ 复杂长事务建议用 SAGA 模式;
  7. 日志要开全 :Seata 日志级别设为 DEBUG,方便排查问题(配置logging.level.io.seata=DEBUG);
  8. 版本要兼容:Spring Cloud Alibaba 和 Seata 版本要匹配(比如 2022.0.0.0 对应 Seata 1.6.x,2021.0.1.0 对应 Seata 1.4.x),版本不兼容会报各种奇怪的错。

五、Seata 选型:不是所有场景都适合用

最后,8 年开发的真心话:Seata 虽好,但不是所有场景都需要用。给大家一个选型参考:

  • 用 Seata 的场景:跨服务、跨库的写操作,需要强一致性(比如下单、支付、资金转账);

  • 不用 Seata 的场景

    1. 单体项目:本地事务@Transactional足够;
    2. 只读操作:比如查询订单 + 查询库存,不需要事务;
    3. 最终一致性即可:比如用户下单后发通知,用 MQ 异步通知就行,不用 Seata(Seata 是强一致,性能开销比 MQ 大)。

总结

Seata 本质是 "微服务分布式事务的解决方案",核心是通过 TC 协调 TM 和 RM,用不同模式(AT/TCC/SAGA)解决不同业务的一致性问题。作为 8 年 Java 开发,我的建议是:先理解业务痛点,再选对模式,最后做好落地配置------ 别为了用 Seata 而用 Seata,适合业务的才是最好的。

如果你在 Seata 落地中遇到过 "回滚失败""锁超时" 等问题,欢迎在评论区交流,咱们一起踩坑一起成长~

相关推荐
架构师沉默7 分钟前
Java 开发者别忽略 return!这 11 种写法你写对了吗?
java·后端·架构
EndingCoder9 分钟前
React 19 与 Next.js:利用最新 React 功能
前端·javascript·后端·react.js·前端框架·全栈·next.js
RainbowJie114 分钟前
Gemini CLI 与 MCP 服务器:释放本地工具的强大潜力
java·服务器·spring boot·后端·python·单元测试·maven
ITMan彪叔24 分钟前
Nodejs打包 Webpack 中 __dirname 的正确配置与行为解析
javascript·后端
GHOME33 分钟前
Vue2与Vue3响应式原理对比
前端·vue.js·面试
张元清35 分钟前
useMergedRefs: 组件封装必不可少的自定义Hook
前端·javascript·面试
用户895356032822041 分钟前
告别重复,用Go泛型精简Gin代码
后端·gin
毕设源码尹学长1 小时前
计算机毕业设计 java 血液中心服务系统 基于 Java 的血液管理平台Java 开发的血液服务系统
java·开发语言·课程设计
运维开发故事1 小时前
AIOps系列 | 开发一个 K8s Chat 命令行工具
后端