SpringCloudAlibabaSeata分布式事务

wen java案例 3

本文目录导读:

SpringCloudAlibabaSeata分布式事务

  1. 核心概念:Seata 的三大组件
  2. 最核心的 AT 模式原理
  3. 实战落地:一整套配置与代码实现
  4. 常见问题与最佳实践
  5. 构建一个可靠的分布式事务体系

这是一个非常核心且实用的分布式技术栈组合。Spring Cloud Alibaba + Seata 是目前 Java 微服务领域解决分布式事务问题的主流方案

下面我将从 核心概念、AT模式原理、实战配置、注意事项 四个维度,帮你系统地梳理 Seata 分布式事务。


核心概念:Seata 的三大组件

Seata(Simple Extensible Autonomous Transaction Architecture)的设计思路是解耦,它把一次分布式事务拆解为三个核心角色:

  1. TC (Transaction Coordinator) - 事务协调器
    • 这是独立部署的 Seata-Server
    • 负责维护全局事务和分支事务的状态,驱动全局事务的提交或回滚。
  2. TM (Transaction Manager) - 事务管理器
    • 定义事务的范围,比如在微服务A(发起方)的方法上,通过 @GlobalTransactional 注解标记,该方法就是全局事务的起点和终点。
    • TM 负责向 TC 申请、开启、提交或回滚全局事务。
  3. RM (Resource Manager) - 资源管理器
    • 管理分支事务,通常是每一个参与事务的微服务数据库或消息队列。
    • RM 向 TC 注册分支事务,并负责执行本地事务(SQL)以及分支的提交/回滚。

工作流程: TM 通知 TC 开启全局事务 -> 微服务A 调用 微服务B -> B 的 RM 向 TC 注册分支 -> ... -> 全部成功 -> TM 通知 TC 提交 -> TC 通知所有 RM 提交 -> 完成。


最核心的 AT 模式原理

Seata 有多种模式(AT、TCC、Saga、XA),AT 模式 是最常用且对业务代码侵入最小的自动补偿模式。

核心思想: 两阶段提交(2PC)+ 快照回滚。

Branch(执行与记录)

  1. 拦截 SQL。
  2. 解析 SQL,生成 前镜像Before Image):查询修改前的数据。
  3. 执行业务 SQL (UPDATE / DELETE / INSERT)。
  4. 生成 后镜像After Image):查询修改后的数据。
  5. 生成 UNDO_LOG 记录(前镜像+后镜像+行锁信息),并插入到本地数据库undo_log 表中(与业务SQL在同一个本地事务中)。
  6. 向 TC 注册分支,并获取全局锁。
  7. 提交本地事务(业务数据 + undo_log 同时提交)。

Commit(全局提交)或 Rollback(全局回滚)

  • Commit: 如果全局事务成功,RM 收到 TC 的提交请求,Seata 会异步删除 Phase 1 生成的 UNDO_LOG 和全局锁,业务数据已提交,不需再恢复。
  • Rollback: 如果全局事务失败(如微服务B报错),RM 收到 TC 的回滚请求。
    1. 查找 UNDO_LOG 记录。
    2. 校验后镜像与当前数据是否一致(防止脏写),如果一致,则使用前镜像执行回滚 SQL。
    3. 如果不一致(被其他未受 Seata 管理的程序修改了),说明发生了脏写,需要人工介入或抛出异常。

AT 模式的优缺点

  • 优点: 零业务侵入,无需编写回滚逻辑(自动补偿)。
  • 缺点: 性能损耗较大(需要读写UNDO_LOG、全局锁),不适用于高并发热点数据(可能会产生全局锁等待)。

实战落地:一整套配置与代码实现

步骤 1:部署 Seata-Server (TC)

  • 下载: 从 GitHub 发布页下载 seata-server-1.x.x.tar.gz
  • 配置存储模式(重要):
    • 生产环境建议使用 db 模式,存储在 MySQL 中(需要有 global_tablebranch_tablelock_table 三张表)。
    • 开发环境可以使用 file 模式。
  • 配置中心/注册中心:

    将 Seata-Server 注册到 Nacos(推荐),这样微服务可以通过 Nacos 发现 Seata-Server。

  • 启动: sh seata-server.sh -p 8091 -h 127.0.0.1 -m db

步骤 2:引入 Maven 依赖(微服务端)

以 Spring Boot 2.x 为例,在 pom.xml 中添加:

<!-- Spring Cloud Alibaba Seata -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <!-- 该依赖会自动引入 seata-all,无需额外引入 seata-spring-boot-starter -->
</dependency>
<!-- 注意:确保 spring-cloud-alibaba-seata 版本与你的 Spring Cloud Alibaba 版本匹配 -->
<!-- 2021.0.5.0 对应 Seata 1.6.1 -->

步骤 3:微服务端配置(application.yml)

  1. 配置事务分组(核心): 需要告诉微服务,你属于哪个事务分组,从而让客户端找到正确的 TC 节点。

    seata:
      enabled: true
      application-id: ${spring.application.name} # 微服务名称
      tx-service-group: my_test_tx_group        # ⚠️ 事务分组名称,自定义
      service:
        vgroup-mapping:
          my_test_tx_group: default             # ⚠️ 将事务分组映射到 Seata 集群名
        grouplist:
          default: 127.0.0.1:8091               # ⚠️ Seata Server 地址(如果没用注册中心)
        enable-degrade: false
        disable-global-transaction: false
      # 配置数据源代理(务必配)
      data-source-proxy-mode: AT
  2. 数据源代理(关键步骤): Seata 必须接管数据源才能生成 UNDO_LOG,通常需要配置一个 DataSourceProxy 的 Bean,如果你的项目使用了 druid,配置如下:

    @Configuration
    public class SeataDataSourceConfig {
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.druid") // 你的数据源配置前缀
        public DataSource druidDataSource() {
            return new DruidDataSource();
        }
        @Bean("dataSource") // 必须叫 dataSource 或者使用 @Primary
        public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
            return new DataSourceProxy(druidDataSource);
        }
    }

    注意: 如果你使用了 MyBatis-Plus 的 @MapperScan,确保它扫描的 SqlSessionFactory 使用的是 DataSourceProxy 包裹后的数据源。

步骤 4:创建 UNDO_LOG 表

在每个参与事务的业务数据库中,必须创建 undo_log 表(AT 模式使用):

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,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

步骤 5:标记全局事务(业务代码)

在发起全局事务的入口方法上,加上 @GlobalTransactional 注解。

@Service
public class BusinessService {
    @Autowired
    private OrderFeignClient orderFeignClient; // 远程调用微服务A
    @Autowired
    private AccountFeignClient accountFeignClient; // 远程调用微服务B
    // ⚠️ 核心:开启全局事务
    @GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
    public void createOrder(OrderDTO order) {
        // 1. 调用订单服务创建订单(本地事务)
        orderFeignClient.create(order);
        // 2. 调用账户服务扣减余额(另一个微服务的本地事务)
        accountFeignClient.debit(order.getUserId(), order.getAmount());
        // 如果这里抛出异常,throw new RuntimeException("模拟异常");
        // Seata 会回滚上面 1 和 2 两个微服务的操作。
    }
}

建议: @GlobalTransactional 一定要加在最顶层的 Service 或 Controller 方法上,不能只加在 Feign 调用的内部方法上。


常见问题与最佳实践

  1. 全局锁冲突与性能

    • 在 AT 模式下,如果多个全局事务操作同一行数据,第二个事务会尝试获取全局锁,如果获取不到(被第一个事务锁住了),会等待(默认重试30次,间隔10ms),这可能导致接口响应变慢。
    • 解决方案: 对于高并发热点账户,建议使用 TCC 模式(Try-Confirm-Cancel)手动控制资源预留,或者使用乐观锁配合 Seata 的回滚。
  2. Feign 调用的事务传递

    • Seata 通过 RootContext 中的 XID 来传递全局事务上下文。
    • 如果你使用 Feign/RestTemplate,默认的 请求拦截器 (SeataInterceptor) 会自动将 xid 放到请求头中。
    • 注意: 如果使用了异步线程(@Async)或消息队列(MQ),xid 无法自动传递,需要手动传递RootContext.bind(xid)RootContext.unbind())。
  3. 与 MyBatis / MyBatis-Plus 集成

    • Seata 对 MyBatis 基础 CRUD 的支持很好,但如果是存储过程、批量操作、多表 JOIN 更新,生成的 SQL 解析(前/后镜像)可能不准确,导致回滚失败,此时建议切换到 TCCSaga 模式。
  4. TCC 模式的应用场景

    • 高并发热点数据(如秒杀库存):用 Try 冻结资源,Confirm 扣减或 Cancel 解冻。
    • 非数据库资源(如发送短信、调用第三方支付):需要手动编写 Try、Confirm、Cancel 三个方法。
    • 需要业务方自己处理幂等性、悬挂(Cancel 比 Try 先到)、空回滚(Try 没执行就 Cancel)。

构建一个可靠的分布式事务体系

  • 优先使用 AT 模式:对于大部分 CRUD 业务,AT 模式代码侵入最小。
  • 考虑性能/热点场景时使用 TCC:需要对业务有侵入,但性能最优。
  • 长事务/阶梯性回滚使用 Saga:适合多服务、复杂编排、需要补偿操作的场景(如订单流程)。
  • Seata-Server 必须高可用:部署多个 TC 节点,并使用 Nacos 或 Consul 做注册发现。
  • 监控与运维:启用 Seata 的 metrics(对接 Prometheus + Grafana),监控全局事务失败率、分支事务耗时,设置合理的超时时间(globalTransactionTimeout)。

Seata 的核心价值在于自动化,让开发者只需关注业务逻辑,把分布式事务的复杂性交给中间件处理。

抱歉,评论功能暂时关闭!