Skip to content
清晨的一缕阳光
返回

Seata TCC 模式实战

Seata TCC 模式实战

TCC 模式原理

三阶段

Try 阶段

Confirm 阶段

Cancel 阶段

工作流程

┌─────────────────────────────────────────────┐
│            Transaction Coordinator          │
│                  (TC)                       │
└───────────────────┬─────────────────────────┘

        ┌───────────┼───────────┐
        │           │           │
        ▼           ▼           ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│   服务 A   │ │   服务 B   │ │   服务 C   │
│  Try      │ │  Try      │ │  Try      │
│  Confirm  │ │  Confirm  │ │  Confirm  │
│  Cancel   │ │  Cancel   │ │  Cancel   │
└───────────┘ └───────────┘ └───────────┘

AT vs TCC

特性AT 模式TCC 模式
侵入性无侵入需要实现三个接口
性能中等
隔离性有全局锁无锁,资源预留
适用场景一般场景高性能要求场景
实现复杂度

快速开始

1. 添加依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

2. 配置 Seata

seata:
  enabled: true
  tx-service-group: default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default
    grouplist:
      default: 127.0.0.1:8091

3. 实现 TCC 接口

public interface TccTransactionService {
    
    /**
     * Try 阶段
     */
    @TCCBusinessActionName("tccTry")
    boolean tryMethod(TccParam param);
    
    /**
     * Confirm 阶段
     */
    @TCCBusinessActionName("tccConfirm")
    boolean confirmMethod(TccParam param);
    
    /**
     * Cancel 阶段
     */
    @TCCBusinessActionName("tccCancel")
    boolean cancelMethod(TccParam param);
}

4. 使用 @LocalTCC 注解

@LocalTCC
public interface AccountTccService {
    
    /**
     * Try 阶段:检查和预留资金
     */
    @TCCBusinessActionName("accountTry")
    boolean tryMethod(@BusinessActionContextParameter(paramName = "userId") Long userId,
                     @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
    
    /**
     * Confirm 阶段:扣减资金
     */
    @TCCBusinessActionName("accountConfirm")
    boolean confirmMethod(@BusinessActionContextParameter(paramName = "userId") Long userId,
                         @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
    
    /**
     * Cancel 阶段:释放预留资金
     */
    @TCCBusinessActionName("accountCancel")
    boolean cancelMethod(@BusinessActionContextParameter(paramName = "userId") Long userId,
                        @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
}

5. 实现 TCC 服务

@Service
public class AccountTccServiceImpl implements AccountTccService {
    
    @Autowired
    private AccountMapper accountMapper;
    
    @Override
    public boolean tryMethod(Long userId, BigDecimal amount) {
        // 1. 检查账户是否存在
        Account account = accountMapper.selectById(userId);
        if (account == null) {
            throw new BusinessException("账户不存在");
        }
        
        // 2. 检查余额是否充足
        if (account.getMoney().compareTo(amount) < 0) {
            throw new BusinessException("余额不足");
        }
        
        // 3. 预留资金(冻结)
        accountMapper.freezeMoney(userId, amount);
        
        log.info("TCC Try - 预留资金成功,userId={}, amount={}", userId, amount);
        return true;
    }
    
    @Override
    public boolean confirmMethod(Long userId, BigDecimal amount) {
        // 1. 扣减冻结资金
        accountMapper.deductFrozenMoney(userId, amount);
        
        log.info("TCC Confirm - 扣减资金成功,userId={}, amount={}", userId, amount);
        return true;
    }
    
    @Override
    public boolean cancelMethod(Long userId, BigDecimal amount) {
        // 1. 释放冻结资金
        accountMapper.unfreezeMoney(userId, amount);
        
        log.info("TCC Cancel - 释放资金成功,userId={}, amount={}", userId, amount);
        return true;
    }
}

6. 使用全局事务

@Service
public class OrderTccService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private AccountTccService accountTccService;
    
    @Autowired
    private StorageTccService storageTccService;
    
    @GlobalTransactional
    public void createOrder(Order order) {
        // 1. 创建订单
        orderMapper.insert(order);
        
        // 2. TCC 扣减账户
        accountTccService.tryMethod(order.getUserId(), order.getAmount());
        
        // 3. TCC 扣减库存
        storageTccService.tryMethod(order.getProductId(), order.getCount());
        
        // 4. 模拟异常
        if (order.getAmount() > 1000) {
            throw new BusinessException("订单金额过大");
        }
    }
}

进阶实现

1. 防悬挂控制

问题:Cancel 比 Try 先执行

解决方案

@Service
public class StorageTccServiceImpl implements StorageTccService {
    
    @Autowired
    private StorageMapper storageMapper;
    
    @Autowired
    private TccFenceMapper tccFenceMapper;
    
    @Override
    public boolean tryMethod(Long productId, Integer count) {
        // 检查是否已悬挂
        String xid = RootContext.getXID();
        Long branchId = BranchSessionManager.getBranchId();
        
        if (tccFenceMapper.selectByXidAndBranchId(xid, branchId) != null) {
            log.warn("TCC 已悬挂,xid={}, branchId={}", xid, branchId);
            return true;
        }
        
        // 检查库存
        Storage storage = storageMapper.selectById(productId);
        if (storage == null || storage.getCount() < count) {
            throw new BusinessException("库存不足");
        }
        
        // 预留库存
        storageMapper.freezeStock(productId, count);
        
        // 记录 TCC 事务
        tccFenceMapper.insert(new TccFence(xid, branchId, "try", Status.TRYED));
        
        return true;
    }
    
    @Override
    public boolean confirmMethod(Long productId, Integer count) {
        String xid = RootContext.getXID();
        Long branchId = BranchSessionManager.getBranchId();
        
        // 检查是否已执行
        TccFence fence = tccFenceMapper.selectByXidAndBranchId(xid, branchId);
        if (fence == null) {
            // Cancel 先执行,悬挂
            log.warn("TCC 悬挂,xid={}, branchId={}", xid, branchId);
            return true;
        }
        
        if (fence.getStatus() == Status.CONFIRMED) {
            log.warn("TCC 已确认,xid={}, branchId={}", xid, branchId);
            return true;
        }
        
        // 扣减库存
        storageMapper.deductFrozenStock(productId, count);
        
        // 更新状态
        tccFenceMapper.updateStatus(xid, branchId, Status.CONFIRMED);
        
        return true;
    }
    
    @Override
    public boolean cancelMethod(Long productId, Integer count) {
        String xid = RootContext.getXID();
        Long branchId = BranchSessionManager.getBranchId();
        
        // 检查是否已执行
        TccFence fence = tccFenceMapper.selectByXidAndBranchId(xid, branchId);
        if (fence == null) {
            // Try 未执行,记录悬挂
            tccFenceMapper.insert(new TccFence(xid, branchId, "cancel", Status.SUSPENDED));
            log.warn("TCC 悬挂,xid={}, branchId={}", xid, branchId);
            return true;
        }
        
        if (fence.getStatus() == Status.CANCELLED) {
            log.warn("TCC 已取消,xid={}, branchId={}", xid, branchId);
            return true;
        }
        
        // 释放库存
        storageMapper.unfreezeStock(productId, count);
        
        // 更新状态
        tccFenceMapper.updateStatus(xid, branchId, Status.CANCELLED);
        
        return true;
    }
}

2. 空回滚控制

问题:Try 未执行,直接回滚

解决方案

@Override
public boolean cancelMethod(Long productId, Integer count) {
    String xid = RootContext.getXID();
    Long branchId = BranchSessionManager.getBranchId();
    
    // 检查 Try 是否执行
    TccFence fence = tccFenceMapper.selectByXidAndBranchId(xid, branchId);
    if (fence == null) {
        // Try 未执行,空回滚
        log.warn("TCC 空回滚,xid={}, branchId={}", xid, branchId);
        tccFenceMapper.insert(new TccFence(xid, branchId, "cancel", Status.ROLLEDBACK));
        return true;
    }
    
    // 正常回滚逻辑
    storageMapper.unfreezeStock(productId, count);
    tccFenceMapper.updateStatus(xid, branchId, Status.CANCELLED);
    
    return true;
}

3. 幂等控制

问题:Confirm/Cancel 重复执行

解决方案

@Override
public boolean confirmMethod(Long productId, Integer count) {
    String xid = RootContext.getXID();
    Long branchId = BranchSessionManager.getBranchId();
    
    // 检查是否已确认
    TccFence fence = tccFenceMapper.selectByXidAndBranchId(xid, branchId);
    if (fence != null && fence.getStatus() == Status.CONFIRMED) {
        log.warn("TCC 重复确认,xid={}, branchId={}", xid, branchId);
        return true;  // 幂等返回
    }
    
    // 执行确认逻辑
    storageMapper.deductFrozenStock(productId, count);
    tccFenceMapper.updateStatus(xid, branchId, Status.CONFIRMED);
    
    return true;
}

4. 异常处理

@Override
public boolean tryMethod(Long productId, Integer count) {
    try {
        // 业务逻辑
        storageMapper.freezeStock(productId, count);
        return true;
    } catch (BusinessException e) {
        // 业务异常,直接抛出
        log.error("TCC Try 业务异常", e);
        throw e;
    } catch (Exception e) {
        // 系统异常,记录日志
        log.error("TCC Try 系统异常", e);
        throw new SystemException("TCC Try 失败", e);
    }
}

性能优化

1. 资源预留优化

@Override
public boolean tryMethod(Long productId, Integer count) {
    // 使用 Redis 预扣减,提高性能
    String key = "stock:freeze:" + productId;
    Long frozen = redisTemplate.opsForValue().increment(key, count);
    
    // 检查是否超卖
    if (frozen > getStockCount(productId)) {
        redisTemplate.opsForValue().decrement(key, count);
        throw new BusinessException("库存不足");
    }
    
    // 异步同步到数据库
    asyncSyncToDatabase(productId, count);
    
    return true;
}

2. 批量确认

@GlobalTransactional
public void batchConfirm(List<Order> orders) {
    for (Order order : orders) {
        // 批量确认
        accountTccService.confirmMethod(order.getUserId(), order.getAmount());
        storageTccService.confirmMethod(order.getProductId(), order.getCount());
    }
}

3. 异步 Confirm

@GlobalTransactional(asyncCommit = true)
public void createOrder(Order order) {
    // Try 阶段
    accountTccService.tryMethod(order.getUserId(), order.getAmount());
    storageTccService.tryMethod(order.getProductId(), order.getCount());
    
    // Confirm 阶段异步执行
}

监控与排查

1. TCC 事务日志

@RestController
@RequestMapping("/tcc")
public class TccController {
    
    @Autowired
    private TccFenceMapper tccFenceMapper;
    
    @GetMapping("/fences")
    public List<TccFence> listFences(@RequestParam String xid) {
        return tccFenceMapper.selectList(
            new QueryWrapper<TccFence>().eq("xid", xid)
        );
    }
    
    @GetMapping("/fences/stats")
    public Map<String, Object> getStats() {
        Map<String, Object> stats = new HashMap<>();
        stats.put("tryed", countByStatus(Status.TRYED));
        stats.put("confirmed", countByStatus(Status.CONFIRMED));
        stats.put("cancelled", countByStatus(Status.CANCELLED));
        stats.put("suspended", countByStatus(Status.SUSPENDED));
        return stats;
    }
}

2. 事务状态监控

@Component
public class TccTransactionMonitor {
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    @Scheduled(fixedRate = 60000)
    public void monitor() {
        // 统计各状态事务数
        long tryedCount = countByStatus(Status.TRYED);
        long confirmedCount = countByStatus(Status.CONFIRMED);
        long cancelledCount = countByStatus(Status.CANCELLED);
        
        meterRegistry.gauge("seata.tcc.tryed", tryedCount);
        meterRegistry.gauge("seata.tcc.confirmed", confirmedCount);
        meterRegistry.gauge("seata.tcc.cancelled", cancelledCount);
    }
}

3. 异常告警

@Component
public class TccAlertHandler {
    
    @Autowired
    private AlertService alertService;
    
    @Scheduled(fixedRate = 60000)
    public void checkSuspendedTransactions() {
        long suspendedCount = countByStatus(Status.SUSPENDED);
        
        if (suspendedCount > 10) {
            alertService.sendAlert(
                String.format("TCC 悬挂事务过多:%d 个", suspendedCount)
            );
        }
    }
}

最佳实践

1. 接口设计

2. 资源管理

3. 异常处理

4. 性能优化

常见问题

1. 空回滚

问题:Try 未执行,直接 Cancel

解决方案

2. 幂等性

问题:Confirm/Cancel 重复执行

解决方案

3. 数据不一致

问题:TCC 执行后数据不一致

解决方案

总结

Seata TCC 模式是一种高性能的分布式事务解决方案,通过 Try-Confirm-Cancel 三个接口实现两阶段提交。

相比 AT 模式,TCC 模式性能更高,但实现复杂度也更高,需要处理空回滚、防悬挂、幂等性问题。

在生产环境中,建议建立完善的监控告警机制,并实现补偿机制保证数据一致性。


分享这篇文章到:

上一篇文章
Spring Boot 分布式定时任务
下一篇文章
Spring Boot RocketMQ 消息队列集成