Spring Boot微服务架构中Seata分布式事务实战配置指南

微服务为什么需要分布式事务

单体应用拆分为微服务后,原本在同一个数据库事务中完成的操作分散到了多个服务中。比如电商下单场景:库存服务扣减库存、订单服务创建订单、账户服务扣减余额,这三个操作要么全部成功要么全部回滚。本地事务无法跨服务生效,分布式事务就是解决这个一致性问题的核心机制。

Seata的AT模式原理

Seata的AT(Automatic Transaction)模式是对业务代码侵入最小的分布式事务方案。其核心流程分为两阶段:

一阶段:拦截业务SQL,在执行前后记录数据快照(before-image和after-image),生成回滚日志(undo_log),一起提交到本地事务中。业务SQL和undo_log在同一个本地事务中原子提交。

二阶段提交:全局事务协调器通知各分支提交,异步删除undo_log即可(因为一阶段已经提交)。

二阶段回滚:读取undo_log中的before-image,反向补偿SQL恢复原始数据。

AT模式的优势在于业务代码零侵入——只需加一个@GlobalTransactional注解。

Seata Server部署

Seata Server(TC事务协调器)是独立的服务端组件,推荐使用Nacos作为注册中心和配置中心:

# 下载Seata Server 1.8+
wget https://github.com/seata/seata/releases/download/v1.8.0/seata-server-1.8.0.tar.gz
tar -xzf seata-server-1.8.0.tar.gz
cd seata

配置conf/application.yml:

server:
  port: 7091

seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP
  store:
    mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
      user: seata
      password: seata_pwd

在Nacos配置中心创建seataServer.properties,包含事务分组映射:

# 事务分组映射
service.vgroupMapping.order-service-group=default
service.vgroupMapping.inventory-service-group=default
service.vgroupMapping.account-service-group=default

# 锁和会话存储模式
store.lock.mode=db
store.session.mode=db

Spring Boot微服务集成Seata

每个参与分布式事务的微服务都需要集成Seata Client。以订单服务为例:

1. 添加Maven依赖

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.8.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2022.0.0.0</version>
</dependency>

2. 配置application.yml

seata:
  enabled: true
  application-id: order-service
  tx-service-group: order-service-group
  service:
    vgroup-mapping:
      order-service-group: default
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP
      application: seata-server

3. 创建undo_log表

每个参与分布式事务的数据库都需要创建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,
  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

业务代码中使用@GlobalTransactional

@Service
public class OrderService {

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

    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public Order createOrder(OrderDTO orderDTO) {
        // 1. 创建订单(本地事务)
        Order order = new Order();
        order.setUserId(orderDTO.getUserId());
        order.setProductId(orderDTO.getProductId());
        order.setCount(orderDTO.getCount());
        order.setAmount(orderDTO.getAmount());
        order.setStatus("CREATING");
        orderMapper.insert(order);
        
        // 2. 远程调用:扣减库存
        inventoryClient.deduct(orderDTO.getProductId(), orderDTO.getCount());
        
        // 3. 远程调用:扣减余额
        accountClient.debit(orderDTO.getUserId(), orderDTO.getAmount());
        
        // 4. 更新订单状态
        order.setStatus("COMPLETED");
        orderMapper.updateById(order);
        
        return order;
    }
}

只需在方法上加@GlobalTransactional注解,Seata会自动协调三个服务的本地事务。任何一个服务抛出异常,所有已提交的分支事务会自动回滚。

Feign拦截器传递XID

微服务间通过Feign调用时,需要将全局事务XID传递到下游服务。添加Feign拦截器:

@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);
        }
    }
}

// 同时需要配置Seata的拦截器,在接收端恢复XID
@Configuration
public class SeataFilterConfig {
    @Bean
    public SeataFilter seataFilter() {
        return new SeataFilter();
    }
}

常见问题与排障

问题1:全局事务超时

默认超时时间60秒。如果业务逻辑确实需要更长时间:

@GlobalTransactional(timeoutMills = 300000)  // 5分钟

问题2:undo_log找不到导致回滚失败

undo_log被手动清理或数据库异常丢数据。检查undo_log表数据完整性,必要时手动清理脏数据并重新初始化。

问题3:全局锁冲突

并发场景下多个全局事务争抢同一行数据的全局锁。优化方式:减少全局事务持有锁的时间,将非关键操作移到事务外;或者使用GlobalLock注解在本地事务中获取全局锁。

生产环境注意事项

1. Seata Server必须集群部署(至少2个节点),避免TC单点故障。

2. 使用数据库存储模式(store.mode=db),不要用file模式,file模式不支持集群。

3. 监控Seata事务指标:事务成功率、平均耗时、回滚率。回滚率超过10%需要排查业务逻辑。

4. 对非核心链路考虑使用最终一致性方案(消息表+定时补偿),降低Seata的负载。

5. 做好幂等设计。分布式事务重试可能导致同一操作执行多次,下游接口必须保证幂等性。

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

(0)
小编小编
上一篇 13小时前
下一篇 13小时前

相关推荐