微服务架构下的分布式事务解决方案:Seata AT模式与TCC模式深度对比分析

 
更多

微服务架构下的分布式事务解决方案:Seata AT模式与TCC模式深度对比分析

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

随着微服务架构在企业级系统中的广泛应用,传统的单体应用事务管理机制已无法满足跨服务、跨数据库的事务一致性需求。在微服务场景下,一个业务操作往往涉及多个服务的协同调用,每个服务可能拥有独立的数据存储。当这些服务需要共同完成一个逻辑事务时,如何保证数据的原子性、一致性、隔离性和持久性(ACID),成为系统设计中的核心难题。

分布式事务的处理机制直接影响系统的可靠性、性能和可维护性。Seata(Simple Extensible Autonomous Transaction Architecture)作为阿里巴巴开源的分布式事务解决方案,提供了多种事务模式,其中 AT模式(Automatic Transaction Mode)TCC模式(Try-Confirm-Cancel) 是最常用且最具代表性的两种。

本文将深入剖析 Seata 框架中 AT 模式与 TCC 模式的实现原理,通过代码示例、性能对比、适用场景分析以及配置实践,帮助开发者全面理解并选择合适的分布式事务方案。


一、Seata 架构概览

在深入讨论 AT 与 TCC 模式之前,首先需要了解 Seata 的整体架构组成。Seata 采用 TC(Transaction Coordinator)- TM(Transaction Manager)- RM(Resource Manager) 三者分离的架构模型:

  • TC(事务协调器):全局事务的协调者,负责维护全局事务的状态,驱动全局提交或回滚。
  • TM(事务管理器):位于事务发起方,负责开启、提交或回滚全局事务。
  • RM(资源管理器):位于各参与方服务中,负责分支事务的注册、状态上报以及本地事务的提交/回滚。

Seata 支持多种事务模式,包括:

  • AT 模式:基于两阶段提交(2PC)的自动补偿型事务
  • TCC 模式:基于业务层面的两阶段提交
  • SAGA 模式:长事务补偿机制
  • XA 模式:基于 XA 协议的传统分布式事务

本文重点聚焦于 AT 模式TCC 模式 的对比分析。


二、AT 模式详解

2.1 基本原理

AT 模式是 Seata 提供的默认事务模式,其核心思想是 “无侵入式”自动补偿。它通过代理数据源(DataSourceProxy)拦截 SQL 执行,在事务执行过程中自动生成 前镜像(Before Image)后镜像(After Image),并记录在 undo_log 表中。一旦全局事务需要回滚,Seata 会根据这些镜像数据自动生成反向 SQL 进行补偿。

AT 模式本质上是一种 增强版的两阶段提交(2PC)

  • 第一阶段(Prepare)

    • 执行本地事务并提交(但不释放数据库锁)
    • 记录 undo_log,包含修改前后的数据快照
    • 向 TC 注册分支事务
  • 第二阶段(Commit/Rollback)

    • 若全局提交:异步删除 undo_log
    • 若全局回滚:根据 undo_log 生成反向 SQL 并执行,恢复数据

2.2 优势与特点

  • 低业务侵入性:无需编写补偿逻辑,开发者只需关注业务 SQL
  • 自动补偿:由 Seata 框架自动生成回滚 SQL
  • 支持主流数据库:MySQL、Oracle、PostgreSQL 等
  • 读已提交隔离级别:通过全局锁机制实现,避免脏读

2.3 使用前提

  • 数据库表必须包含 undo_log 表(Seata 提供建表语句)
  • 仅支持 本地事务 中的 DML 操作(INSERT、UPDATE、DELETE)
  • 不支持跨数据库类型的混合事务(如 MySQL + Oracle)
  • SQL 必须能被 Seata 解析并生成镜像(复杂 SQL 可能失败)

2.4 代码示例

1. 创建 undo_log 表(MySQL)

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` LONGBLOB NOT NULL,
  `log_status` INT(11) NOT NULL,
  `log_created` DATETIME NOT NULL,
  `log_modified` DATETIME NOT NULL,
  `ext` VARCHAR(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

2. 配置 Seata 数据源代理(Spring Boot)

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      my_test_tx_group: default
  config:
    type: nacos
    nacos:
      server-addr: localhost:8848
      group: SEATA_GROUP
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: localhost:8848
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSourceProxy);
        return factoryBean.getObject();
    }
}

3. 业务方法使用 @GlobalTransactional

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private StorageFeignClient storageClient;

    @GlobalTransactional
    public void createOrder(Long userId, Long productId, Integer count) {
        // 扣减库存(调用远程服务)
        storageClient.deduct(productId, count);

        // 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setCount(count);
        orderMapper.insert(order);

        // 模拟异常触发回滚
        if (count > 100) {
            throw new RuntimeException("订单数量超限");
        }
    }
}

三、TCC 模式详解

3.1 基本原理

TCC 模式是一种 业务层面的两阶段提交,其核心是将一个业务操作拆分为三个阶段:

  • Try:资源检查与预留(如冻结库存)
  • Confirm:确认执行业务(如扣减库存),通常幂等
  • Cancel:取消预留资源(如释放冻结库存)

TCC 模式不依赖数据库日志,而是由开发者显式实现这三个方法,Seata 负责协调调用顺序。

3.2 优势与特点

  • 高性能:Try 阶段即可释放数据库锁,提升并发
  • 灵活控制:业务逻辑完全由开发者掌控
  • 支持复杂业务:适用于库存、资金、积分等需预占资源的场景
  • 高可靠性:Confirm/Cancel 必须保证幂等性

3.3 使用前提

  • 需要手动实现 TryConfirmCancel 三个方法
  • 所有方法必须满足 幂等性
  • 需处理空回滚、悬挂等异常场景
  • 业务逻辑需支持资源预占模型

3.4 代码示例

1. 定义 TCC 接口

@LocalTCC
public interface StorageTCCAction {

    @TwoPhaseBusinessAction(name = "storageTccAction", commitMethod = "commit", rollbackMethod = "rollback")
    boolean tryDeduct(BusinessActionContext actionContext, Long productId, Integer count);

    boolean commit(BusinessActionContext actionContext);

    boolean rollback(BusinessActionContext actionContext);
}

2. 实现 TCC 服务

@Service
@RequiredArgsConstructor
public class StorageTCCActionImpl implements StorageTCCAction {

    private final StorageMapper storageMapper;

    @Override
    public boolean tryDeduct(BusinessActionContext actionContext, Long productId, Integer count) {
        // 检查库存并冻结
        Storage storage = storageMapper.selectById(productId);
        if (storage.getAvailable() < count) {
            throw new RuntimeException("库存不足");
        }

        // 冻结库存
        storage.setAvailable(storage.getAvailable() - count);
        storage.setFrozen(storage.getFrozen() + count);
        storageMapper.updateById(storage);

        return true;
    }

    @Override
    public boolean commit(BusinessActionContext actionContext) {
        Long productId = (Long) actionContext.getActionContext("productId");
        Integer count = (Integer) actionContext.getActionContext("count");

        // 确认扣减:将冻结库存转为已用
        Storage storage = storageMapper.selectById(productId);
        storage.setFrozen(storage.getFrozen() - count);
        storage.setUsed(storage.getUsed() + count);
        storageMapper.updateById(storage);

        return true;
    }

    @Override
    public boolean rollback(BusinessActionContext actionContext) {
        Long productId = (Long) actionContext.getActionContext("productId");
        Integer count = (Integer) actionContext.getActionContext("count");

        // 释放冻结库存
        Storage storage = storageMapper.selectById(productId);
        storage.setAvailable(storage.getAvailable() + count);
        storage.setFrozen(storage.getFrozen() - count);
        storageMapper.updateById(storage);

        return true;
    }
}

3. 调用方开启全局事务

@Service
public class OrderService {

    @Autowired
    private StorageTCCAction storageTCCAction;

    @Autowired
    private OrderMapper orderMapper;

    @GlobalTransactional
    public void createOrder(Long userId, Long productId, Integer count) {
        // 调用 TCC Try 方法
        storageTCCAction.tryDeduct(null, productId, count);

        // 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setCount(count);
        orderMapper.insert(order);

        if (count > 100) {
            throw new RuntimeException("订单数量超限");
        }
    }
}

注意:实际使用中需通过 BusinessActionContext 传递参数,此处为简化示例。


四、AT 模式 vs TCC 模式:深度对比

对比维度 AT 模式 TCC 模式
业务侵入性 低,仅需添加注解和配置 高,需实现 Try/Confirm/Cancel 三个方法
开发复杂度 简单,框架自动处理补偿 复杂,需手动编写幂等逻辑
性能 第一阶段持有数据库锁,影响并发 Try 阶段后即可释放锁,高并发场景更优
适用场景 简单 CRUD 操作,如订单创建、日志记录 资源预占型业务,如库存、资金、优惠券
数据一致性 强一致性(基于数据库) 依赖业务实现,需保证幂等
回滚机制 自动基于 undo_log 生成反向 SQL 手动实现 Cancel 逻辑
异常处理 框架处理 需处理空回滚、悬挂、幂等
SQL 支持 有限制(复杂 SQL 可能无法解析) 无限制
部署成本 低,只需配置数据源代理 高,需改造业务逻辑

五、适用场景分析

5.1 推荐使用 AT 模式的场景

  • 日志记录、通知发送:操作简单,无需资源预占
  • 订单创建、用户注册:仅涉及单表插入
  • 系统改造初期:希望以最小代价引入分布式事务
  • 团队技术能力有限:不希望引入复杂的业务补偿逻辑

5.2 推荐使用 TCC 模式的场景

  • 电商交易系统:涉及库存冻结、资金预扣
  • 金融支付系统:需保证资金最终一致性
  • 高并发系统:要求 Try 阶段快速释放锁
  • 长事务流程:需要分阶段确认执行结果

六、配置与最佳实践

6.1 Seata Server 部署建议

  • 使用 NacosEureka 作为注册中心
  • 配置 高可用模式(多节点部署)
  • 使用 DB 模式 存储事务日志(生产环境)
  • 合理设置事务超时时间(默认 60s)

6.2 性能优化建议

  • AT 模式

    • 避免大事务(减少 undo_log 体积)
    • 定期清理 undo_log 表(Seata 提供异步删除机制)
    • 使用连接池优化数据库性能
  • TCC 模式

    • 确保 Confirm/Cancel 幂等(建议使用事务 ID + 状态机)
    • Try 阶段尽量轻量,避免长时间占用资源
    • 使用缓存减少数据库查询压力

6.3 幂等性设计(TCC 关键)

@Override
public boolean commit(BusinessActionContext actionContext) {
    String xid = actionContext.getXid();
    // 查询事务执行状态,避免重复提交
    if (transactionLogService.isCommitted(xid)) {
        return true; // 已提交,直接返回
    }

    // 执行业务逻辑
    // ...

    // 记录提交日志
    transactionLogService.logCommit(xid);
    return true;
}

6.4 处理空回滚与悬挂

  • 空回滚:Try 未执行,Cancel 先执行

    • 解决方案:记录事务状态,Cancel 时判断 Try 是否已执行
  • 悬挂:Cancel 先执行,Try 后执行

    • 解决方案:Try 执行前检查 Cancel 是否已执行,若已执行则拒绝

七、故障排查与监控

7.1 常见问题

问题现象 可能原因 解决方案
全局事务未回滚 TC 与 RM 网络不通 检查网络、注册中心配置
undo_log 积压 异步删除失败 检查数据库连接、日志清理任务
TCC Confirm 失败 业务异常、幂等未处理 添加日志、幂等控制
分支事务未注册 RM 未正确初始化 检查数据源代理、Seata 配置
全局锁冲突 高并发下资源竞争 优化事务粒度、增加重试机制

7.2 监控建议

  • 集成 Prometheus + Grafana 监控 Seata Server 指标
  • 记录全局事务日志(XID、状态、耗时)
  • 使用 SkyWalking 或 Zipkin 进行链路追踪
  • 设置告警规则:事务超时、回滚率过高

八、总结与选型建议

Seata 的 AT 模式与 TCC 模式各有优劣,选择时应根据业务特点和技术团队能力综合判断:

  • 优先选择 AT 模式:当业务逻辑简单、希望快速落地分布式事务时,AT 模式是最佳选择。其“无侵入”特性极大降低了开发成本。

  • 优先选择 TCC 模式:当业务涉及资源预占、高并发或对性能要求极高时,TCC 模式能提供更优的并发能力和控制粒度,尽管开发成本较高。

在实际项目中,也可以采用 混合模式:核心交易链路使用 TCC,辅助操作使用 AT,实现性能与开发效率的平衡。


参考资料

  • Seata 官方文档:https://seata.io
  • 《阿里巴巴分布式事务实践》
  • Spring Cloud Alibaba Seata 集成指南
  • Nacos 注册中心配置手册

作者注:本文基于 Seata 1.7.x 版本编写,配置与 API 可能随版本演进而变化,请以官方文档为准。

打赏

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

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

微服务架构下的分布式事务解决方案:Seata AT模式与TCC模式深度对比分析:等您坐沙发呢!

发表评论


快捷键:Ctrl+Enter