Skip to content

Seata AT(Auto Transaction)模式

一、AT 模式的核心原理

Seata AT 模式是基于 两阶段提交(2PC) 优化的分布式事务解决方案,通过 数据源代理全局锁 实现事务的自动提交与回滚,对业务代码低侵入。其核心思想是:

  1. 一阶段:执行业务 SQL 并生成回滚日志(undo_log),提交本地事务。
  2. 二阶段
  • 提交:异步删除回滚日志。
  • 回滚:根据 undo_log 生成反向 SQL 恢复数据。

二、AT 模式的工作流程

1. 一阶段(本地事务提交)

  1. 解析 SQL:Seata 代理数据源,解析业务 SQL(如 UPDATE product SET stock = stock - 1 WHERE id = 1)。
  2. 生成回滚日志:记录数据修改前的快照(before image)到 undo_log 表。
  3. 提交本地事务:业务 SQL 和 undo_log 在同一本地事务中提交。

undo_log 表示例

sql
CREATE TABLE undo_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    branch_id BIGINT NOT NULL,       -- 分支事务 ID
    xid VARCHAR(100) NOT NULL,       -- 全局事务 ID
    context VARCHAR(128) NOT NULL,   -- 上下文(数据源、序列化格式)
    rollback_info LONGBLOB NOT NULL, -- 回滚信息(before/after image)
    log_status INT NOT NULL,         -- 日志状态(0:正常,1:已回滚)
    log_created TIMESTAMP NOT NULL,
    log_modified TIMESTAMP NOT NULL
);

2. 二阶段(全局事务提交/回滚)

  • 提交阶段
    全局事务成功 → 异步删除所有参与者的 undo_log 记录。

  • 回滚阶段
    全局事务失败 → 根据 undo_log 生成反向 SQL(如 UPDATE product SET stock = stock + 1 WHERE id = 1)并执行,恢复数据。


三、AT 模式的关键机制

1. 全局锁(Global Lock)

  • 作用:防止其他事务修改已被 AT 事务锁定的数据(避免脏写)。
  • 实现
    • 在一阶段提交前,Seata 向 TC(事务协调器)申请全局锁。
    • 若其他事务尝试修改同一数据,需等待锁释放。

2. 数据快照(Snapshot)

  • before image:记录 SQL 执行前的数据状态,用于回滚。
  • after image:记录 SQL 执行后的数据状态,用于二阶段提交后的一致性校验(可选)。

3. 隔离级别

  • 默认读未提交:AT 模式不保证全局隔离性,可能出现脏读。
  • 读隔离增强:通过 SELECT FOR UPDATE 获取全局锁,实现读已提交。

四、AT 模式的适用场景

场景特征说明
中低并发全局锁可能成为高并发场景的性能瓶颈
简单业务逻辑适合单库多服务事务,避免跨复杂业务链路的协调问题
低侵入性需求无需修改业务代码,仅需引入 Seata 依赖并配置数据源代理
支持本地事务的数据库如 MySQL、Oracle、PostgreSQL 等(依赖数据库的本地事务能力)

五、AT 模式的优缺点

优点缺点
低代码侵入:无需业务层改造全局锁性能损耗:高并发下锁竞争影响吞吐量
自动回滚:基于 undo_log 实现反向补偿脏读风险:默认不保证全局隔离性
支持多数据源:统一代理管理依赖数据库能力:需支持本地事务和行锁

六、AT 模式实战示例

1. 配置 Seata

  • 引入依赖(Spring Boot):
    xml
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.7.0</version>
    </dependency>
  • 配置文件application.yml):
    yaml
    seata:
      enabled: true
      application-id: order-service
      tx-service-group: my-tx-group
      service:
        vgroup-mapping:
          my-tx-group: default
      registry:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848
      config:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848

2. 业务代码示例

java
@GlobalTransactional  // 开启全局事务
public void createOrder(Order order) {
    // 1. 扣减库存
    productService.reduceStock(order.getProductId(), order.getCount());
    
    // 2. 创建订单
    orderMapper.insert(order);
    
    // 3. 扣款
    accountService.debit(order.getUserId(), order.getAmount());
}

七、常见问题与解决方案

1. 全局锁冲突

  • 现象:高并发下事务等待超时(Global lock wait timeout)。
  • 解决
    • 优化 SQL 性能,减少锁持有时间。
    • 使用 TCC 模式替代 AT 模式(避免全局锁)。

2. 回滚失败

  • 原因undo_log 丢失或反向 SQL 执行异常。
  • 解决
    • 确保 undo_log 与业务数据在同一本地事务中提交。
    • 监控告警并人工介入处理异常事务。

3. 跨服务数据源问题

  • 限制:AT 模式不支持跨服务的同一数据库实例操作。
  • 解决:改用 TCC 模式或 Saga 模式。

八、总结

  • 核心价值:AT 模式通过数据源代理和自动补偿机制,以低侵入方式实现分布式事务,适合传统项目改造。
  • 使用建议
    • 避免在高并发热点数据场景使用 AT 模式(如秒杀库存扣减)。
    • 结合 Seata 控制台(TC)监控事务状态,及时处理异常。
    • 在需要更高性能的场景,评估切换为 TCC 或 Saga 模式。

最佳实践

  • 确保所有参与者服务使用相同的事务分组(tx-service-group)。
  • 对关键业务表添加索引,优化 undo_log 查询效率。
  • 定期清理已完成的 undo_log 记录(通过 Seata 内置任务或自定义脚本)。

TCC 事务

以下是 TCC(Try-Confirm-Cancel)分布式事务 的详细解析,涵盖核心原理、实现流程、关键问题及最佳实践:


一、TCC 的核心思想

TCC 是一种 业务层补偿型事务,通过 预留资源补偿机制 保证分布式事务的最终一致性。其核心思想是将事务拆分为三个阶段:

  1. Try(尝试):预留资源,完成业务检查(如冻结库存、预扣款)。
  2. Confirm(确认):提交资源,完成业务操作(如实际扣减库存、完成支付)。
  3. Cancel(取消):释放预留资源,回滚 Try 阶段的操作(如解冻库存、退款)。

二、TCC 的适用场景

  • 强一致性要求:如金融交易、库存扣减等不允许最终一致性的场景。
  • 跨服务调用:涉及多个服务的业务操作(如订单创建 → 支付 → 物流)。
  • 高并发场景:通过资源预留避免全局锁,提升吞吐量。

三、TCC 的执行流程

1. 正常流程(Try → Confirm)

2. 异常流程(Try → Cancel)


四、TCC 的关键问题与解决方案

1. 空回滚(Cancel 未执行 Try)

  • 场景:Try 阶段未执行(如超时),但 Cancel 被触发。
  • 解决
    • 在 Cancel 操作中检查 Try 是否已执行(通过事务日志)。
    • 若未执行 Try,直接返回成功,避免误补偿。

2. 悬挂(Try 在 Cancel 之后到达)

  • 场景:Try 请求因网络延迟在 Cancel 之后到达,导致资源被锁定。
  • 解决
    • 在 Try 阶段检查事务状态,若已回滚则拒绝执行。
    • 通过事务日志记录状态(如 inittryingconfirmed/cancelled)。

3. 幂等性

  • 要求:每个阶段的接口需支持多次调用(如网络重试)。
  • 实现
    • 通过 事务 ID 唯一标识操作,记录已处理的事务状态。
    • 使用数据库唯一索引或 Redis 原子操作去重。

五、TCC 的实现步骤

1. 定义 TCC 接口

每个参与者需实现 Try、Confirm、Cancel 接口:

java
public interface TccOrderService {
    @Transactional
    boolean tryCreateOrder(String orderId, BigDecimal amount);

    @Transactional
    boolean confirmCreateOrder(String orderId);

    @Transactional
    boolean cancelCreateOrder(String orderId);
}

2. 事务日志记录

  • 记录事务状态:在 Try 阶段插入事务日志,标记为 trying
  • 更新状态:Confirm/Cancel 阶段更新日志为 confirmedcancelled
sql
CREATE TABLE tcc_transaction (
    tx_id VARCHAR(64) PRIMARY KEY,  -- 事务 ID
    status VARCHAR(20),             -- 状态(trying/confirmed/cancelled)
    create_time TIMESTAMP
);

3. 事务协调器(Coordinator)

  • 职责
    • 发起全局事务,调用各参与者的 Try 方法。
    • 根据 Try 结果决定提交(Confirm)或回滚(Cancel)。
    • 重试失败的操作(需保证幂等性)。

六、最佳实践

1. 设计原则

  • 资源预留轻量化:Try 阶段仅冻结必要资源,避免过度占用。
  • 快速失败:在 Try 阶段完成所有业务检查,减少 Confirm 阶段失败概率。
  • 超时机制:设置事务超时时间,避免资源长期锁定。

2. 事务监控与恢复

  • 监控:跟踪事务状态(如 trying 状态的未完成事务)。
  • 恢复:定时任务扫描超时事务,触发补偿(Cancel)操作。

3. 案例:电商下单流程

  • Try 阶段
    • 订单服务:生成订单(状态为 预创建)。
    • 库存服务:冻结商品库存。
    • 支付服务:预扣用户余额。
  • Confirm 阶段
    • 订单服务:更新订单状态为 已创建
    • 库存服务:扣减冻结库存。
    • 支付服务:完成扣款。
  • Cancel 阶段
    • 订单服务:删除预创建订单。
    • 库存服务:解冻库存。
    • 支付服务:返还预扣余额。

七、TCC 的优缺点

优点缺点
无全局锁,高并发代码侵入性高,需实现补偿逻辑
业务灵活,可自定义补偿逻辑需处理悬挂、空回滚等边界问题
避免资源长期占用事务日志管理复杂

八、与其他方案的对比

方案一致性性能复杂度适用场景
TCC强一致性金融交易、库存扣减
Seata AT强一致性简单跨服务事务
消息队列最终一致性异步通知、数据同步

九、总结

  • 核心价值:TCC 通过业务层补偿机制,在无全局锁的情况下实现强一致性,适合高并发、高可靠场景。
  • 关键挑战:需处理幂等性、悬挂、空回滚等问题,并保证事务日志的可靠性。
  • 实施建议
    • 使用框架(如 ByteTCC、Seata TCC 模式)减少编码量。
    • 结合熔断、限流机制提升系统容错能力。
    • 通过压测验证事务恢复机制的稳定性。

RocketMQ 的事务消息

RocketMQ 的事务消息是一种确保消息发送与本地事务执行具备原子性的机制,适用于需要保证业务操作与消息投递一致性的场景(如支付成功后通知发货)。以下是其核心原理、实现流程及最佳实践的详细介绍:


一、事务消息的核心目标

解决 “本地事务执行”“消息发送” 的原子性问题,避免以下问题:

  • 消息发送成功,本地事务失败:消费者收到消息但业务未实际完成。
  • 本地事务成功,消息发送失败:业务已提交但下游服务未感知。

二、事务消息的工作流程

RocketMQ 事务消息通过 两阶段提交(半消息 + 事务状态确认) 实现:

1. 第一阶段:发送半消息(Half Message)

  • Producer 向 Broker 发送半消息(对 Consumer 不可见)。
  • Broker 存储半消息,但不会将其投递到目标 Topic。
  • 代码示例
    java
    TransactionMQProducer producer = new TransactionMQProducer("group");
    producer.sendMessageInTransaction(msg, null);  // 发送半消息

2. 第二阶段:执行本地事务

  • Producer 执行本地数据库事务(如订单状态更新)。
    java
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            // 执行本地事务(如更新订单状态)
            boolean success = orderService.updateOrderStatus(msg.getKeys());
            return success ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;
        } catch (Exception e) {
            return LocalTransactionState.UNKNOW;
        }
    }

3. 第三阶段:提交或回滚事务消息

  • Broker 根据 Producer 返回的事务状态处理半消息:
    • COMMIT_MESSAGE:将消息投递到目标 Topic,Consumer 可见。
    • ROLLBACK_MESSAGE:丢弃半消息。
    • UNKNOW:触发 事务状态回查(Broker 主动询问 Producer 事务结果)。

4. 事务状态回查(Transaction Check)

  • 触发条件:Producer 未明确提交或回滚(如网络超时、服务宕机)。
  • Broker 定时回调 Producer 的 checkLocalTransaction 方法,获取事务最终状态。
    java
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 根据消息 ID 查询本地事务状态(如检查订单是否已更新)
        OrderStatus status = orderService.getStatusByMsgId(msg.getTransactionId());
        return status.isSuccess() ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;
    }

三、事务消息的关键机制

1. 消息存储与重试

  • 半消息存储:半消息存储在 RMQ_SYS_TRANS_HALF_TOPIC 队列,不暴露给 Consumer。
  • 回查重试:Broker 默认每 1 分钟回查一次,最多 15 次(可配置)。

2. 消息恢复

  • 超时未确认的消息:若 Producer 未响应回查,Broker 在超时(默认 7 天)后自动回滚消息。

3. 幂等性处理

  • Producer 端:通过事务 ID(transactionId)避免重复提交。
  • Consumer 端:需自行实现幂等(如数据库唯一键、Redis 原子操作)。

四、使用场景

1. 订单支付后通知发货

  • 本地事务:支付系统更新订单状态为“已支付”。
  • 消息投递:支付成功后发送“发货通知”到物流系统。

2. 用户注册后发送优惠券

  • 本地事务:注册系统写入用户表。
  • 消息投递:注册成功后发送“发放优惠券”消息到营销系统。

3. 跨系统数据同步

  • 本地事务:主系统更新业务数据。
  • 消息投递:同步数据变更到其他子系统(如缓存、搜索引擎)。

五、最佳实践

1. 事务消息配置

  • Producer 配置
    java
    TransactionMQProducer producer = new TransactionMQProducer("group");
    producer.setNamesrvAddr("127.0.0.1:9876");
    producer.setTransactionListener(new YourTransactionListener());  // 设置事务监听器
    producer.start();
  • Broker 配置broker.conf):
    properties
    transactionTimeout=60000          # 事务回查超时时间(毫秒)
    transactionCheckMax=15            # 最大回查次数

2. 避免消息堆积

  • 缩短事务执行时间:确保本地事务快速完成,减少回查概率。
  • 优化回查逻辑:在 checkLocalTransaction 中高效查询事务状态。

3. 异常处理

  • 本地事务失败:返回 ROLLBACK_MESSAGE,Broker 丢弃消息。
  • 事务状态未知:记录日志并告警,人工介入处理。

4. 消费者幂等设计

  • 唯一业务 ID:消息体中包含唯一标识(如订单 ID)。
  • 去重表:在消费前检查该 ID 是否已处理。
    sql
    CREATE TABLE msg_idempotent (
        id VARCHAR(64) PRIMARY KEY,
        status INT
    );

六、常见问题与解决方案

1. 消息重复消费

  • 原因:网络重发或事务回查导致消息多次投递。
  • 解决:消费者端实现幂等性(如数据库唯一约束)。

2. 事务回查失败

  • 原因:Producer 宕机或查询接口异常。
  • 解决:监控回查失败次数,设置告警并人工处理。

3. 半消息堆积

  • 原因:大量事务处于 UNKNOW 状态。
  • 解决:优化本地事务稳定性,排查超时原因。

七、与其他方案的对比

方案优点缺点适用场景
RocketMQ 事务消息解耦业务与消息、支持异步需处理消费者幂等、存在短暂延迟跨系统数据同步、事件通知
Seata AT 模式强一致性、低侵入性能损耗(全局锁)、依赖数据库同一数据库实例的跨服务事务
TCC无资源锁定、高并发代码侵入高、需实现补偿逻辑高一致性要求的金融交易

八、总结

RocketMQ 事务消息通过 半消息 + 事务状态确认 + 回查机制 确保本地事务与消息投递的原子性,适用于需要最终一致性的分布式场景。使用时需注意:

  1. 本地事务高效执行,减少回查概率。
  2. 消费者严格幂等,避免重复处理。
  3. 监控事务消息状态,及时处理异常。

文章来源于自己总结和网络转载,内容如有任何问题,请大佬斧正!联系我