Spring Boot微服务架构中分布式事务的四种解决方案对比与实战

微服务架构下为什么必然面对分布式事务

单体应用中一个本地事务就能保证数据一致性,微服务拆分后,一个业务操作跨越多个服务,每个服务有独立数据库。下单扣库存、转账扣余额、订单支付回调——这些场景都需要跨服务事务保证。这篇文章对比当前主流的四种分布式事务方案,结合Spring Boot项目给出选型建议和实战代码。

方案一:2PC两阶段提交——XA协议

XA协议是数据库层面的分布式事务标准,通过事务协调器统一控制所有参与者的提交或回滚。

Spring Boot + Atomikos配置:

// pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
@Configuration
public class XADataSourceConfig {

    @Bean(name = "orderDataSource")
    public DataSource orderDataSource() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setUniqueResourceName("orderDB");
        ds.setXaProperties(Map.of(
            "url", "jdbc:mysql://localhost:3306/order_db",
            "user", "root",
            "password", "123456"
        ));
        ds.setPoolSize(5);
        return ds;
    }

    @Bean(name = "inventoryDataSource")
    public DataSource inventoryDataSource() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setUniqueResourceName("inventoryDB");
        ds.setXaProperties(Map.of(
            "url", "jdbc:mysql://localhost:3306/inventory_db",
            "user", "root",
            "password", "123456"
        ));
        ds.setPoolSize(5);
        return ds;
    }
}

XA方案的局限性:数据库必须支持XA协议,协调器单点故障风险,长时间锁定资源影响吞吐。适合对一致性要求极高、并发不高的场景。

方案二:TCC(Try-Confirm-Cancel)模式

TCC是业务层面的分布式事务,把一个事务拆成三个阶段:Try预留资源、Confirm确认提交、Cancel取消释放。

以扣库存为例:

@Service
public class InventoryTccService {

    @Autowired
    private InventoryMapper inventoryMapper;

    // Try:冻结库存
    @Transactional
    public boolean tryDeduct(String orderId, String sku, int quantity) {
        int rows = inventoryMapper.freezeStock(sku, quantity);
        if (rows == 0) return false; // 库存不足

        // 记录事务日志
        transactionLogMapper.insert(new TransactionLog(
            orderId, sku, quantity, "TRYING"
        ));
        return true;
    }

    // Confirm:确认扣减
    @Transactional
    public void confirm(String orderId) {
        TransactionLog log = transactionLogMapper.selectByOrderId(orderId);
        inventoryMapper.confirmDeduct(log.getSku(), log.getQuantity());
        transactionLogMapper.updateStatus(orderId, "CONFIRMED");
    }

    // Cancel:释放冻结库存
    @Transactional
    public void cancel(String orderId) {
        TransactionLog log = transactionLogMapper.selectByOrderId(orderId);
        inventoryMapper.unfreezeStock(log.getSku(), log.getQuantity());
        transactionLogMapper.updateStatus(orderId, "CANCELLED");
    }
}

TCC的优势:不依赖数据库XA支持,资源锁定时间短。代价是业务代码侵入性强,每个操作都要写三个方法。

方案三:Seata框架AT模式

Seata的AT模式是当前微服务架构中最流行的分布式事务方案,自动生成回滚日志,对业务代码零侵入。

Spring Boot集成:

// pom.xml
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.8.0</version>
</dependency>
# application.yml
seata:
  tx-service-group: order-tx-group
  service:
    vgroup-mapping:
      order-tx-group: default
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata

业务代码只需加一个注解:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryClient inventoryClient;

    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public void createOrder(OrderDTO dto) {
        // 1. 创建订单
        orderMapper.insert(new Order(dto));

        // 2. 远程扣库存(Feign调用)
        Result result = inventoryClient.deduct(dto.getSku(), dto.getQuantity());

        if (!result.isSuccess()) {
            throw new BusinessException("库存扣减失败");
            // Seata自动回滚订单数据
        }
    }
}

Seata AT模式的核心是undo_log表,记录修改前后的数据快照,回滚时反向补偿。需要每个业务库创建这张表:

CREATE TABLE undo_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    branch_id BIGINT NOT NULL,
    xid VARCHAR(100) NOT NULL,
    context VARCHAR(128) NULL,
    rollback_info LONGBLOB NOT NULL,
    log_status INT NOT NULL,
    log_created DATETIME NOT NULL,
    log_modified DATETIME NOT NULL,
    UNIQUE KEY ux_undo_log (xid, branch_id)
);

方案四:基于消息中间件的最终一致性

对一致性要求不绝对严格的场景,用消息队列实现最终一致性是最轻量的方案。

RocketMQ事务消息实现:

@Service
public class OrderMessageService {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    public void createOrderWithMessage(OrderDTO dto) {
        // 发送事务消息:本地事务+消息发送原子化
        rocketMQTemplate.sendMessageInTransaction(
            "order-topic",
            MessageBuilder.withPayload(dto).build(),
            dto
        );
    }

    // 本地事务执行
    @Transactional
    public void executeLocalTransaction(Message msg, OrderDTO dto) {
        orderMapper.insert(new Order(dto));
    }

    // 事务回查
    public void checkLocalTransaction(Message msg) {
        String orderId = msg.getHeaders().get("orderId", String.class);
        Order order = orderMapper.selectById(orderId);
        if (order != null) return CommitMessage.COMMIT;
        return CommitMessage.ROLLBACK;
    }
}

库存服务消费消息执行扣减,消费失败会自动重试,最终达到一致。

四种方案选型对比

XA:强一致,低并发,数据库原生支持,适合金融核心交易
TCC:强一致,业务侵入高,适合资源明确可预留的业务(库存、余额)
Seata AT:准一致,零侵入,适合快速落地的通用微服务场景
消息最终一致:弱一致,最轻量,适合异步业务(积分、通知、日志)

没有万能方案,根据业务的一致性要求和并发规模选择。同一个系统中不同业务可以用不同方案——核心链路用Seata AT保障,旁路逻辑用消息队列异步处理。

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

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

相关推荐