Spring Boot微服务分布式事务方案对比与Seata实战部署

分布式事务的核心挑战

微服务架构下,一个业务操作可能跨多个服务的数据源,本地事务无法保证跨服务数据一致性。订单服务扣减库存、支付服务扣款、积分服务增减积分——任一步骤失败,如何回滚已执行的操作?这是分布式事务要解决的核心问题。这篇文章对比主流方案的适用场景,并给出Seata AT模式在Spring Boot中的完整部署实践。

三种主流方案对比

2PC(两阶段提交):强一致性协议,协调者统一控制所有参与者的事务提交或回滚。XA协议是2PC的标准实现,Java生态中由数据库驱动和应用服务器支持。优点是严格保证一致性,缺点是同步阻塞严重,单点协调者故障会导致所有参与者锁死。适合对一致性要求极高、并发量不高的场景。

TCC(Try-Confirm-Cancel):业务层面的2PC。Try阶段预留资源(冻结库存),Confirm阶段确认扣减,Cancel阶段释放预留。每个服务需实现三个接口,业务侵入性大,但性能优于2PC——锁持有时间短,仅在Try阶段冻结。适合资金、库存等核心业务场景。

Saga模式:长事务编排,每个步骤执行本地事务并记录补偿操作,失败时按反向顺序执行补偿。正向和反向都是本地事务,无全局锁。适合业务流程长、步骤多的场景,代价是只有最终一致性。

Seata AT模式实战部署

Seata的AT模式是最容易接入的分布式事务方案——对业务代码零侵入,只需加注解。原理是通过拦截SQL自动生成回滚日志(undo_log),全局回滚时根据日志反向补偿。

Step 1: 部署Seata Server

# docker部署seata-server
docker run -d --name seata-server   -p 8091:8091   -e SEATA_IP=192.168.1.100   -v /data/seata/config:/seata-server/config   seataio/seata-server:2.0.0

Seata Server配置文件application.yml使用Nacos作为注册中心:

seata:
  registry:
    type: nacos
    nacos:
      server-addr: 192.168.1.100:8848
      namespace: seata
      group: DEFAULT_GROUP
  store:
    mode: db
    db:
      datasource: druid
      url: jdbc:mysql://192.168.1.100:3306/seata?useSSL=false
      username: seata
      password: seata_pwd

Step 2: 微服务接入Seata

每个参与分布式事务的微服务需要添加依赖和配置。Maven依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2023.0.1.0</version>
</dependency>

每个服务的数据源需要被Seata代理,使用DataSourceProxy包装原始数据源。Spring Boot自动配置版本引入starter后无需手动配置。

每个服务数据库创建undo_log表:

CREATE TABLE IF NOT EXISTS `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,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Step 3: 业务代码使用

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private StockClient stockClient;

    @Autowired
    private AccountClient accountClient;

    @GlobalTransactional(timeoutMills = 60000, name = "create-order")
    public void createOrder(OrderDTO orderDTO) {
        // 1. 创建订单(本地事务,自动记录undo_log)
        Order order = new Order();
        order.setUserId(orderDTO.getUserId());
        order.setProductId(orderDTO.getProductId());
        order.setAmount(orderDTO.getAmount());
        orderMapper.insert(order);

        // 2. 扣减库存(远程调用,Seata拦截并注册分支事务)
        stockClient.deduct(orderDTO.getProductId(), orderDTO.getQuantity());

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

@GlobalTransactional注解标记全局事务入口。Seata拦截订单服务的本地SQL生成undo_log,拦截远程调用注册分支事务。任一步骤异常,Seata Server协调所有分支回滚,根据undo_log反向补偿。

生产环境注意事项

1. 全局锁与写隔离:AT模式在全局事务提交前获取全局锁,防止脏写。如果全局锁获取超时(默认30秒),事务回滚。高并发场景下需关注全局锁冲突率,必要时切换为写隔离级别RC。

2. undo_log清理:已提交事务的undo_log需要定期清理,Seata Server默认每小时清理一次。大流量场景下undo_log表增长快,建议设置定时任务或缩短清理间隔。

3. Seata Server高可用:生产环境至少部署3个Seata Server节点,通过Nacos注册中心实现服务发现。Store模式使用数据库而非文件,确保事务日志持久化。

4. 超时设置timeoutMills不宜过短(默认60秒),跨服务调用链路长时容易超时回滚;也不宜过长,避免全局锁占用时间过长影响并发。

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

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

相关推荐