Spring Boot微服务分布式事务处理:Seata AT模式集成与生产调优

微服务架构中的分布式事务挑战

微服务拆分后,原本在单体应用中一个数据库事务就能保证的数据一致性,变成了跨服务、跨数据库的分布式事务问题。比如电商下单流程涉及订单服务、库存服务、账户服务三个独立数据库,任何一个环节失败都需要全部回滚。Spring Boot框架本身不提供分布式事务解决方案,需要引入专门的事务协调器。

Seata是微服务架构中使用最广泛的开源分布式事务框架。它提供四种事务模式:AT、TCC、SAGA、XA。AT模式对业务代码侵入最小,通过自动生成补偿SQL实现回滚,适合大多数CRUD场景。TCC模式需要手动编写Try/Confirm/Cancel三个接口,灵活度高但代码量大。本文聚焦AT模式的生产实践。

Seata Server部署与配置

Seata架构中,TC(Transaction Coordinator)是独立的事务协调器,管理全局事务的提交和回滚。TM(Transaction Manager)定义全局事务的边界,RM(Resource Manager)管理分支事务。

部署TC Server,使用Nacos作为注册中心,MySQL存储事务日志:

# file.conf - 存储模式配置
store {
  mode = "db"
  
  db {
    datasource = "druid"
    dbType = "mysql"
    driverClassName = "com.mysql.cj.jdbc.Driver"
    url = "jdbc:mysql://192.168.1.100:3306/seata?useUnicode=true&characterEncoding=utf8"
    user = "seata"
    password = "seata_pwd"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
  }
}
# registry.conf - 注册中心配置
registry {
  type = "nacos"
  nacos {
    application = "seata-server"
    serverAddr = "192.168.1.100:8848"
    group = "SEATA_GROUP"
    namespace = "public"
    cluster = "default"
  }
}

config {
  type = "nacos"
  nacos {
    serverAddr = "192.168.1.100:8848"
    group = "SEATA_GROUP"
    namespace = "public"
  }
}

TC Server的数据库需要先执行建表脚本:

-- Seata Server存储表
CREATE TABLE IF NOT EXISTS `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint,
  `status` tinyint NOT NULL,
  `application_id` varchar(32),
  `transaction_service_group` varchar(32),
  `transaction_name` varchar(128),
  `timeout` int,
  `begin_time` bigint,
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  PRIMARY KEY (`xid`),
  KEY `idx_status_gmt_modified` (`status`, `gmt_modified`)
);

CREATE TABLE IF NOT EXISTS `branch_table` (
  `branch_id` bigint NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint,
  `resource_group_id` varchar(32),
  `resource_id` varchar(256),
  `branch_type` varchar(8),
  `status` tinyint,
  `client_id` varchar(64),
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
);

CREATE TABLE IF NOT EXISTS `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96),
  `transaction_id` bigint,
  `branch_id` bigint,
  `resource_id` varchar(256),
  `table_name` varchar(32),
  `pk` varchar(36),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  PRIMARY KEY (`row_key`)
);

业务服务集成Seata AT模式

以订单服务为入口,在pom.xml中引入Seata依赖:

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  <version>2022.0.0.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: 192.168.1.100:8848
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: 192.168.1.100:8848
      group: SEATA_GROUP
  data-source-proxy-mode: AT

每个参与分布式事务的数据库必须创建undo_log表,Seata用此表记录数据变更前快照用于回滚:

-- 在订单库、库存库、账户库各自执行
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 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`)
);

业务代码中,只需在入口方法加@GlobalTransactional注解:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private InventoryFeignClient inventoryClient;
    @Autowired
    private AccountFeignClient accountClient;

    @GlobalTransactional(name = "createOrder", timeoutMills = 60000, rollbackFor = Exception.class)
    public OrderResult createOrder(OrderDTO orderDTO) {
        // 1. 创建订单(操作订单库)
        Order order = new Order();
        order.setUserId(orderDTO.getUserId());
        order.setProductId(orderDTO.getProductId());
        order.setQuantity(orderDTO.getQuantity());
        order.setAmount(orderDTO.getAmount());
        order.setStatus("CREATED");
        orderMapper.insert(order);

        // 2. 扣减库存(远程调用库存服务,操作库存库)
        Result<Void> inventoryResult = inventoryClient.deduct(orderDTO.getProductId(), orderDTO.getQuantity());
        if (!inventoryResult.isSuccess()) {
            throw new BusinessException("库存扣减失败: " + inventoryResult.getMessage());
        }

        // 3. 扣减余额(远程调用账户服务,操作账户库)
        Result<Void> accountResult = accountClient.deduct(orderDTO.getUserId(), orderDTO.getAmount());
        if (!accountResult.isSuccess()) {
            throw new BusinessException("余额扣减失败: " + accountResult.getMessage());
        }

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

        return new OrderResult(order.getId(), "SUCCESS");
    }
}

库存服务和账户服务的本地方法仍用@Transactional注解,Seata的DataSourceProxy会自动拦截SQL执行,生成undo_log记录。

AT模式工作原理与回滚机制

AT模式的全局事务执行流程:

阶段一(执行业务SQL):拦截SQL解析出操作类型和影响行,查询变更前数据生成before image,执行SQL,查询变更后数据生成after image。将before/after image序列化存入undo_log表。本地事务在阶段一提交——业务SQL和undo_log在同一个本地事务中,保证原子性。

阶段二(提交或回滚):全局事务提交时,异步删除undo_log记录。全局事务回滚时,根据undo_log中的before image生成反向SQL(UPDATE变UPDATE、INSERT变DELETE、DELETE变INSERT),执行反向SQL恢复数据,然后删除undo_log。

这里的关键设计是阶段一就提交本地事务释放数据库锁,通过全局锁(lock_table)保证隔离性。如果全局事务回滚时数据已被其他事务修改,Seata会检测到after image不一致并重试或报错。

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

AT模式在高并发场景下的主要瓶颈是全局锁竞争。当多个全局事务同时修改同一行数据时,后到的请求需要等待前一个事务释放全局锁。消息中间件可以在一定程度上削峰,但核心还是要减少锁持有时间。

// 调优方向1: 缩小事务范围
// 坏的实践 — 把不需要事务的操作放在@GlobalTransactional中
@GlobalTransactional
public void processOrder(OrderDTO dto) {
    // 查询操作不需要在分布式事务中
    ProductInfo product = productClient.getInfo(dto.getProductId()); // 不必要
    UserInfo user = userClient.getInfo(dto.getUserId());             // 不必要
    
    // 只有写操作才需要在事务中
    orderMapper.insert(order);
    inventoryClient.deduct(...);
}
// 调优方向2: 合理设置超时和重试
@GlobalTransactional(
    name = "createOrder",
    timeoutMills = 30000,           // 超时时间不宜过长
    rollbackFor = Exception.class
)

// Seata配置中调整锁重试参数
seata:
  client:
    rm:
      lock:
        retry-interval: 10          // 锁重试间隔10ms
        retry-times: 30             // 最多重试30次
        retry-policy-branch-rollback-on-conflict: true

服务治理层面,需要监控Seata的事务成功率、平均耗时、全局锁等待时间等指标。将Seata metrics接入Prometheus:

# seata配置开启metrics
metrics:
  enabled: true
  registry-type: compact
  exporter-list: prometheus
  exporter-prometheus-port: 9898

# Prometheus scrape配置
scrape_configs:
- job_name: 'seata'
  static_configs:
  - targets: ['seata-server:9898']

API接口规范方面,远程调用的Feign接口需要正确传播全局事务ID(XID)。Seata默认通过HTTP Header传播XID,但自定义Feign拦截器时不小心覆盖Header会导致XID丢失。排查这类问题需要在Feign拦截器中检查XID是否存在:

@Component
public class SeataFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        String xid = RootContext.getXID();
        if (xid != null) {
            template.header(RootContext.KEY_XID, xid);
        }
    }
}

业务中台建设中消息中间件与分布式事务的配合也需要注意。如果使用RocketMQ事务消息替代Seata的TCC模式,可以减少服务间同步调用,但消息保证的是最终一致性而非强一致性。选择Seata AT还是事务消息,取决于业务对一致性实时性的要求。订单创建场景用Seata AT,库存异步扣减场景用事务消息,两者配合使用是常见架构。

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

(0)
小编小编
上一篇 2026年8月1日
下一篇 2026年8月1日

相关推荐

Spring Boot微服务分布式事务处理:Seata AT模式集成与高并发场景实战

微服务架构下,跨服务的数据一致性是后端开发面临的核心挑战。Seata的AT模式通过两阶段提交和SQL解析实现自动化分布式事务,对业务代码侵入性低。本文记录Spring Boot集成Seata的完整流程。

Seata AT模式工作原理

AT模式分为两个阶段。第一阶段:拦截业务SQL,解析SQL语义找到操作的行记录,在事务提交前保存数据快照(before image)和操作后的数据快照(after image)到undo_log表,并在分支事务表中注册分支事务。第二阶段:如果全局事务决议提交,异步删除undo_log记录;如果决议回滚,根据before image生成反向SQL恢复数据。

核心角色说明:

  • TC(Transaction Coordinator):事务协调器,维护全局事务和分支事务状态
  • TM(Transaction Manager):事务管理器,定义全局事务范围,负责开启/提交/回滚
  • RM(Resource Manager):资源管理器,管理分支事务上的资源,向TC注册分支事务

Seata Server部署配置

使用Docker部署Seata Server,以Nacos作为注册中心和配置中心:

docker run -d --name seata-server \
  -p 8091:8091 \
  -p 7091:7091 \
  -e SEATA_IP=192.168.1.50 \
  -e SEATA_PORT=8091 \
  -e STORE_MODE=db \
  -e SEATA_CONFIG_NAME=file:/root/seata-config/registry \
  -v /data/seata/config:/root/seata-config \
  apache/seata-server:2.0.0

registry.conf配置文件:

registry {
  type = "nacos"
  nacos {
    application = "seata-server"
    serverAddr = "192.168.1.40:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
  }
}

config {
  type = "nacos"
  nacos {
    serverAddr = "192.168.1.40:8848"
    group = "SEATA_GROUP"
    namespace = ""
    dataId = "seataServer.properties"
  }
}

Spring Boot微服务集成Seata客户端

添加Maven依赖:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>2.3.0</version>
</dependency>

application.yml配置:

seata:
  enabled: true
  application-id: order-service
  tx-service-group: yunthe_tx_group
  service:
    vgroup-mapping:
      yunthe_tx_group: default
    grouplist:
      default: 192.168.1.50:8091
  registry:
    type: nacos
    nacos:
      server-addr: 192.168.1.40:8848
      group: SEATA_GROUP
      namespace: ""
  config:
    type: nacos
    nacos:
      server-addr: 192.168.1.40:8848
      group: SEATA_GROUP
      namespace: ""
  data-source-proxy-mode: AT

业务代码实现:订单创建分布式事务

以电商下单场景为例,涉及订单服务、库存服务和账户服务三个微服务:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private StorageFeignClient storageFeignClient;
    @Autowired
    private AccountFeignClient accountFeignClient;

    @GlobalTransactional(name = "createOrder", rollbackFor = Exception.class)
    public Order createOrder(OrderDTO dto) {
        // 1. 创建订单
        Order order = new Order();
        order.setUserId(dto.getUserId());
        order.setProductId(dto.getProductId());
        order.setCount(dto.getCount());
        order.setMoney(dto.getMoney());
        order.setStatus(0);
        orderMapper.insert(order);

        // 2. 扣减库存(远程调用库存服务)
        Result storageResult = storageFeignClient.decreaseStorage(
            dto.getProductId(), dto.getCount()
        );
        if (!storageResult.isSuccess()) {
            throw new RuntimeException("库存扣减失败: " + storageResult.getMessage());
        }

        // 3. 扣减账户余额(远程调用账户服务)
        Result accountResult = accountFeignClient.decreaseAccount(
            dto.getUserId(), dto.getMoney()
        );
        if (!accountResult.isSuccess()) {
            throw new RuntimeException("余额扣减失败: " + accountResult.getMessage());
        }

        // 4. 修改订单状态为已完成
        order.setStatus(1);
        orderMapper.updateById(order);

        return order;
    }
}

@GlobalTransactional注解标记全局事务入口。TM向TC发起全局事务开启请求,获取XID。XID通过RPC请求头传递到下游服务,RM自动注册分支事务。任意环节抛出异常,TC协调所有分支事务回滚。

高并发场景下的优化策略

高并发场景下Seata AT模式存在性能瓶颈,主要来自全局锁竞争和undo_log写入开销。优化方案如下:

方案一:热点数据使用Redis预扣减,减少数据库锁持有时间。

@Service
public class StorageService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private StorageMapper storageMapper;

    @Transactional(rollbackFor = Exception.class)
    public void decreaseStorage(Long productId, Integer count) {
        String key = "stock:" + productId;
        Long remaining = redisTemplate.opsForValue().increment(key, -count.longValue());

        if (remaining == null || remaining < 0) {
            redisTemplate.opsForValue().increment(key, count.longValue());
            throw new RuntimeException("库存不足");
        }

        // 异步更新数据库
        storageMapper.decreaseStorage(productId, count);
    }
}

方案二:将同步扣减改为消息中间件异步处理,降低事务持有时间。订单创建成功后发送MQ消息,库存和账户服务消费消息异步扣减。服务治理方面需配合幂等性检查,防止消息重复消费。

undo_log表创建与数据源代理配置

每个参与分布式事务的数据库都需要创建undo_log表:

CREATE TABLE undo_log (
    branch_id BIGINT NOT NULL COMMENT '分支事务ID',
    xid VARCHAR(128) NOT NULL COMMENT '全局事务ID',
    context VARCHAR(128) NOT NULL COMMENT '上下文',
    rollback_info LONGBLOB NOT NULL COMMENT '回滚信息',
    log_status INT NOT NULL COMMENT '状态',
    log_created DATETIME(6) NOT NULL COMMENT '创建时间',
    log_modified DATETIME(6) NOT NULL COMMENT '修改时间',
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB COMMENT = 'AT模式回滚日志表';

Seata自动代理数据源,拦截SQL执行生成undo_log。如果使用多数据源,需要手动配置DataSourceProxy。排查分布式事务问题时,检查TC控制台的事务日志,确认各分支事务状态是否一致。XID日志追踪可以在日志框架中添加MDC过滤器,将XID打印到每条日志中便于关联。

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

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

相关推荐