分布式事务实战:Seata AT模式与TCC补偿方案对比与配置

分布式事务是微服务架构中保证跨服务数据一致性的核心机制。Seata是阿里巴巴开源的分布式事务解决方案,提供AT、TCC、SAGA、XA四种事务模式。AT模式基于SQL解析自动生成补偿操作,接入成本最低;TCC模式需要业务代码实现Try-Confirm-Cancel三个接口,灵活性最高但开发量大。本文对比AT和TCC两种模式的实现方式与适用场景。

Seata Server部署与注册中心配置

Seata Server(TC,事务协调者)是分布式事务的核心组件,负责管理全局事务和分支事务的状态。使用Docker部署Seata Server并注册到Nacos:

# docker-compose.yml
version: '3'
services:
  seata-server:
    image: seataio/seata-server:2.0.0
    environment:
      - SEATA_PORT=8091
      - STORE_MODE=db
      - SEATA_IP=192.168.1.100
    volumes:
      - ./seata-config/application.yml:/seata-server/resources/application.yml
    ports:
      - "8091:8091"
      - "7091:7091"

Seata Server配置application.yml,使用Nacos作为注册中心和配置中心,MySQL存储事务日志:

# seata-config/application.yml
seata:
  config:
    type: nacos
    nacos:
      server-addr: 192.168.1.50:8848
      group: SEATA_GROUP
      namespace: seata
      data-id: seataServer.properties

  registry:
    type: nacos
    nacos:
      server-addr: 192.168.1.50:8848
      group: SEATA_GROUP
      namespace: seata
      cluster: default

  store:
    mode: db
    db:
      datasource: druid
      db-type: mysql
      url: jdbc:mysql://192.168.1.60:3306/seata?characterEncoding=utf8
      user: root
      password: password123
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table

创建Seata所需的数据库表(global_table、branch_table、lock_table、distributed_lock),建表SQL在Seata源码script/server/db/mysql.sql中。

AT模式接入与自动补偿原理

AT模式(Automatic Transaction)通过拦截SQL执行,自动记录修改前后的数据快照(before image和after image),在事务回滚时根据before image自动生成反向SQL。业务代码只需添加@GlobalTransactional注解,无需编写补偿逻辑。

微服务接入AT模式配置(Spring Boot + Nacos):

# application.yml
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: 192.168.1.50:8848
      group: SEATA_GROUP
      namespace: seata
  datasource:
    auto-commit: false

# 每个微服务的数据库需要创建undo_log表
# CREATE TABLE undo_log (
#   id BIGINT PRIMARY KEY AUTO_INCREMENT,
#   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,
#   UNIQUE KEY ux_undo_log (xid, branch_id)
# )

业务代码使用@GlobalTransactional注解开启全局事务:

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private AccountFeignClient accountFeignClient;
    @Autowired
    private StorageFeignClient storageFeignClient;

    @GlobalTransactional(name = "createOrder", rollbackFor = Exception.class)
    public void createOrder(OrderDTO dto) {
        // 1. 创建订单(本地事务)
        orderMapper.insert(dto.toEntity());

        // 2. 扣减账户余额(远程调用,分支事务)
        accountFeignClient.debit(dto.getUserId(), dto.getAmount());

        // 3. 扣减库存(远程调用,分支事务)
        storageFeignClient.deduct(dto.getProductId(), dto.getCount());
    }
}

// 账户服务 - 分支事务
@RestController
@RequestMapping("/account")
public class AccountController {

    @Autowired
    private AccountMapper accountMapper;

    @PostMapping("/debit")
    public String debit(@RequestParam Long userId, @RequestParam BigDecimal amount) {
        Account account = accountMapper.selectById(userId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        accountMapper.updateBalance(userId, account.getBalance().subtract(amount));
        return "success";
    }
}

AT模式执行流程:TM(事务发起方)向TC注册全局事务获取XID → RM(资源管理器)拦截SQL,记录before/after image到undo_log表 → 业务执行 → TC检测到异常,通知各RM根据undo_log回滚 → RM执行反向SQL恢复数据。整个过程对业务代码透明。

TCC模式接入与手动补偿实现

TCC模式(Try-Confirm-Cancel)需要业务代码实现三个方法:Try预留资源、Confirm确认提交、Cancel回滚释放资源。TCC不依赖数据库事务,性能更高,适合非关系型数据源或跨异构数据源场景。

// 定义TCC接口,使用@LocalTCC注解
@LocalTCC
public interface AccountTccAction {

    // Try阶段:冻结金额
    @TwoPhaseBusinessAction(
        name = "debitTcc",
        commitMethod = "confirm",
        rollbackMethod = "cancel"
    )
    boolean prepare(
        @BusinessActionContextParameter(paramName = "userId") Long userId,
        @BusinessActionContextParameter(paramName = "amount") BigDecimal amount
    );

    // Confirm阶段:扣除冻结金额
    boolean confirm(BusinessActionContext context);

    // Cancel阶段:解冻金额
    boolean cancel(BusinessActionContext context);
}

// TCC实现
@Service
public class AccountTccActionImpl implements AccountTccAction {

    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private TccLogMapper tccLogMapper;

    @Override
    @Transactional
    public boolean prepare(Long userId, BigDecimal amount) {
        // 检查余额
        Account account = accountMapper.selectById(userId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        // 冻结金额:balance减少,frozen增加
        accountMapper.freeze(userId, amount);

        // 记录TCC日志,用于幂等和空回滚判断
        TccLog log = new TccLog();
        log.setXid(RootContext.getXID());
        log.setBranchId(RootContext.getBranchId());
        log.setUserId(userId);
        log.setAmount(amount);
        log.setStatus("trying");
        tccLogMapper.insert(log);
        return true;
    }

    @Override
    @Transactional
    public boolean confirm(BusinessActionContext context) {
        Long userId = (Long) context.getActionContext("userId");
        BigDecimal amount = (BigDecimal) context.getActionContext("amount");
        String xid = context.getXid();

        // 幂等检查:已确认则跳过
        TccLog log = tccLogMapper.findByXid(xid);
        if (log != null && "confirmed".equals(log.getStatus())) {
            return true;
        }
        // 扣减冻结金额
        accountMapper.deductFrozen(userId, amount);
        log.setStatus("confirmed");
        tccLogMapper.update(log);
        return true;
    }

    @Override
    @Transactional
    public boolean cancel(BusinessActionContext context) {
        Long userId = (Long) context.getActionContext("userId");
        BigDecimal amount = (BigDecimal) context.getActionContext("amount");
        String xid = context.getXid();

        // 空回滚检查:Try未执行则直接返回
        TccLog log = tccLogMapper.findByXid(xid);
        if (log == null) {
            return true;
        }
        // 幂等检查
        if ("cancelled".equals(log.getStatus())) {
            return true;
        }
        // 解冻金额:balance恢复,frozen减少
        accountMapper.unfreeze(userId, amount);
        log.setStatus("cancelled");
        tccLogMapper.update(log);
        return true;
    }
}

AT与TCC模式对比与选型

AT模式接入成本最低,只需添加注解和undo_log表,适合关系型数据库的简单CRUD场景。AT模式在提交阶段依赖全局锁保证隔离性,高并发写入场景下性能有瓶颈。

TCC模式需要手动实现三个接口,开发量大,但不依赖数据库事务和undo_log,性能更高。TCC适合高并发、跨异构数据源(如Redis+MySQL)或非标准SQL操作的场景。

TCC模式需要处理三个关键问题:幂等性(Confirm和Cancel可能被重复调用)、空回滚(Try未执行但Cancel被调用)、悬挂(Cancel先于Try执行)。通过维护TCC日志表记录每个分支事务的XID和状态来解决这三个问题。

SAGA模式适合长事务场景(如涉及外部系统调用、人工审批),通过状态机驱动正反向操作。XA模式依赖数据库XA协议,性能最低但强一致性最好,适合金融核心场景。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-yu-tcc-bu-chang/

赞 (0)
小编小编
上一篇 2026年9月18日
下一篇 2026年9月18日

相关推荐

分布式事务实战:Seata AT模式与TCC补偿方案落地

微服务架构下,一个业务操作可能涉及多个服务的数据写入,本地事务无法保证跨服务的数据一致性。分布式事务解决方案中,Seata(Simple Extensible Autonomous Transaction Architecture)是应用最广的开源框架,提供AT、TCC、SAGA、XA四种事务模式。Spring Boot框架集成Seata后,业务代码以近透明的方式获得分布式事务能力,高并发设计场景下的数据一致性得到保障。

Seata AT模式原理与全局事务协调

AT(Auto Transaction)模式是Seata的默认模式,对业务代码侵入最小。核心机制是通过SQL解析自动生成回滚日志(undo_log),在事务失败时自动补偿。涉及三个角色:TC(事务协调器,独立部署)、TM(事务管理器,发起全局事务的服务)、RM(资源管理器,参与事务的数据库代理)。

# Docker部署Seata Server 2.0
docker run -d --name seata-server \
  -p 8091:8091 \
  -p 7091:7091 \
  -e SEATA_IP=192.168.1.100 \
  -v /opt/seata/config:/seata-server/resources \
  apache/seata-server:2.0.0

# application.yml 关键配置
seata:
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
  store:
    mode: db
    db:
      datasource: druid
      db-type: mysql
      url: jdbc:mysql://127.0.0.1:3306/seata
      user: root
      password: xxx
// Spring Boot客户端配置
// application.yml
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
  datasource:
    auto-commit: false  # 必须关闭自动提交

// Java业务代码 - AT模式使用示例
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private AccountFeignClient accountFeignClient;  // RPC调用账户服务
    @Autowired
    private StorageFeignClient storageFeignClient;   // RPC调用库存服务

    @GlobalTransactional  // 只需加这个注解
    public void createOrder(OrderDTO dto) {
        // 1. 创建订单(本地事务,操作order_db)
        orderMapper.insert(dto);

        // 2. 扣减账户余额(远程调用,操作account_db)
        accountFeignClient.debit(dto.getUserId(), dto.getMoney());

        // 3. 扣减库存(远程调用,操作storage_db)
        storageFeignClient.deduct(dto.getProductId(), dto.getCount());

        // 如果步骤2或3抛异常,Seata自动回滚步骤1的数据
    }
}

AT模式执行流程:TM向TC注册全局事务获取XID,XID通过RPC请求传播到RM。各RM在本地事务提交前生成undo_log记录修改前数据镜像,本地事务提交后向TC上报分支事务状态。全局事务决定提交时,TC通知各RM异步删除undo_log;决定回滚时,TC通知各RM根据undo_log反向补偿。

undo_log表结构与回滚补偿机制

每个参与AT模式的数据库需创建undo_log表,Seata在事务执行前后自动记录数据镜像。

-- undo_log表结构(每个业务库都要创建)
CREATE TABLE `undo_log` (
    `branch_id`     BIGINT(20)   NOT NULL COMMENT '分支事务ID',
    `xid`           VARCHAR(128) NOT NULL COMMENT '全局事务ID',
    `context`       VARCHAR(128) NOT NULL COMMENT '上下文',
    `rollback_info` LONGBLOB     NOT NULL COMMENT '回滚信息',
    `log_status`    INT(11)      NOT NULL COMMENT '状态',
    `log_created`   DATETIME(6)  NOT NULL COMMENT '创建时间',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT '修改时间',
    `ext`           VARCHAR(100) DEFAULT NULL COMMENT '扩展字段',
    PRIMARY KEY (`branch_id`),
    KEY `idx_log_created` (`log_created`)
) ENGINE = InnoDB COMMENT 'AT事务回滚日志';

-- undo_log中存储的回滚信息JSON示例
{
    "branchId": 1001,
    "sqlType": "UPDATE",
    "tableName": "account",
    "beforeImage": {
        "rows": [{
            "fields": {
                "id": 1,
                "user_id": "U001",
                "balance": 1000      // 修改前余额
            }
        }]
    },
    "afterImage": {
        "rows": [{
            "fields": {
                "id": 1,
                "user_id": "U001",
                "balance": 800       // 修改后余额(扣减200)
            }
        }]
    }
}

-- 回滚时执行的补偿SQL
UPDATE account SET balance = 1000 WHERE id = 1;

AT模式依赖数据库本地事务的ACID保证和全局锁机制。提交本地事务前,RM会向TC申请全局锁(防止脏写),确保当前分支事务的数据在全局事务完成前不被其他全局事务修改。

TCC补偿模式与业务侵入方案

AT模式虽然对业务透明,但存在全局锁性能开销和脏读问题。对性能要求高的核心业务,TCC(Try-Confirm-Cancel)模式更合适。TCC要求业务实现三个方法,由TC统一调度。

// TCC模式实现示例 - 账户扣款
@LocalTCC
public interface AccountTccAction {

    @TwoPhaseBusinessAction(
        name = "debitTcc",
        commitMethod = "confirm",
        rollbackMethod = "cancel"
    )
    boolean tryDebit(BusinessActionContext context,
                     @BusinessActionContextParameter(paramName = "userId") String userId,
                     @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);

    boolean confirm(BusinessActionContext context);

    boolean cancel(BusinessActionContext context);
}

@Implement
public class AccountTccActionImpl implements AccountTccAction {

    @Autowired
    private AccountMapper accountMapper;

    // Try阶段:冻结金额(不是直接扣减)
    @Override
    public boolean tryDebit(BusinessActionContext ctx, String userId, BigDecimal amount) {
        Account account = accountMapper.selectByUserId(userId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        // 冻结:balance减,frozen加
        accountMapper.tryDebit(userId, amount);
        return true;
    }

    // Confirm阶段:扣减冻结金额
    @Override
    public boolean confirm(BusinessActionContext ctx) {
        String userId = ctx.getActionContext("userId").toString();
        BigDecimal amount = new BigDecimal(
            ctx.getActionContext("amount").toString()
        );
        accountMapper.confirmDebit(userId, amount);
        return true;
    }

    // Cancel阶段:解冻金额,恢复balance
    @Override
    public boolean cancel(BusinessActionContext ctx) {
        String userId = ctx.getActionContext("userId").toString();
        BigDecimal amount = new BigDecimal(
            ctx.getActionContext("amount").toString()
        );
        accountMapper.cancelDebit(userId, amount);
        return true;
    }
}

账户表需增加frozen字段:balance(可用余额)、frozen(冻结金额)。Try阶段balance -= amount, frozen += amount;Confirm阶段frozen -= amount;Cancel阶段frozen -= amount, balance += amount。TCC模式无全局锁,Confirm和Cancel需保证幂等性。

分布式事务监控与故障排查

Seata的事务日志存储在TC端的branch_table和global_table中,可通过控制台或SQL查询事务状态。

-- 查询当前未完成的全局事务
SELECT * FROM global_table WHERE status IN (1, 2, 3);
-- status: 1=Begin, 2=Committing, 3=Rollbacking

-- 查询分支事务执行状态
SELECT * FROM branch_table WHERE xid = '192.168.1.100:8091:123456789';

-- 查询全局锁
SELECT * FROM lock_table WHERE xid = '192.168.1.100:8091:123456789';

-- 清理超时未完成的事务(默认超时60s)
DELETE FROM global_table
WHERE status IN (4, 5, 6)
  AND gmt_modified < DATE_SUB(NOW(), INTERVAL 1 DAY);
-- status: 4=Committed, 5=CommitFailed, 6=Rollbacked

常见问题排查:事务一直处于Begin状态——检查分支服务是否正常注册到TC,网络是否通畅,undo_log表是否创建。回滚失败——检查undo_log中beforeImage数据是否完整,可能是并发改写导致镜像不匹配。全局锁超时——调整client.rm.lock.retryTimes的值,默认30次重试,每次间隔10ms。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-yu-tcc-bu-chang/

赞 (0)
小编小编
上一篇 2026年9月17日
下一篇 2026年9月17日

相关推荐

分布式事务实战:Seata AT模式与TCC补偿机制配置详解

分布式事务的CAP权衡与方案选型

微服务架构中,一次业务操作可能跨多个服务和数据库实例,本地事务无法保证跨库的数据一致性。分布式事务方案需要在一致性(C)、可用性(A)和分区容错性(P)之间权衡。强一致方案(XA两阶段提交)性能开销大、锁占用时间长,不适合高并发互联网场景;柔性事务方案(TCC、Saga、本地消息表)牺牲强一致性换取性能,通过最终一致性保证数据正确。

Seata是阿里开源的分布式事务框架,支持AT、TCC、SAGA和XA四种模式。AT模式通过SQL解析自动生成补偿SQL,业务代码零侵入;TCC模式需要业务实现Try-Confirm-Cancel三个接口,灵活性高但开发量大。实际项目中,AT模式适合对性能要求不极端且SQL操作规范的场景,TCC模式适合对一致性要求高且业务逻辑明确的场景。

Seata Server部署与配置

Seata Server(TC,Transaction Coordinator)是事务协调器,负责管理全局事务的分支注册和提交/回滚决策。使用Docker部署:

docker run -d --name seata-server \
  -p 8091:8091 \
  -p 7091:7091 \
  -e SEATA_IP=127.0.0.1 \
  -e STORE_MODE=db \
  -e STORE_DB_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver \
  -e STORE_DB_URL="jdbc:mysql://mysql:3306/seata" \
  -e STORE_DB_USER=root \
  -e STORE_DB_PASSWORD=password \
  apache/seata-server:2.1.0

DB存储模式下,Seata Server将事务日志持久化到MySQL,支持多节点集群部署。创建元数据表:

-- seata数据库建表SQL
CREATE DATABASE seata;
USE seata;

-- 全局事务表
CREATE TABLE global_table (
  xid VARCHAR(128) NOT NULL,
  transaction_id BIGINT,
  status TINYINT NOT NULL,
  application_id VARCHAR(32),
  transaction_service_group VARCHAR(32),
  transaction_name VARCHAR(128),
  timeout INT,
  begin_time BIGINT,
  application_data VARCHAR(2000),
  gmt_modified DATETIME NOT NULL,
  PRIMARY KEY (xid),
  KEY idx_gmt_modified (gmt_modified)
);

-- 分支事务表
CREATE TABLE branch_table (
  branch_id BIGINT NOT NULL,
  xid VARCHAR(128) NOT NULL,
  transaction_id BIGINT,
  resource_group_id VARCHAR(32),
  resource_id VARCHAR(256),
  branch_type VARCHAR(8),
  status TINYINT,
  client_id VARCHAR(64),
  application_data VARCHAR(2000),
  gmt_create DATETIME,
  gmt_modified DATETIME,
  PRIMARY KEY (branch_id),
  KEY idx_xid (xid)
);

-- 锁表(AT模式行锁)
CREATE TABLE lock_table (
  row_key VARCHAR(128) NOT NULL,
  xid VARCHAR(128),
  transaction_id BIGINT,
  branch_id BIGINT,
  resource_id VARCHAR(256),
  table_name VARCHAR(32),
  pk VARCHAR(36),
  gmt_create DATETIME,
  gmt_modified DATETIME,
  PRIMARY KEY (row_key)
);

AT模式原理与Spring Boot集成

AT模式分为两个阶段。一阶段:拦截业务SQL,解析SQL获取操作前的前镜像(before image)和操作后的后镜像(after image),将前后镜像存入undo_log表,执行业务SQL并提交本地事务,同时向TC注册分支事务并申请全局锁。二阶段:TC通知提交时,异步删除undo_log即可;TC通知回滚时,根据undo_log的前镜像生成反向SQL补偿数据。

<!-- Maven依赖 -->
<dependency>
  <groupId>io.seata</groupId>
  <artifactId>seata-spring-boot-starter</artifactId>
  <version>2.1.0</version>
</dependency>
# application.yml
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_tx_group
  service:
    vgroup-mapping:
      my_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  registry:
    type: file
  config:
    type: file
    file:
      name: seata-server.conf
  data-source:
    auto-commit: false
    proxy-mode: AT

每个参与全局事务的数据库需要创建undo_log表:

-- 在业务数据库中创建undo_log表
CREATE TABLE undo_log (
  branch_id BIGINT NOT NULL COMMENT '分支事务ID',
  xid VARCHAR(128) NOT NULL COMMENT '全局事务ID',
  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,
  ext_field VARCHAR(4000),
  PRIMARY KEY (branch_id),
  KEY idx_xid (xid)
);

业务代码中使用@GlobalTransactional注解开启全局事务:

@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private AccountFeignClient accountFeignClient;
    @Autowired
    private StorageFeignClient storageFeignClient;
    
    @GlobalTransactional(name = "createOrder", timeoutMills = 60000, rollbackFor = Exception.class)
    public void createOrder(OrderDTO dto) {
        // 1. 创建订单(操作order_db)
        Order order = new Order();
        order.setUserId(dto.getUserId());
        order.setAmount(dto.getAmount());
        order.setStatus("CREATED");
        orderMapper.insert(order);
        
        // 2. 扣减账户余额(远程调用account-service,操作account_db)
        accountFeignClient.deduct(dto.getUserId(), dto.getAmount());
        
        // 3. 扣减库存(远程调用storage-service,操作storage_db)
        storageFeignClient.deduct(dto.getProductId(), dto.getQuantity());
        
        // 如果步骤2或3抛出异常,Seata TC会通知所有分支回滚
        // order_db: 根据undo_log回滚insert
        // account_db: 远程服务收到回滚通知,根据undo_log回滚扣减
        // storage_db: 同理回滚
    }
}

TCC模式实现与补偿接口设计

TCC模式不依赖SQL解析,由业务自行实现Try、Confirm、Cancel三个接口。Try阶段做资源预留(如冻结金额、预占库存),Confirm阶段做真正的业务操作(扣减冻结金额、确认库存扣减),Cancel阶段释放预留资源(解冻金额、释放库存)。

// TCC接口定义
@LocalTCC
public interface AccountTccAction {
    
    /**
     * Try: 冻结金额
     */
    @TwoPhaseBusinessAction(
        name = "deductAccount",
        commitMethod = "confirmDeduct",
        rollbackMethod = "cancelDeduct"
    )
    boolean prepareDeduct(
        @BusinessActionContextParameter(paramName = "userId") Long userId,
        @BusinessActionContextParameter(paramName = "amount") BigDecimal amount
    );
    
    /**
     * Confirm: 确认扣减(从冻结金额中扣减)
     */
    boolean confirmDeduct(BusinessActionContext context);
    
    /**
     * Cancel: 取消冻结(返还冻结金额)
     */
    boolean cancelDeduct(BusinessActionContext context);
}

// TCC实现
@Service
public class AccountTccActionImpl implements AccountTccAction {
    
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private FreezeMapper freezeMapper;
    
    @Override
    @Transactional
    public boolean prepareDeduct(Long userId, BigDecimal amount) {
        // 幂等检查:防止重复Try
        FreezeRecord exist = freezeMapper.findByXidAndBranch(
            RootContext.getXID(), RootContext.getBranchId()
        );
        if (exist != null) return true;
        
        Account account = accountMapper.selectById(userId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        
        // 冻结金额
        account.setBalance(account.getBalance().subtract(amount));
        account.setFrozen(account.getFrozen().add(amount));
        accountMapper.updateById(account);
        
        // 记录冻结(用于幂等和Cancel补偿)
        FreezeRecord record = new FreezeRecord();
        record.setXid(RootContext.getXID());
        record.setBranchId(RootContext.getBranchId());
        record.setUserId(userId);
        record.setAmount(amount);
        record.setStatus("FROZEN");
        freezeMapper.insert(record);
        
        return true;
    }
    
    @Override
    @Transactional
    public boolean confirmDeduct(BusinessActionContext context) {
        Long userId = context.getActionContext("userId", Long.class);
        BigDecimal amount = context.getActionContext("amount", BigDecimal.class);
        
        // 幂等检查
        FreezeRecord record = freezeMapper.findByXidAndBranch(
            context.getXid(), context.getBranchId()
        );
        if (record == null || record.getStatus().equals("CONFIRMED")) return true;
        
        // 从冻结金额中扣减
        Account account = accountMapper.selectById(userId);
        account.setFrozen(account.getFrozen().subtract(amount));
        accountMapper.updateById(account);
        
        // 更新冻结记录状态
        record.setStatus("CONFIRMED");
        freezeMapper.updateById(record);
        
        return true;
    }
    
    @Override
    @Transactional
    public boolean cancelDeduct(BusinessActionContext context) {
        Long userId = context.getActionContext("userId", Long.class);
        BigDecimal amount = context.getActionContext("amount", BigDecimal.class);
        
        // 幂等检查
        FreezeRecord record = freezeMapper.findByXidAndBranch(
            context.getXid(), context.getBranchId()
        );
        if (record == null) return true;
        if (record.getStatus().equals("CANCELLED")) return true;
        
        // 释放冻结金额,返还可用余额
        Account account = accountMapper.selectById(userId);
        account.setBalance(account.getBalance().add(amount));
        account.setFrozen(account.getFrozen().subtract(amount));
        accountMapper.updateById(account);
        
        record.setStatus("CANCELLED");
        freezeMapper.updateById(record);
        
        return true;
    }
}

幂等性与空回滚处理

TCC模式的三大问题:幂等性、空回滚和悬挂。幂等性要求Confirm和Cancel接口可重复执行不产生副作用;空回滚指Try未执行但收到了Cancel请求(如Try超时后TC发送Cancel);悬挂指Cancel先于Try执行,之后Try才到达导致资源被永久锁定。

// 幂等、空回滚、悬挂统一处理
public abstract class TccHandler {
    
    @Autowired
    private TccLogMapper tccLogMapper;
    
    protected TccLog checkAndRecord(String xid, Long branchId, String phase) {
        TccLog log = tccLogMapper.findByXidAndBranch(xid, branchId);
        
        // 空回滚检查:Cancel时Try未执行
        if ("cancel".equals(phase) && log == null) {
            // Try未执行,插入标记记录防止后续Try执行(防悬挂)
            TccLog mark = new TccLog();
            mark.setXid(xid);
            mark.setBranchId(branchId);
            mark.setStatus("CANCELLED_WITHOUT_TRY");
            tccLogMapper.insert(mark);
            return null; // 返回null表示空回滚,跳过业务处理
        }
        
        // 幂等检查
        if (log != null && log.getStatus().equals(phase + "ED")) {
            return null; // 已执行过,跳过
        }
        
        // 悬挂检查:Try时发现已有Cancel记录
        if ("try".equals(phase) && log != null 
                && "CANCELLED_WITHOUT_TRY".equals(log.getStatus())) {
            return null; // Cancel已执行,Try不再执行
        }
        
        return log;
    }
}

通过事务日志表(tcc_log)记录每个分支事务的执行状态,在三个阶段入口处做检查,解决幂等、空回滚和悬挂问题。这是TCC模式生产环境的必备防护机制。

AT与TCC混合使用场景

实际项目中,同一全局事务内可以混合使用AT和TCC模式。对简单CRUD操作的服务使用AT模式(零侵入),对涉及复杂业务逻辑或非数据库资源(如调用第三方API扣款)的服务使用TCC模式。Seata通过@GlobalTransactional统一管理全局事务,各分支自行选择AT或TCC模式注册。

@GlobalTransactional
public void mixedOrder(OrderDTO dto) {
    // AT模式分支:订单服务自动生成undo_log
    orderMapper.insert(buildOrder(dto));
    
    // TCC模式分支:账户服务使用TCC补偿
    accountTccAction.prepareDeduct(dto.getUserId(), dto.getAmount());
    
    // AT模式分支:库存服务自动生成undo_log
    storageMapper.deduct(dto.getProductId(), dto.getQuantity());
}

回滚时,AT分支通过undo_log自动补偿,TCC分支通过Cancel接口补偿。两种模式的事务日志独立存储,TC统一调度。混合模式兼顾开发效率和业务灵活性,是微服务架构中推荐的分布式事务实践方案。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-yu-tcc-bu-chang/

赞 (0)
小编小编
上一篇 2026年8月25日
下一篇 2026年8月25日

相关推荐