分布式事务Saga模式落地:Spring Boot整合Seata实现跨服务数据一致性

微服务架构下,一个业务操作跨多个服务和数据库,本地事务无法保证全局一致性。Saga模式通过将长事务拆分为一系列本地事务,配合补偿操作实现最终一致性。Seata框架提供了生产级Saga实现,本文以订单支付场景为例,完整演示Spring Boot整合Seata的落地过程。

Saga模式与Seata架构

Saga将分布式事务拆分为N个本地事务T1、T2…Tn,每个本地事务有对应的补偿操作C1、C2…Cn。执行顺序为T1→T2→…→Tn,如果第i步失败,则执行Ci-1→Ci-2→…→C1回滚。与XA两阶段提交不同,Saga没有全局锁,各本地事务独立提交,通过补偿达到最终一致。

Seata的Saga模式基于状态机引擎(StateMachineEngine),通过JSON定义事务流程,每个节点对应一个服务调用和补偿调用。核心组件包括:TC(Transaction Coordinator)事务协调器,TM(Transaction Manager)事务管理器,RM(Resource Manager)资源管理器。

Seata Server部署与Nacos注册

Seata Server作为TC独立部署,使用Nacos作为注册中心和配置中心:

# application.yml (Seata Server)
seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata
  store:
    mode: db
    db:
      url: jdbc:mysql://127.0.0.1:3306/seata
      user: root
      password: your_password

store.mode设为db保证事务日志持久化,重启不丢失。Nacos中配置seataServer.properties,包含事务组到集群的映射关系。Seata Server 2.x版本默认使用AT模式,Saga模式需要单独启用状态机引擎。

Spring Boot微服务接入Seata Saga

三个微服务:订单服务(order-service)、库存服务(inventory-service)、账户服务(account-service)。以订单服务为TM发起全局事务。Maven依赖:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-saga-statemachine</artifactId>
    <version>2.0.0</version>
</dependency>

application.yml配置事务组:

seata:
  tx-service-group: order_tx_group
  service:
    vgroup-mapping:
      order_tx_group: default
  saga:
    enable: true
    state-machine:
      enable: true

Saga状态机定义与补偿逻辑

状态机JSON定义文件放在resources/statelang/目录下,描述事务流程中的每个步骤及其补偿操作:

{
  "Name": "orderPaymentSaga",
  "Comment": "订单支付Saga事务",
  "StartState": "CreateOrder",
  "States": {
    "CreateOrder": {
      "Type": "ServiceTask",
      "ServiceName": "orderService",
      "ServiceMethod": "create",
      "CompensateState": "CompensateCreateOrder",
      "Next": "DeductInventory"
    },
    "CompensateCreateOrder": {
      "Type": "ServiceTask",
      "ServiceName": "orderService",
      "ServiceMethod": "cancel"
    },
    "DeductInventory": {
      "Type": "ServiceTask",
      "ServiceName": "inventoryService",
      "ServiceMethod": "deduct",
      "CompensateState": "CompensateDeductInventory",
      "Next": "DeductAccount"
    },
    "CompensateDeductInventory": {
      "Type": "ServiceTask",
      "ServiceName": "inventoryService",
      "ServiceMethod": "addBack"
    },
    "DeductAccount": {
      "Type": "ServiceTask",
      "ServiceName": "accountService",
      "ServiceMethod": "deduct",
      "CompensateState": "CompensateDeductAccount",
      "Next": "Succeed"
    },
    "CompensateDeductAccount": {
      "Type": "ServiceTask",
      "ServiceName": "accountService",
      "ServiceMethod": "refund"
    },
    "Succeed": {
      "Type": "Succeed"
    }
  }
}

如果DeductAccount步骤失败(余额不足),状态机自动执行CompensateDeductInventory和CompensateCreateOrder,将库存和订单恢复到初始状态。每个ServiceTask的ServiceName对应Spring Bean名称,ServiceMethod为方法名。

业务代码实现与事务发起

订单服务中通过StateMachineEngine发起Saga事务:

@Service
public class OrderSagaService {

    @Autowired
    private StateMachineEngine stateMachineEngine;

    public String processOrder(OrderDTO order) {
        Map<String, Object> params = new HashMap<>();
        params.put("orderId", order.getId());
        params.put("userId", order.getUserId());
        params.put("productId", order.getProductId());
        params.put("amount", order.getAmount());
        params.put("money", order.getMoney());

        StateMachineInstance instance = stateMachineEngine.start(
            "orderPaymentSaga",
            BusinessKeyGenerator.generate(order.getId()),
            params
        );

        if (instance.getStatus() == ExecutionStatus.SU) {
            return "订单处理成功: " + order.getId();
        } else {
            throw new RuntimeException("订单处理失败,已执行补偿: " 
                + instance.getException());
        }
    }
}

各服务的业务方法需保证幂等性,因为网络超时可能导致重试调用。补偿方法同样需要幂等,同一个补偿可能被多次执行。实现幂等的常见方案:使用唯一请求ID + 数据库唯一索引,或通过状态字段判断是否已处理。

// 库存服务 - 扣减与补偿
@Service
public class InventoryService {

    @Transactional
    public boolean deduct(Long productId, Integer amount, String txId) {
        // 幂等检查
        if (txLogRepository.existsByTxId(txId)) {
            return true;
        }
        int rows = inventoryRepository.deductStock(productId, amount);
        if (rows == 0) {
            throw new RuntimeException("库存不足");
        }
        txLogRepository.save(new TxLog(txId, "DEDUCT", productId, amount));
        return true;
    }

    @Transactional
    public boolean addBack(Long productId, Integer amount, String txId) {
        if (txLogRepository.existsByTxId(txId + "_COMP")) {
            return true;
        }
        inventoryRepository.addBackStock(productId, amount);
        txLogRepository.save(new TxLog(txId + "_COMP", "ADD_BACK", productId, amount));
        return true;
    }
}

事务监控与异常处理

Seata控制台提供事务全链路可视化,可查看每个Saga实例的执行状态、各步骤耗时和补偿记录。生产环境中关注以下指标:事务成功率、平均执行时间、补偿触发率。补偿触发率持续偏高说明业务逻辑存在设计缺陷,需排查具体失败步骤。

超时配置影响Saga整体执行时间。全局超时建议设为各步骤超时之和的1.5倍,避免极端场景下事务长时间挂起。Seata Server的retry策略对网络抖动场景下的自动恢复至关重要,重试次数建议设为3次,间隔5秒。对于不可补偿的操作(如调用第三方支付接口),放在Saga流程的最后一步执行,减少回滚需求。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-saga-mo-shi-luo-di-springboot-zheng-he/

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

相关推荐