微服务架构下分布式事务解决方案:Seata AT模式与Saga模式实战对比

 
更多

微服务架构下分布式事务解决方案:Seata AT模式与Saga模式实战对比

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

在现代软件架构中,微服务已成为构建复杂系统的核心范式。通过将大型单体应用拆分为多个独立部署、自治运行的服务模块,微服务架构显著提升了系统的可维护性、可扩展性和灵活性。然而,这种“分而治之”的设计理念也带来了新的技术难题——分布式事务

传统单体应用中,事务管理依赖于本地数据库的ACID特性(原子性、一致性、隔离性、持久性),所有操作都在同一个数据库实例内完成,由数据库自身保证事务的一致性。但在微服务架构中,每个服务通常拥有自己的私有数据库,跨服务的数据操作需要跨越多个独立的数据库或消息队列,这使得传统的本地事务机制失效。

例如,在一个电商系统中,用户下单涉及以下关键操作:

  • 订单服务创建订单记录
  • 库存服务扣减商品库存
  • 财务服务扣除用户余额
  • 通知服务发送短信提醒

这些操作分布在不同的服务中,分别连接各自的数据库。如果其中任何一个环节失败(如库存不足或余额不足),就必须确保其他已成功执行的操作全部回滚,否则将导致数据不一致。

这就是典型的分布式事务问题:如何在跨服务、跨数据库的场景下,保证多个操作要么全部成功,要么全部失败,从而维持业务逻辑的一致性。

目前主流的分布式事务解决方案包括两阶段提交(2PC)、TCC(Try-Confirm-Cancel)、Saga模式以及基于XA协议的方案。但这些方案各有优劣,尤其在性能、可用性和实现复杂度方面存在明显差异。

在此背景下,Seata(Simple Extensible Autonomous Transaction Architecture)作为一款开源的分布式事务框架,提供了多种模式来应对不同场景下的事务需求。其中,AT模式(Automatic Transaction Mode)和Saga模式是两种最常用的实现方式。它们分别代表了“自动补偿”与“事件驱动补偿”两种思想路径。

本文将从原理剖析、代码实践、性能调优到适用场景等多个维度,对 Seata 的 AT 模式与 Saga 模式进行系统性比较,帮助开发者根据实际业务需求选择最优方案。


Seata AT模式详解:自动化的全局事务管理

原理概述

Seata 的 AT 模式(Auto Transaction Mode)是一种基于代理数据源全局锁机制的分布式事务解决方案,其核心思想是:无需修改业务代码即可实现分布式事务的自动管理

AT 模式的设计目标是在保持业务逻辑透明的前提下,通过中间件层自动完成事务的协调与控制。它适用于大多数读写频繁、对性能要求较高的场景,特别适合那些希望“零侵入”地接入分布式事务能力的团队。

核心组件介绍

  1. TC(Transaction Coordinator)
    全局事务协调器,负责管理全局事务的生命周期,包括注册分支事务、提交/回滚全局事务等。

  2. TM(Transaction Manager)
    事务管理器,位于客户端应用中,用于发起和控制全局事务,调用 TC 接口进行事务开启、提交、回滚。

  3. RM(Resource Manager)
    资源管理器,运行在每个微服务中,负责管理本地资源(如数据库连接),并注册为分支事务,向 TC 报告事务状态。

  4. Data Source Proxy(数据源代理)
    AT 模式的关键所在。Seata 会将原始的数据源替换为一个代理对象(DataSourceProxy),拦截 SQL 执行,并记录前后镜像(before/after image),用于后续的回滚操作。

工作流程解析

以下是 AT 模式下一次完整分布式事务的执行流程:

  1. 事务开始
    TM 向 TC 发起 begin 请求,获取一个全局事务 ID(XID)。

  2. SQL 执行与快照生成
    RM 在执行 SQL 前,通过 DataSourceProxy 拦截请求,读取当前数据行的状态,生成“前镜像”(before image)。执行完 SQL 后,再生成“后镜像”(after image)。

  3. 分支事务注册
    RM 将本次本地事务的信息(包括 XID、数据变更记录、前/后镜像)上报给 TC,注册为一个分支事务。

  4. 全局提交或回滚

    • 若所有分支事务均成功,则 TM 发起 commit 请求,TC 通知所有 RM 提交事务。
    • 若任一分支失败,则 TM 发起 rollback 请求,TC 通知各 RM 执行回滚。
  5. 回滚机制
    RM 收到回滚指令后,使用“前镜像”恢复数据,即把表中对应记录还原成事务开始时的状态。

✅ 关键优势:无需手动编写回滚逻辑,Seata 自动根据前后镜像生成反向 SQL。

实战配置示例

下面以 Spring Boot + MySQL + Seata AT 模式为例,演示完整配置过程。

1. 环境准备

  • Java 8+
  • MySQL 5.7+
  • Nacos 2.x(作为注册中心和配置中心)
  • Seata Server 1.5+

2. 添加依赖

<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>

    <!-- Seata AT 模式客户端 -->
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.5.2</version>
    </dependency>

    <!-- Nacos 注册与配置 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <version>2021.0.5.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        <version>2021.0.5.0</version>
    </dependency>
</dependencies>

3. 配置文件设置

application.yml
server:
  port: 8081

spring:
  application:
    name: order-service
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yaml

# Seata 配置
seata:
  enabled: true
  service:
    vgroup-mapping:
      my_test_tx_group: default
  client:
    rm:
      report-retry-count: 5
      report-success-enable: false
    tm:
      commit-retry-count: 5
      rollback-retry-count: 5
    undo:
      log-store-type: db
      log-table: undo_log
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: localhost:8848
      group: SEATA_GROUP
      namespace: public

⚠️ 注意:vgroup-mapping 中的 my_test_tx_group 是自定义事务组名,需与 Seata Server 中配置一致。

bootstrap.yml(优先级更高)
spring:
  main:
    allow-bean-definition-overriding: true

seata:
  enabled: true
  tx-service-group: my_test_tx_group

4. 数据库初始化

order_db 数据库中创建 undo_log 表(用于存储回滚日志):

CREATE TABLE `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` LONGTEXT NOT NULL,
  `log_status` INT(11) NOT NULL,
  `log_created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `log_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

5. 业务代码实现

假设我们有一个订单服务,包含以下实体类:

// Order.java
@Data
@TableName("t_order")
public class Order {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String userId;
    private String commodityCode;
    private Integer count;
    private BigDecimal amount;
}

Mapper 接口:

@Mapper
public interface OrderMapper extends BaseMapper<Order> {}

Service 层:

@Service
@Transactional(rollbackFor = Exception.class)
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private StockService stockService;

    // 下单操作:跨服务调用
    public void createOrder(String userId, String commodityCode, Integer count) {
        // 1. 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setCommodityCode(commodityCode);
        order.setCount(count);
        order.setAmount(new BigDecimal(count * 10));
        orderMapper.insert(order);

        // 2. 调用库存服务扣减库存
        stockService.deductStock(commodityCode, count);
    }
}

注意:此处虽然加了 @Transactional 注解,但不能直接用于分布式事务,因为它是本地事务。真正起作用的是 Seata 的 @GlobalTransactional 注解。

修正后的 Service:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private StockService stockService;

    @GlobalTransactional(name = "create-order-tx", timeoutMills = 30000, rollbackFor = Exception.class)
    public void createOrder(String userId, String commodityCode, Integer count) {
        // 1. 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setCommodityCode(commodityCode);
        order.setCount(count);
        order.setAmount(new BigDecimal(count * 10));
        orderMapper.insert(order);

        // 2. 调用库存服务扣减库存
        stockService.deductStock(commodityCode, count);
    }
}

@GlobalTransactional 是 Seata 提供的注解,标记该方法为全局事务入口。

6. 完整事务流程验证

当调用 createOrder 方法时,Seata 会:

  • 开启全局事务(生成 XID)
  • 本地数据库操作被拦截,记录前后镜像
  • 远程调用 stockService.deductStock() 时,Seata 自动注入 XID 到上下文
  • 如果远程服务调用失败,Seata 将触发回滚,使用 undo_log 中的前镜像恢复数据

Seata Saga模式详解:事件驱动的长事务处理

原理概述

与 AT 模式强调“自动回滚”不同,Saga 模式采用“补偿事务”的思想,适用于长时间运行、不可中断的业务流程,比如金融支付、审批流、物流调度等。

Saga 模式的核心理念是:不等待所有步骤完成才做决策,而是每一步都立即提交本地事务,一旦某步失败,则执行一系列预定义的补偿操作来回滚前面的成功步骤

它并不依赖于全局锁或统一的事务协调器,而是通过事件驱动机制(Event Sourcing)来实现最终一致性。

两种 Saga 模式变体

  1. Choreography(编排型)
    各个服务之间通过发布/订阅事件通信,没有中央控制器。每个服务监听特定事件,决定是否执行下一步或补偿动作。

  2. Orchestration(编排型)
    存在一个中心化的协调器(Orchestrator),负责按顺序调用各个服务,并管理失败时的补偿逻辑。

Seata 默认支持 Orchestration 模式,即通过一个专门的 Saga 事务协调器来控制整个流程。

工作流程解析

  1. 启动 Saga 事务
    TM 发起 start 请求,生成一个 Saga 事务 ID。

  2. 依次执行业务步骤
    协调器调用第一个服务的 try 方法,成功则继续下一个;若失败,则跳转至补偿逻辑。

  3. 补偿机制触发
    若第 N 步失败,协调器调用前 N-1 步的 cancel 方法,逐级回滚。

  4. 最终状态
    所有补偿完成后,事务结束,系统达到一致状态。

🔄 与 AT 模式对比:Saga 不依赖“前镜像”,而是由开发者显式编写“补偿逻辑”。

实战配置示例

1. 项目结构说明

我们仍使用上述订单系统,但现在模拟一个“支付+发货”流程,该流程持续时间较长,适合 Saga 模式。

2. 添加依赖(同 AT 模式,略)

3. 配置文件更新

application.yml 中添加 Saga 相关配置:

seata:
  enabled: true
  service:
    vgroup-mapping:
      my_saga_tx_group: default
  client:
    saga:
      enable: true
      mode: orchestration
      # 可选:指定 Saga 协调器地址
      coordinator:
        server-addr: localhost:8091

⚠️ mode: orchestration 表示使用编排型 Saga。

4. 编写 Saga 事务协调器(Orchestrator)

创建一个 SagaCoordinator 类,负责调度流程:

@Component
public class SagaCoordinator {

    @Autowired
    private OrderService orderService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private DeliveryService deliveryService;

    /**
     * 执行完整的支付发货 Saga 流程
     */
    public boolean executePaymentAndDelivery(String orderId, String userId, String address) {
        try {
            // Step 1: 创建订单
            orderService.createOrder(orderId, userId, "COMMODITY_001", 1);

            // Step 2: 支付
            if (!paymentService.pay(orderId, new BigDecimal(10))) {
                throw new RuntimeException("支付失败");
            }

            // Step 3: 发货
            if (!deliveryService.ship(orderId, address)) {
                throw new RuntimeException("发货失败");
            }

            return true; // 成功
        } catch (Exception e) {
            // 触发补偿
            compensate(orderId);
            return false;
        }
    }

    /**
     * 补偿逻辑:按逆序撤销已执行的操作
     */
    private void compensate(String orderId) {
        System.out.println("开始补偿流程...");

        // 1. 取消发货
        deliveryService.cancelShip(orderId);

        // 2. 退款
        paymentService.refund(orderId);

        // 3. 删除订单(可选)
        orderService.deleteOrder(orderId);
    }
}

5. 各服务实现补偿方法

OrderService.java
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public void createOrder(String orderId, String userId, String commodityCode, Integer count) {
        Order order = new Order();
        order.setId(orderId);
        order.setUserId(userId);
        order.setCommodityCode(commodityCode);
        order.setCount(count);
        order.setAmount(new BigDecimal(count * 10));
        orderMapper.insert(order);
    }

    public void deleteOrder(String orderId) {
        orderMapper.deleteById(orderId);
    }
}
PaymentService.java
@Service
public class PaymentService {

    public boolean pay(String orderId, BigDecimal amount) {
        // 模拟支付接口调用
        System.out.println("正在支付订单:" + orderId + ",金额:" + amount);
        // 模拟网络延迟或失败
        if (Math.random() > 0.8) {
            throw new RuntimeException("支付超时");
        }
        return true;
    }

    public boolean refund(String orderId) {
        System.out.println("正在退款订单:" + orderId);
        return true;
    }
}
DeliveryService.java
@Service
public class DeliveryService {

    public boolean ship(String orderId, String address) {
        System.out.println("正在发货订单:" + orderId + ",收货地址:" + address);
        if (Math.random() > 0.7) {
            throw new RuntimeException("物流公司异常");
        }
        return true;
    }

    public boolean cancelShip(String orderId) {
        System.out.println("取消发货订单:" + orderId);
        return true;
    }
}

6. 调用测试

@RestController
@RequestMapping("/api/saga")
public class SagaController {

    @Autowired
    private SagaCoordinator sagaCoordinator;

    @GetMapping("/execute")
    public String execute() {
        boolean result = sagaCoordinator.executePaymentAndDelivery("ORD001", "U001", "北京市朝阳区");
        return result ? "Saga 执行成功" : "Saga 执行失败,已触发补偿";
    }
}

访问 /api/saga/execute,若中途某步失败,系统将自动执行补偿逻辑。


AT模式 vs Saga模式:全面对比分析

维度 AT模式 Saga模式
事务模型 基于两阶段提交(2PC)的自动回滚 事件驱动的补偿机制
是否需要修改业务代码 否(仅需加注解) 是(必须编写补偿逻辑)
回滚方式 自动根据前后镜像生成反向 SQL 手动实现 cancel 方法
性能表现 较高(短事务优化好) 低(长事务开销大)
适用场景 短时、高频、强一致性要求 长时、异步、最终一致性容忍
数据库支持 支持主流关系型数据库(MySQL/Oracle等) 无特殊限制,但需保证幂等性
容错能力 依赖 TC 可用性 更强(可脱离 TC 独立运行)
实现复杂度 低(自动化程度高) 高(需设计补偿链)
监控与追踪 易于通过 Seata Dashboard 查看事务状态 需自行集成事件日志跟踪

选择建议

场景 推荐模式
电商下单、账户转账、库存扣减 ✅ AT模式
订单审批流程、多阶段支付、物流调度 ✅ Saga模式
事务跨度小于 1s,且需强一致性 ✅ AT模式
事务可能持续数分钟甚至数小时 ✅ Saga模式
团队缺乏分布式事务经验 ✅ AT模式
有成熟事件总线(如 Kafka)支撑 ✅ Saga模式(Choreography)

性能调优与最佳实践

AT模式调优策略

  1. 合理设置超时时间

    seata:
      client:
        tm:
          timeout-mills: 30000
    
  2. 启用批量提交(Batch Commit)

    seata:
      client:
        rm:
          batch-commit-limit: 500
    
  3. 优化 Undo Log 表性能

    • 添加索引:(xid, branch_id)
    • 使用分区表或归档策略处理历史数据
  4. 避免大事务

    • 单个事务不要涉及过多表或大量数据
    • 分拆为多个小事务
  5. 关闭不必要的日志输出

    logging:
      level:
        io.seata: WARN
    

Saga模式调优策略

  1. 使用幂等性设计

    • 所有 trycancel 方法必须具备幂等性
    • 可引入 Redis 或数据库唯一键防止重复执行
  2. 引入重试机制

    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public boolean pay(...) { ... }
    
  3. 异步执行补偿逻辑

    • 将补偿任务放入消息队列(如 RabbitMQ/Kafka),避免阻塞主线程
  4. 增加事务状态记录表

    CREATE TABLE saga_transaction (
        id BIGINT PRIMARY KEY,
        status ENUM('STARTED', 'SUCCESS', 'FAILED', 'COMPENSATING') DEFAULT 'STARTED',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );
    
  5. 可视化事务流程

    • 使用 ELK 或 Prometheus + Grafana 监控 Saga 执行进度

结论与展望

在微服务架构日益普及的今天,分布式事务已成为绕不开的技术挑战。Seata 作为国内领先的分布式事务解决方案,其 AT 模式与 Saga 模式分别代表了“自动化”与“事件驱动”两大方向。

  • AT 模式适合大多数常规业务场景,特别是对性能敏感、要求强一致性的系统。它的零侵入性和高自动化程度使其成为入门首选。
  • Saga 模式则更适合复杂流程、长时运行的业务,如金融交易、供应链协同等。尽管实现成本较高,但其灵活性和容错能力更强。

未来,随着云原生架构的发展,混合模式(Hybrid Pattern)将成为趋势:结合 AT 的高效性与 Saga 的弹性,利用事件溯源(Event Sourcing)+ CQRS 架构,打造更健壮的分布式系统。

对于开发者而言,理解这两种模式的本质差异,掌握其配置与调优技巧,是构建稳定可靠微服务系统的关键一步。


🔗 参考资料

  • Seata 官方文档:https://seata.io/
  • Apache ShardingSphere 文档
  • 《微服务架构设计模式》(Martin Fowler)
  • 《分布式系统:概念与设计》(G. Coulouris)

打赏

本文固定链接: https://www.cxy163.net/archives/8669 | 绝缘体

该日志由 绝缘体.. 于 2019年07月27日 发表在 未分类 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: 微服务架构下分布式事务解决方案:Seata AT模式与Saga模式实战对比 | 绝缘体
关键字: , , , ,

微服务架构下分布式事务解决方案:Seata AT模式与Saga模式实战对比:等您坐沙发呢!

发表评论


快捷键:Ctrl+Enter