微服务架构下分布式事务解决方案实战对比:Seata vs Saga vs 本地消息表

微服务拆分后的事务一致性难题

单体应用中,一个数据库事务(BEGIN到COMMIT)就能保证数据一致性。微服务拆分后,一个业务操作跨越多个服务和多个数据库,本地事务无法覆盖全局。订单创建涉及订单服务、库存服务、支付服务,任何一个环节失败都需要回滚全部操作,这就是分布式事务的核心挑战。

分布式事务没有银弹方案。CAP定理决定了在网络分区时,一致性和可用性只能选一个。实际工程中采用BASE理论——基本可用、软状态、最终一致性,在业务可接受的时间窗口内达成一致。

Seata AT模式:对业务代码侵入最小的方案

Seata是阿里开源的分布式事务框架,AT模式是其最易用的方案。AT模式的核心机制是”两阶段提交+全局锁”:一阶段执行业务SQL并记录回滚日志(undo_log),二阶段根据全局事务结果决定提交(删除undo_log)或回滚(执行反向SQL)。

工程集成步骤:

<!-- 1. 引入Seata依赖 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.8.0</version>
</dependency>

<!-- 2. 每个参与方数据库创建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          NOT NULL,
  `log_created`   DATETIME     NOT NULL,
  `log_modified`  DATETIME     NOT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB;
// 3. 业务代码加@GlobalTransactional注解
@GlobalTransactional(timeoutMills = 60000, name = "create-order")
public OrderResult createOrder(OrderRequest request) {
    orderService.create(request);
    inventoryService.deduct(request.getSkuId(), request.getQuantity());
    paymentService.freeze(request.getUserId(), request.getAmount());
    return OrderResult.success();
}

AT模式的优势是对业务代码几乎零侵入(只需加注解),劣势是全局锁机制在高并发场景下性能下降明显。全局锁会锁定参与方的数据行直到全局事务完成,如果事务耗时较长(比如支付服务响应慢),库存行被长时间锁定,其他事务只能等待。实测数据:单表1000 TPS时,全局锁争用导致TPS下降到300-400。

Saga模式:长事务场景下的替代方案

Saga模式将长事务拆分为多个本地事务,每个本地事务提交后执行下一步,任一环节失败则反向执行补偿操作。没有全局锁,可用性更高,但只有最终一致性。

Seata的Saga模式通过状态机定义事务流程:

{
  "Name": "createOrderSaga",
  "Comment": "订单创建Saga流程",
  "StartState": "CreateOrder",
  "States": {
    "CreateOrder": {
      "Type": "ServiceTask",
      "ServiceName": "orderService",
      "ServiceMethod": "create",
      "CompensateState": "CancelOrder",
      "Next": "DeductInventory",
      "Input": ["$.request"],
      "Output": { "orderId": "$.createResult.id" }
    },
    "DeductInventory": {
      "Type": "ServiceTask",
      "ServiceName": "inventoryService",
      "ServiceMethod": "deduct",
      "CompensateState": "RestoreInventory",
      "Next": "FreezePayment",
      "Input": ["$.request.skuId", "$.request.quantity"]
    },
    "FreezePayment": {
      "Type": "ServiceTask",
      "ServiceName": "paymentService",
      "ServiceMethod": "freeze",
      "CompensateState": "UnfreezePayment",
      "Input": ["$.request.userId", "$.request.amount"],
      "IsEnd": true
    },
    "CancelOrder": { "Type": "ServiceTask", "ServiceName": "orderService", "ServiceMethod": "cancel" },
    "RestoreInventory": { "Type": "ServiceTask", "ServiceName": "inventoryService", "ServiceMethod": "restore" },
    "UnfreezePayment": { "Type": "ServiceTask", "ServiceName": "paymentService", "ServiceMethod": "unfreeze" }
  }
}

Saga模式的优势是无需全局锁,各本地事务独立提交,性能好。劣势是补偿操作必须幂等(可能被执行多次),且业务侧需要为每个正向操作编写补偿逻辑,开发量约为AT模式的2倍。

本地消息表+最终一致性:最可靠的兜底方案

本地消息表方案的核心思路是:业务操作和消息写入在同一个本地事务中完成,保证”业务执行成功则消息一定存在”,再由异步任务保证消息投递到下游。

// 1. 本地消息表结构
CREATE TABLE `outbox_message` (
  `id`          BIGINT PRIMARY KEY AUTO_INCREMENT,
  `topic`       VARCHAR(64) NOT NULL,
  `key`         VARCHAR(128) NOT NULL,
  `body`        TEXT NOT NULL,
  `status`      TINYINT DEFAULT 0,
  `retry_count` INT DEFAULT 0,
  `created_at`  DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX `idx_status_created` (`status`, `created_at`)
) ENGINE=InnoDB;

// 2. 业务方法:同一事务中写入业务数据和消息
@Transactional
public void createOrderWithMessage(OrderRequest request) {
    Order order = orderMapper.insert(request);
    OutboxMessage msg = new OutboxMessage();
    msg.setTopic("order-created");
    msg.setKey(String.valueOf(order.getId()));
    msg.setBody(JSON.toJSONString(order));
    outboxMapper.insert(msg);
}

// 3. 定时任务扫描未发送消息并投递
@Scheduled(fixedDelay = 1000)
public void sendOutboxMessages() {
    List<OutboxMessage> messages = outboxMapper.selectPending(100);
    for (OutboxMessage msg : messages) {
        try {
            mqProducer.send(msg.getTopic(), msg.getKey(), msg.getBody());
            outboxMapper.updateStatus(msg.getId(), 1);
        } catch (Exception e) {
            outboxMapper.incrementRetry(msg.getId());
            if (msg.getRetryCount() >= 5) {
                outboxMapper.updateStatus(msg.getId(), 2);
            }
        }
    }
}

本地消息表方案没有全局锁,没有事务协调器单点,最坏情况下消息延迟但不会丢失。适合对实时性要求不高但可靠性要求极高的场景,如金融交易、库存扣减。

三种方案的选型决策矩阵

根据业务特征选择方案:

Seata AT模式:适合短事务(小于5秒)、强一致性要求高、并发量中等(小于500 TPS)的场景。典型应用:订单+库存扣减、转账交易。优点是代码侵入低,缺点是全局锁限制并发。

Saga模式:适合长事务(大于5秒)、允许最终一致性、流程步骤多的场景。典型应用:旅游订单(机票+酒店+租车)、供应链审批流。优点是性能好无全局锁,缺点是补偿逻辑开发量大。

本地消息表:适合跨系统异步通知、对可靠性要求极高但对实时性要求不高的场景。典型应用:支付结果通知、库存同步到搜索引擎。优点是最可靠,缺点是延迟高(秒级到分钟级)。

实际项目中,三种方案经常组合使用:核心交易链路用Seata AT保证强一致,审批流用Saga,跨系统通知用本地消息表。分布式事务方案的选择不是技术偏好问题,而是业务特征的匹配问题。

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

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

相关推荐