Spring Boot微服务分布式事务实战:Seata AT模式集成与踩坑指南

微服务架构分布式事务的必要性

单体应用中,数据库本地事务(ACID)就能保证数据一致性。拆分成微服务后,一个业务操作可能跨越多个服务,每个服务有独立的数据库,本地事务无法覆盖跨服务的数据一致性。以电商下单为例:订单服务创建订单、库存服务扣减库存、账户服务扣减余额,三步必须全部成功或全部回滚。

Spring Boot框架中处理分布式事务的主流方案:

| 方案 | 一致性 | 性能 | 侵入性 | 适用场景 |
|——|——–|——|——–|———-|
| 2PC(XA) | 强一致 | 低 | 低 | 传统企业应用 |
| TCC | 强一致 | 中 | 高 | 资金交易 |
| Saga | 最终一致 | 高 | 中 | 长流程业务 |
| Seata AT | 强一致 | 中高 | 低 | 通用业务 |

Seata AT模式因侵入性最低、对业务代码几乎零改造,成为Spring Boot微服务架构中分布式事务的首选方案。

Seata AT模式的工作原理

AT模式的核心是”自动拦截SQL,自动补偿”:

1. 一阶段:拦截业务SQL,执行前保存before-image,执行后保存after-image,生成undo_log,同时提交本地事务
2. 二阶段提交:异步删除undo_log
3. 二阶段回滚:根据undo_log的before-image反向补偿,恢复数据

对比TCC需要业务方实现Try/Confirm/Cancel三个接口,AT模式只需要加一个@GlobalTransactional注解。

但这种自动化的代价是:Seata需要代理数据源,拦截所有SQL来构建undo_log。对SQL的兼容性是AT模式最大的坑。

Seata Server部署与配置

Seata 2.x版本的Server端支持多种存储模式,生产环境推荐使用数据库存储(避免Server重启丢失事务日志):

# application.yml - Seata Server配置
server:
  port: 8091

seata:
  store:
    mode: db
    db:
      datasource: druid
      db-type: mysql
      url: jdbc:mysql://mysql-host:3306/seata?rewriteBatchedStatements=true
      user: seata
      password: ${SEATA_DB_PASS}
      min-conn: 5
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock

  # 注册中心
  registry:
    type: nacos
    nacos:
      server-addr: nacos-host:8848
      namespace: seata
      group: DEFAULT_GROUP
      application: seata-server

  # 配置中心
  config:
    type: nacos
    nacos:
      server-addr: nacos-host:8848
      namespace: seata
      group: DEFAULT_GROUP

Seata Server的数据库建表脚本在官方GitHub的script/server/db目录下,需要提前在MySQL中创建。

Spring Boot客户端集成步骤

1. 添加依赖

<!-- pom.xml -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

<!-- 如果使用Spring Cloud,用这个 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2023.0.1.2</version>
</dependency>

2. 客户端配置

# application.yml
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my-tx-group
  service:
    vgroup-mapping:
      my-tx-group: default
  registry:
    type: nacos
    nacos:
      server-addr: nacos-host:8848
      namespace: seata
      group: DEFAULT_GROUP

3. 业务数据库建undo_log表

每个参与分布式事务的微服务的数据库都需要这张表:

CREATE TABLE IF NOT EXISTS undo_log (
  branch_id     BIGINT       NOT NULL,
  xid           VARCHAR(128) NOT NULL,
  context       VARCHAR(128) NOT NULL,
  rollback_info LONGBLOB     NOT NULL,
  log_status    INT(11)      NOT NULL,
  log_created   DATETIME(6)  NOT NULL,
  log_modified  DATETIME(6)  NOT NULL,
  PRIMARY KEY (branch_id),
  KEY idx_xid (xid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

4. 在业务方法上加注解

@Service
public class OrderService {

    @GlobalTransactional(timeoutMills = 60000, name = "create-order")
    public Order createOrder(OrderDTO orderDTO) {
        // 1. 创建订单(本地事务)
        Order order = orderMapper.insert(orderDTO);

        // 2. 扣减库存(远程调用)
        inventoryClient.deduct(orderDTO.getProductId(), orderDTO.getQuantity());

        // 3. 扣减账户余额(远程调用)
        accountClient.debit(orderDTO.getUserId(), orderDTO.getAmount());

        return order;
    }
}

高并发场景下的锁冲突与优化

AT模式在一阶段就会获取全局锁(记录在Seata Server的lock_table中),防止脏写。但这也带来了锁冲突问题:

当两个分布式事务同时修改同一行数据时,后到的事务会因获取全局锁超时而回滚。在高并发库存扣减场景下,这个问题尤为严重。

优化方案:

1. 缩短全局事务时间:全局事务持有锁的时间越长,冲突概率越高。把非核心操作(如发通知、记日志)移出全局事务。

@GlobalTransactional
public void createOrder(OrderDTO dto) {
    // 只放核心数据操作
    Order order = orderMapper.insert(dto);
    inventoryClient.deduct(dto.getProductId(), dto.getQuantity());
    accountClient.debit(dto.getUserId(), dto.getAmount());
}

// 非核心操作在事务外执行
@Async
public void sendNotification(Long orderId) {
    notificationClient.send(orderId);
}

2. 降低隔离级别到读未提交:默认全局锁的隔离级别是读已提交,如果业务允许短暂读到未提交数据,可以降低隔离级别减少锁持有时间:

@GlobalTransactional(isolation = Isolation.READ_UNCOMMITTED)

3. 热点数据用Redis预扣减:库存等热点数据先用Redis原子操作预扣减,再异步同步到数据库,绕过分布式事务的锁竞争。

消息中间件与最终一致性方案

不是所有业务都需要强一致。对于长流程业务(如退款、物流),Saga模式或基于消息中间件的最终一致性更合适:

// 退款Saga编排
@Saga
public class RefundSaga {

    @SagaStep(compensateMethod = "cancelRefund")
    public void initiateRefund(RefundRequest req) {
        accountClient.refund(req.getUserId(), req.getAmount());
    }

    @SagaStep(compensateMethod = "restoreInventory")
    public void restoreStock(RefundRequest req) {
        inventoryClient.restore(req.getProductId(), req.getQuantity());
    }

    @SagaStep
    public void notifyUser(RefundRequest req) {
        notificationClient.sendRefundNotice(req.getUserId());
    }
}

结合消息中间件实现可靠消息最终一致性:业务服务发消息前先写本地消息表,定时任务扫描消息表确保消息投递成功。RocketMQ的事务消息机制本质相同,但实现更优雅,不需要本地消息表。

Seata与RocketMQ事务消息的选型标准:跨3个以上服务的强一致业务用Seata AT,2个服务间的一致性用事务消息,长流程异步业务用Saga。

生产环境踩坑清单

1. 数据源代理失效:Seata AT必须代理DataSource才能拦截SQL。如果项目中配置了多个DataSource(如读写分离),确保@GlobalTransactional使用的是Seata代理的那个。检查方法:打断点看DataSource的实现类是否为DataSourceProxy。

2. undo_log序列化失败:MySQL的JSON/TEXT列在undo_log的rollback_info中可能超过LONGBLOB限制。避免在分布式事务中操作超过1MB的单行数据。

3. 回滚时before-image为空:发生在事务提交后、undo_log被清理前,数据被其他事务直接修改(绕过Seata全局锁)。AT模式能检测到这种脏写并抛出异常,但需要人工介入。

4. 全局事务超时:默认60秒。如果业务确实需要更长执行时间,调整timeoutMills参数,但不要设太大——超时的事务会被Seata Server自动回滚,如果此时业务方还在执行,可能导致部分回滚的不一致状态。

5. 服务治理层面的雪崩:Seata Server是单点(即便是集群部署)。Server不可用时不影响已提交的事务,但新事务无法开启。在服务治理策略中,Seata Server降级后应自动切换到本地事务模式,而非让整个服务不可用。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-fen-bu-shi-shi-wu-shi-zhan-seataat-mo/

(0)
小编小编
上一篇 2026年7月30日
下一篇 2026年7月30日

相关推荐

Spring Boot微服务分布式事务实战:Seata AT模式踩坑与排查手册

微服务分布式事务的工程取舍

微服务拆分后,跨服务的数据一致性是绕不开的问题。分布式事务不是要不要做的问题,而是在哪个环节做、用什么模式做、能做到什么一致性级别的问题。Seata的AT模式是目前Spring Boot微服务中落地成本最低的方案——对业务代码侵入小,只需要加@GlobalTransactional注解。但低成本不代表低风险,AT模式的隐式补偿机制在特定场景下会出问题,这些问题在生产环境中排查成本极高。

分布式事务的使用原则:能用本地事务解决的绝不上分布式事务,能用最终一致性解决的绝不要求强一致性。分布式事务是最后的兜底手段,不是首选方案。

Seata AT模式工作原理与隐藏代价

AT模式的核心流程分两阶段:

一阶段(Branch Commit):拦截业务SQL,在执行前查询数据的前镜像(Before Image),执行后查询后镜像(After Image),将两份镜像存入undo_log表,然后提交本地事务。这个阶段业务SQL和undo_log写入在同一个本地事务中完成。

二阶段(全局提交或回滚):全局提交时异步清理undo_log;全局回滚时根据undo_log中的前镜像反向补偿数据。

隐藏代价:
性能开销:每条业务SQL额外产生2次SELECT(前镜像+后镜像)+ 1次INSERT(undo_log),写放大3倍起
全局锁:一阶段提交后到全局提交期间,相关行被全局锁锁定,其他全局事务无法修改同一行
undo_log膨胀:高并发写入场景下undo_log表数据量暴增,需要定期清理

Spring Boot集成Seata的完整配置

// pom.xml依赖
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.8.0</version>
</dependency>

// application.yml核心配置
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my-tx-group
  service:
    vgroup-mapping:
      my-tx-group: default
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata

每个参与分布式事务的数据库必须创建undo_log表:

-- undo_log表结构(每个业务库都要建)
CREATE TABLE undo_log (
    id            BIGINT       NOT NULL AUTO_INCREMENT,
    branch_id     BIGINT       NOT NULL,
    xid           VARCHAR(128) 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,
    PRIMARY KEY (id),
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE=InnoDB;

业务代码集成与@GlobalTransactional使用

// 订单服务 - 分布式事务入口
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private AccountFeignClient accountClient;
    
    @Autowired
    private StorageFeignClient storageClient;
    
    @GlobalTransactional(
        name = "create-order",
        rollbackFor = Exception.class,
        timeoutMills = 60000
    )
    public Order createOrder(OrderDTO dto) {
        Order order = new Order();
        order.setUserId(dto.getUserId());
        order.setProductId(dto.getProductId());
        order.setAmount(dto.getAmount());
        order.setStatus("CREATING");
        orderMapper.insert(order);
        
        Result accountResult = accountClient.debit(dto.getUserId(), dto.getAmount());
        if (!accountResult.isSuccess()) {
            throw new BusinessException("账户扣减失败: " + accountResult.getMessage());
        }
        
        Result storageResult = storageClient.deduct(dto.getProductId(), dto.getQuantity());
        if (!storageResult.isSuccess()) {
            throw new BusinessException("库存扣减失败: " + storageResult.getMessage());
        }
        
        order.setStatus("CREATED");
        orderMapper.updateStatus(order);
        return order;
    }
}

远程调用的服务方也需要配置Seata数据源代理和undo_log。Feign客户端需要传递XID:

// Feign拦截器传递全局事务ID
@Component
public class SeataFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        String xid = RootContext.getXID();
        if (StringUtils.isNotBlank(xid)) {
            template.header(RootContext.KEY_XID, xid);
        }
    }
}

生产环境高频踩坑与排查方法

坑1:数据源代理未生效

Seata通过代理DataSource拦截SQL生成undo_log。如果MyBatis或Druid的数据源配置在Seata代理之前初始化,undo_log不会写入,回滚时数据无法恢复。

// 排查方法:检查DataSource是否被Seata代理
@Autowired
private DataSource dataSource;

log.info("DataSource type: {}", dataSource.getClass().getName());
// 正确输出应包含SeataDataSourceProxy

// 修复:确保Seata自动配置在DataSource之后
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class SeataDataSourceConfig {
    @Bean
    @Primary
    public DataSource dataSource(DataSource originalDataSource) {
        return new DataSourceProxy(originalDataSource);
    }
}

坑2:全局锁超时导致事务失败

多个全局事务并发修改同一行数据时,后到的事务在获取全局锁时超时。默认超时30秒,高并发场景需要调整:

# Seata Server端配置
service.default.grouplist=127.0.0.1:8091
store.lock.mode=db

# 客户端配置
seata:
  client:
    rm:
      lock:
        retry-interval: 10
        retry-times: 30
        lock-retry-policy: optimize

坑3:undo_log序列化与字段变更不兼容

业务表新增字段后,历史undo_log的前镜像不包含新字段,回滚时可能出现字段缺失。解决方案:上线DDL变更时同步清空undo_log表(在无进行中事务时操作),并确保所有服务实例同步更新。

坑4:Feign调用超时触发本地事务回滚但全局事务未回滚

Feign超时导致本地事务回滚抛异常,但如果异常被Spring Retry捕获并重试成功,Seata的全局事务已经标记了第一次调用的分支事务为回滚状态,后续补偿会出现状态冲突。解决方案:关闭Feign的Retryer,让失败直接传播到全局事务层处理。

Seata全局事务监控与告警

生产环境必须监控Seata的全局事务状态,核心指标:

// Prometheus指标采集配置
# 全局事务总数
seata_transaction_total{status="committed|rollbacked"}
# 全局事务平均耗时
seata_transaction_duration_seconds_avg
# 全局锁等待超时次数
seata_global_lock_timeout_total
# undo_log表数据量
seata_undo_log_records

告警规则:全局事务回滚率超过5%触发P2告警,全局事务平均耗时超过3秒触发P3告警。这两个指标是分布式事务健康度最直接的反映。定期巡检undo_log表的数据量,如果堆积超过10万条,检查异步清理线程是否正常工作。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-fen-bu-shi-shi-wu-shi-zhan-seataat-mo/

(0)
小编小编
上一篇 2026年7月29日
下一篇 2026年7月29日

相关推荐

Spring Boot微服务分布式事务实战:Seata AT模式踩坑与排查手册

微服务分布式事务的工程取舍

微服务拆分后,跨服务的数据一致性是绕不开的问题。分布式事务不是要不要做的问题,而是在哪个环节做、用什么模式做、能做到什么一致性级别的问题。Seata的AT模式是目前Spring Boot微服务中落地成本最低的方案——对业务代码侵入小,只需要加@GlobalTransactional注解。但低成本不代表低风险,AT模式的隐式补偿机制在特定场景下会出问题,这些问题在生产环境中排查成本极高。

分布式事务的使用原则:能用本地事务解决的绝不上分布式事务,能用最终一致性解决的绝不要求强一致性。分布式事务是最后的兜底手段,不是首选方案。

Seata AT模式工作原理与隐藏代价

AT模式的核心流程分两阶段:

一阶段(Branch Commit):拦截业务SQL,在执行前查询数据的前镜像(Before Image),执行后查询后镜像(After Image),将两份镜像存入undo_log表,然后提交本地事务。这个阶段业务SQL和undo_log写入在同一个本地事务中完成。

二阶段(全局提交或回滚):全局提交时异步清理undo_log;全局回滚时根据undo_log中的前镜像反向补偿数据。

隐藏代价:
性能开销:每条业务SQL额外产生2次SELECT(前镜像+后镜像)+ 1次INSERT(undo_log),写放大3倍起
全局锁:一阶段提交后到全局提交期间,相关行被全局锁锁定,其他全局事务无法修改同一行
undo_log膨胀:高并发写入场景下undo_log表数据量暴增,需要定期清理

Spring Boot集成Seata的完整配置

// pom.xml依赖
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.8.0</version>
</dependency>

// application.yml核心配置
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my-tx-group
  service:
    vgroup-mapping:
      my-tx-group: default
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata

每个参与分布式事务的数据库必须创建undo_log表:

-- undo_log表结构(每个业务库都要建)
CREATE TABLE undo_log (
    id            BIGINT       NOT NULL AUTO_INCREMENT,
    branch_id     BIGINT       NOT NULL,
    xid           VARCHAR(128) 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,
    PRIMARY KEY (id),
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE=InnoDB;

业务代码集成与@GlobalTransactional使用

// 订单服务 - 分布式事务入口
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private AccountFeignClient accountClient;
    
    @Autowired
    private StorageFeignClient storageClient;
    
    @GlobalTransactional(
        name = "create-order",
        rollbackFor = Exception.class,
        timeoutMills = 60000  // 全局事务超时60秒
    )
    public Order createOrder(OrderDTO dto) {
        // 1. 创建订单(本地事务)
        Order order = new Order();
        order.setUserId(dto.getUserId());
        order.setProductId(dto.getProductId());
        order.setAmount(dto.getAmount());
        order.setStatus("CREATING");
        orderMapper.insert(order);
        
        // 2. 扣减账户余额(远程调用)
        Result accountResult = accountClient.debit(dto.getUserId(), dto.getAmount());
        if (!accountResult.isSuccess()) {
            throw new BusinessException("账户扣减失败: " + accountResult.getMessage());
        }
        
        // 3. 扣减库存(远程调用)
        Result storageResult = storageClient.deduct(dto.getProductId(), dto.getQuantity());
        if (!storageResult.isSuccess()) {
            throw new BusinessException("库存扣减失败: " + storageResult.getMessage());
        }
        
        // 4. 更新订单状态
        order.setStatus("CREATED");
        orderMapper.updateStatus(order);
        
        return order;
    }
}

远程调用的服务方也需要配置Seata数据源代理和undo_log。Feign客户端需要传递XID:

// Feign拦截器传递全局事务ID
@Component
public class SeataFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        String xid = RootContext.getXID();
        if (StringUtils.isNotBlank(xid)) {
            template.header(RootContext.KEY_XID, xid);
        }
    }
}

生产环境高频踩坑与排查方法

坑1:数据源代理未生效

Seata通过代理DataSource拦截SQL生成undo_log。如果MyBatis或Druid的数据源配置在Seata代理之前初始化,undo_log不会写入,回滚时数据无法恢复。

// 排查方法:检查DataSource是否被Seata代理
@Autowired
private DataSource dataSource;

// 启动后打印DataSource类型
log.info("DataSource type: {}", dataSource.getClass().getName());
// 正确输出应包含SeataDataSourceProxy
// 错误输出: DruidDataSource或HikariDataSource

// 修复:确保Seata自动配置在DataSource之后
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class SeataDataSourceConfig {
    @Bean
    @Primary
    public DataSource dataSource(DataSource originalDataSource) {
        return new DataSourceProxy(originalDataSource);
    }
}

坑2:全局锁超时导致事务失败

多个全局事务并发修改同一行数据时,后到的事务在获取全局锁时超时。默认超时30秒,高并发场景需要调整:

# Seata Server端配置
service.default.grouplist=127.0.0.1:8091
store.lock.mode=db
store.db.datasource=druid

# 客户端配置
seata:
  client:
    rm:
      lock:
        retry-interval: 10     # 获取锁重试间隔(ms)
        retry-times: 30        # 重试次数
        lock-retry-policy: optimize  # 优化锁策略

坑3:undo_log序列化与字段变更不兼容

业务表新增字段后,历史undo_log的前镜像不包含新字段,回滚时可能出现字段缺失。解决方案:上线DDL变更时同步清空undo_log表(在无进行中事务时操作),并确保所有服务实例同步更新。

坑4:Feign调用超时触发本地事务回滚但全局事务未回滚

Feign超时导致本地事务回滚抛异常,但如果异常被Spring Retry捕获并重试成功,Seata的全局事务已经标记了第一次调用的分支事务为回滚状态,后续补偿会出现状态冲突。解决方案:关闭Feign的Retryer,让失败直接传播到全局事务层处理。

Seata全局事务监控与告警

生产环境必须监控Seata的全局事务状态,核心指标:

// Prometheus指标采集配置
# 全局事务总数
seata_transaction_total{status="committed|rollbacked"}
# 全局事务平均耗时
seata_transaction_duration_seconds_avg
# 全局锁等待超时次数
seata_global_lock_timeout_total
# undo_log表数据量
seata_undo_log_records

告警规则:全局事务回滚率超过5%触发P2告警,全局事务平均耗时超过3秒触发P3告警。这两个指标是分布式事务健康度最直接的反映。定期巡检undo_log表的数据量,如果堆积超过10万条,检查异步清理线程是否正常工作。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-fen-bu-shi-shi-wu-shi-zhan-seataat-mo/

(0)
小编小编
上一篇 2026年7月29日
下一篇 2026年7月29日

相关推荐

Spring Boot微服务分布式事务实战:Seata AT模式从配置到生产调优

分布式事务问题的典型场景

微服务拆分后,一个业务操作可能跨多个服务,每个服务有独立的数据库。下单场景涉及订单服务扣减库存、账户服务扣减余额、积分服务增加积分——三个操作必须全部成功或全部回滚。本地事务无法覆盖跨库操作,这就是分布式事务要解决的问题。

Seata的AT模式是对业务代码侵入最小的方案,只需要一个@GlobalTransactional注解。其原理是在SQL执行前后自动生成回滚日志(undo log),事务失败时自动根据undo log反向补偿。

Seata Server部署与Nacos注册中心集成

Seata Server是事务协调器(TC),负责管理全局事务的提交和回滚。生产环境推荐使用Nacos作为注册中心和配置中心。

# docker部署Seata Server
version: '3'
services:
  seata-server:
    image: seataio/seata-server:2.2.0
    container_name: seata-server
    ports:
      - "8091:8091"
      - "7091:7091"
    environment:
      - SEATA_IP=192.168.1.100
      - SEATA_PORT=8091
    volumes:
      - ./seata-config:/seata-server/resources
    restart: always

# seata-config/application.yml 关键配置
seata:
  config:
    type: nacos
    nacos:
      server-addr: 192.168.1.10:8848
      namespace: seata
      group: SEATA_GROUP
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.1.10:8848
      namespace: seata
      group: SEATA_GROUP
  store:
    mode: db
    db:
      datasource: druid
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.1.10:3306/seata?rewriteBatchedStatements=true
      user: seata
      password: seata_pwd
      min-conn: 5
      max-conn: 30

微服务客户端集成:数据源代理与undo log表

每个参与分布式事务的微服务需要配置Seata数据源代理,并在业务库中创建undo_log表。

<!-- pom.xml 依赖 -->
<dependency>
  <groupId>io.seata</groupId>
  <artifactId>seata-spring-boot-starter</artifactId>
  <version>2.2.0</version>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  <version>2023.0.1.0</version>
</dependency>
-- 每个业务库都需要创建undo_log表
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',
  `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
  `log_status`    INT(11)      NOT NULL COMMENT '0:normal, 1:defensed',
  `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 DEFAULT CHARSET=utf8mb4 COMMENT='AT transaction undo log';

业务代码实现:@GlobalTransactional注解与Feign调用

在业务入口方法上添加@GlobalTransactional注解,Seata通过拦截SQL自动管理事务上下文。

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private AccountClient accountClient;
    @Autowired
    private InventoryClient inventoryClient;

    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public OrderResult createOrder(OrderRequest request) {
        // 1. 创建订单(本地事务,Seata自动代理)
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setProductId(request.getProductId());
        order.setQuantity(request.getQuantity());
        order.setAmount(request.getAmount());
        order.setStatus("CREATED");
        orderMapper.insert(order);

        // 2. 扣减库存(远程调用,XID自动传播)
        inventoryClient.deduct(request.getProductId(), request.getQuantity());

        // 3. 扣减账户余额(远程调用)
        accountClient.debit(request.getUserId(), request.getAmount());

        // 4. 更新订单状态
        order.setStatus("PAID");
        orderMapper.updateStatus(order);

        return OrderResult.success(order.getId());
    }
}

// Feign客户端——XID通过请求头自动传播
@FeignClient(name = "account-service", fallback = AccountFallback.class)
public interface AccountClient {
    @PostMapping("/account/debit")
    Result debit(@RequestParam("userId") Long userId,
                 @RequestParam("amount") BigDecimal amount);
}

XID(全局事务ID)通过HTTP Header在服务间自动传播,这是Seata Spring Cloud集成的核心机制。Feign拦截器自动将当前XID写入请求头,对端服务自动解析并绑定到当前分支事务。

生产环境调优:超时、重试与全局锁

AT模式的核心风险是全局锁。一阶段本地事务提交后,Seata会对修改的数据加全局锁,直到全局事务提交或回滚才释放。如果全局事务耗时过长,全局锁会阻塞其他事务。

# 超时与重试配置
seata:
  client:
    rm:
      async-commit-buffer-limit: 10000
      report-retry-count: 5
      lock:
        retry-interval: 10    # 全局锁重试间隔(ms)
        retry-times: 30       # 全局锁重试次数
    tm:
      commit-retry-count: 3
      rollback-retry-count: 3
      default-global-transaction-timeout: 60000  # 全局事务超时60秒

全局事务超时时间需要根据业务场景设置。跨三个服务的订单创建通常5秒内完成,但涉及外部支付网关时可能需要30秒以上。超时后Seata自动回滚,避免全局锁长期占用。

undo log的清理同样重要。已完成的全局事务的undo log默认保留7天,高频业务场景下undo log表可能积累大量数据,影响回滚性能。配置定时清理任务:

@Scheduled(cron = "0 0 3 * * ?")
public void cleanUndoLog() {
    int deleted = undoLogMapper.cleanExpiredLogs(
        LocalDate.now().minusDays(7)
    );
    log.info("清理undo_log记录数: {}", deleted);
}

分布式事务没有银弹,Seata AT模式适合对一致性要求高、并发量中等的场景。超高频场景考虑TCC模式或最终一致性方案(如本地消息表+定时补偿),低频但强一致场景用AT模式最省心。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-fen-bu-shi-shi-wu-shi-zhan-seataat-mo/

(0)
小编小编
上一篇 2026年7月23日
下一篇 2026年7月23日

相关推荐