专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

TCC模式详解:分布式事务解决方案与Seata框架实战

一、TCC模式概念

TCC(Try-Confirm-Cancel)是一种柔性事务解决方案,它将一个分布式事务拆分为三个操作:

1、 Try:尝试执行业务,完成所有业务检查,预留必需的业务资源
2、 Confirm:确认执行业务,真正执行业务,使用Try阶段预留的资源
3、 Cancel:取消执行业务,释放Try阶段预留的资源

核心思想:

1. 业务拆分:将业务操作拆分为Try、Confirm、Cancel三个阶段

2. 资源预留:Try阶段进行资源预留而非实际业务操作

3. 最终一致:通过Confirm/Cancel实现最终一致性

4. 业务补偿:Cancel阶段提供业务补偿能力

二、TCC模式实现原理

2.1 TCC事务执行流程

img_1

2.2 TCC异常处理流程

img_2

三、TCC模式实现示例

我们以一个电商系统的下单支付场景为例:

1、 订单服务:创建订单
2、 库存服务:扣减库存
3、 账户服务:扣减余额

3.1 定义TCC接口

public interface TccOrderService {
    @Transactional
    boolean prepare(Order order);
    boolean confirm(Order order);
    boolean cancel(Order order);
}
public interface TccInventoryService {
    @Transactional
    boolean prepare(String productId, int quantity);
    boolean confirm(String productId, int quantity);
    boolean cancel(String productId, int quantity);
}
public interface TccAccountService {
    @Transactional
    boolean prepare(String userId, BigDecimal amount);
    boolean confirm(String userId, BigDecimal amount);
    boolean cancel(String userId, BigDecimal amount);
}

3.2 Try阶段实现

@Service
public class TccOrderServiceImpl implements TccOrderService {
    @Autowired
    private OrderDao orderDao;
    @Override
    public boolean prepare(Order order) {
        // 检查业务规则
        if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new RuntimeException("订单金额必须大于0");
        }
        // 设置订单状态为"处理中"
        order.setStatus(OrderStatus.PROCESSING);
        // 保存订单到数据库
        orderDao.save(order);
        return true;
    }
    // confirm和cancel实现后面给出
}
@Service
public class TccInventoryServiceImpl implements TccInventoryService {
    @Autowired
    private InventoryDao inventoryDao;
    @Autowired
    private InventoryFreezeDao freezeDao;
    @Override
    public boolean prepare(String productId, int quantity) {
        // 检查库存是否充足
        Inventory inventory = inventoryDao.findByProductId(productId);
        if (inventory.getAvailable() < quantity) {
            throw new RuntimeException("库存不足");
        }
        // 冻结库存
        inventory.setAvailable(inventory.getAvailable() - quantity);
        inventory.setFrozen(inventory.getFrozen() + quantity);
        inventoryDao.update(inventory);
        // 记录冻结记录
        InventoryFreeze freeze = new InventoryFreeze();
        freeze.setProductId(productId);
        freeze.setQuantity(quantity);
        freeze.setStatus(FreezeStatus.TRY);
        freezeDao.save(freeze);
        return true;
    }
}
@Service
public class TccAccountServiceImpl implements TccAccountService {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private AccountFreezeDao freezeDao;
    @Override
    public boolean prepare(String userId, BigDecimal amount) {
        // 检查账户余额
        Account account = accountDao.findByUserId(userId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        // 冻结金额
        account.setBalance(account.getBalance().subtract(amount));
        account.setFrozen(account.getFrozen().add(amount));
        accountDao.update(account);
        // 记录冻结记录
        AccountFreeze freeze = new AccountFreeze();
        freeze.setUserId(userId);
        freeze.setAmount(amount);
        freeze.setStatus(FreezeStatus.TRY);
        freezeDao.save(freeze);
        return true;
    }
}

3.3 Confirm阶段实现

@Service
public class TccOrderServiceImpl implements TccOrderService {
    // ... prepare方法同上
    @Override
    public boolean confirm(Order order) {
        // 幂等性检查
        Order existing = orderDao.findById(order.getId());
        if (existing.getStatus() == OrderStatus.CONFIRMED) {
            return true;
        }
        // 更新订单状态为"已确认"
        existing.setStatus(OrderStatus.CONFIRMED);
        orderDao.update(existing);
        return true;
    }
    // cancel方法后面给出
}
@Service
public class TccInventoryServiceImpl implements TccInventoryService {
    // ... prepare方法同上
    @Override
    public boolean confirm(String productId, int quantity) {
        // 查询冻结记录
        InventoryFreeze freeze = freezeDao.findLatest(productId);
        if (freeze == null || freeze.getStatus() != FreezeStatus.TRY) {
            throw new RuntimeException("未找到有效的冻结记录");
        }
        // 幂等性检查
        if (freeze.getStatus() == FreezeStatus.CONFIRMED) {
            return true;
        }
        // 更新库存
        Inventory inventory = inventoryDao.findByProductId(productId);
        inventory.setFrozen(inventory.getFrozen() - quantity);
        inventoryDao.update(inventory);
        // 更新冻结记录状态
        freeze.setStatus(FreezeStatus.CONFIRMED);
        freezeDao.update(freeze);
        return true;
    }
}
@Service
public class TccAccountServiceImpl implements TccAccountService {
    // ... prepare方法同上
    @Override
    public boolean confirm(String userId, BigDecimal amount) {
        // 查询冻结记录
        AccountFreeze freeze = freezeDao.findLatest(userId);
        if (freeze == null || freeze.getStatus() != FreezeStatus.TRY) {
            throw new RuntimeException("未找到有效的冻结记录");
        }
        // 幂等性检查
        if (freeze.getStatus() == FreezeStatus.CONFIRMED) {
            return true;
        }
        // 更新账户
        Account account = accountDao.findByUserId(userId);
        account.setFrozen(account.getFrozen().subtract(amount));
        accountDao.update(account);
        // 更新冻结记录状态
        freeze.setStatus(FreezeStatus.CONFIRMED);
        freezeDao.update(freeze);
        return true;
    }
}

3.4 Cancel阶段实现

@Service
public class TccOrderServiceImpl implements TccOrderService {
    // ... prepare和confirm方法同上
    @Override
    public boolean cancel(Order order) {
        // 空回滚处理:如果Try未执行,直接返回
        Order existing = orderDao.findById(order.getId());
        if (existing == null) {
            // 记录空回滚日志
            log.warn("空回滚,订单不存在: {}", order.getId());
            return true;
        }
        // 幂等性检查
        if (existing.getStatus() == OrderStatus.CANCELLED) {
            return true;
        }
        // 防悬挂控制:如果Confirm已执行,不能Cancel
        if (existing.getStatus() == OrderStatus.CONFIRMED) {
            throw new RuntimeException("订单已确认,不能取消");
        }
        // 更新订单状态为"已取消"
        existing.setStatus(OrderStatus.CANCELLED);
        orderDao.update(existing);
        return true;
    }
}
@Service
public class TccInventoryServiceImpl implements TccInventoryService {
    // ... prepare和confirm方法同上
    @Override
    public boolean cancel(String productId, int quantity) {
        // 查询冻结记录
        InventoryFreeze freeze = freezeDao.findLatest(productId);
        // 空回滚处理
        if (freeze == null) {
            // 记录空回滚日志
            log.warn("空回滚,未找到冻结记录: {}", productId);
            return true;
        }
        // 幂等性检查
        if (freeze.getStatus() == FreezeStatus.CANCELLED) {
            return true;
        }
        // 防悬挂控制:如果Confirm已执行,不能Cancel
        if (freeze.getStatus() == FreezeStatus.CONFIRMED) {
            throw new RuntimeException("库存已确认,不能取消");
        }
        // 恢复库存
        Inventory inventory = inventoryDao.findByProductId(productId);
        inventory.setAvailable(inventory.getAvailable() + quantity);
        inventory.setFrozen(inventory.getFrozen() - quantity);
        inventoryDao.update(inventory);
        // 更新冻结记录状态
        freeze.setStatus(FreezeStatus.CANCELLED);
        freezeDao.update(freeze);
        return true;
    }
}
@Service
public class TccAccountServiceImpl implements TccAccountService {
    // ... prepare和confirm方法同上
    @Override
    public boolean cancel(String userId, BigDecimal amount) {
        // 查询冻结记录
        AccountFreeze freeze = freezeDao.findLatest(userId);
        // 空回滚处理
        if (freeze == null) {
            // 记录空回滚日志
            log.warn("空回滚,未找到冻结记录: {}", userId);
            return true;
        }
        // 幂等性检查
        if (freeze.getStatus() == FreezeStatus.CANCELLED) {
            return true;
        }
        // 防悬挂控制:如果Confirm已执行,不能Cancel
        if (freeze.getStatus() == FreezeStatus.CONFIRMED) {
            throw new RuntimeException("账户已确认,不能取消");
        }
        // 恢复余额
        Account account = accountDao.findByUserId(userId);
        account.setBalance(account.getBalance().add(amount));
        account.setFrozen(account.getFrozen().subtract(amount));
        accountDao.update(account);
        // 更新冻结记录状态
        freeze.setStatus(FreezeStatus.CANCELLED);
        freezeDao.update(freeze);
        return true;
    }
}

4.5 事务协调器的实现

@Service
public class TccTransactionCoordinator {
    @Autowired
    private TccOrderService orderService;
    @Autowired
    private TccInventoryService inventoryService;
    @Autowired
    private TccAccountService accountService;
    @Autowired
    private TransactionLogDao transactionLogDao;
    public boolean execute(Order order) {
        // 生成全局事务ID
        String xid = UUID.randomUUID().toString();
        try {
            // 记录事务日志
            transactionLogDao.save(new TransactionLog(xid, TransactionStatus.TRY));
            // Try阶段
            if (!orderService.prepare(order)) {
                throw new RuntimeException("订单服务Try失败");
            }
            if (!inventoryService.prepare(order.getProductId(), order.getQuantity())) {
                throw new RuntimeException("库存服务Try失败");
            }
            if (!accountService.prepare(order.getUserId(), order.getAmount())) {
                throw new RuntimeException("账户服务Try失败");
            }
            // Confirm阶段
            if (!orderService.confirm(order)) {
                throw new RuntimeException("订单服务Confirm失败");
            }
            if (!inventoryService.confirm(order.getProductId(), order.getQuantity())) {
                throw new RuntimeException("库存服务Confirm失败");
            }
            if (!accountService.confirm(order.getUserId(), order.getAmount())) {
                throw new RuntimeException("账户服务Confirm失败");
            }
            // 更新事务状态
            transactionLogDao.updateStatus(xid, TransactionStatus.CONFIRMED);
            return true;
        } catch (Exception e) {
            log.error("事务执行失败", e);
            // Cancel阶段
            try {
                accountService.cancel(order.getUserId(), order.getAmount());
                inventoryService.cancel(order.getProductId(), order.getQuantity());
                orderService.cancel(order);
                transactionLogDao.updateStatus(xid, TransactionStatus.CANCELLED);
            } catch (Exception ex) {
                log.error("事务Cancel失败", ex);
                // 记录异常,需要人工干预
            }
            return false;
        }
    }
    // 定时任务补偿
    @Scheduled(fixedRate = 60000)
    public void compensate() {
        // 查询超时未完成的事务
        List<TransactionLog> timeoutTransactions = transactionLogDao.findTimeoutTransactions();
        for (TransactionLog log : timeoutTransactions) {
            try {
                // 根据日志重试Confirm或Cancel
                // 这里需要根据业务实际情况实现
            } catch (Exception e) {
                log.error("补偿事务失败: {}", log.getXid(), e);
            }
        }
    }
}

四、Seata框架中的TCC模式

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。

img_3

Seata TCC模式同样遵循TC、TM、RM三种角色模型:

1、 TC (Transaction Coordinator):事务协调器,维护全局和分支事务状态,驱动全局事务提交或回滚。
2、 TM (Transaction Manager):事务管理器,定义全局事务范围,决定事务最终状态。
3、 RM (Resource Manager):资源管理器,管理分支事务资源,与TC交互报告分支状态

整体机制:

在两阶段提交协议中,资源管理器(RM, Resource Manager)需要提供“准备”、“提交”和“回滚” 3 个操作;而事务管理器(TM, Transaction Manager)分 2 阶段协调所有资源管理器,在第一阶段询问所有资源管理器“准备”是否成功,如果所有资源均“准备”成功则在第二阶段执行所有资源的“提交”操作,否则在第二阶段执行所有资源的“回滚”操作,保证所有资源的最终状态是一致的,要么全部提交要么全部回滚。

资源管理器有很多实现方式,其中 TCC(Try-Confirm-Cancel)是资源管理器的一种服务化的实现;TCC 是一种比较成熟的分布式事务解决方案,可用于解决跨数据库、跨服务业务操作的数据一致性问题;TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。

TCC 的 Try 操作作为一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 是二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。

使用示例:

1)接口定义

public interface AccountService {
    @TwoPhaseBusinessAction(name = "accountService", commitMethod = "confirm", rollbackMethod = "cancel", useTCCFence = true)
    boolean prepare(BusinessActionContext actionContext, 
                   @BusinessActionContextParameter(paramName = "userId") String userId,
                   @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
    boolean confirm(BusinessActionContext actionContext);
    boolean cancel(BusinessActionContext actionContext);
}

2)全局事务发起

@GlobalTransactional
public String doTransaction() {
    // 调用TCC参与方
    accountService.prepare(null, "user1", new BigDecimal("100"));
    orderService.prepare(null, "order123");
    return "success";
}

更多内容请参考官网:https://seata.apache.org/zh-cn/docs/user/mode/tcc

未经允许不得转载:搜云库 » TCC模式详解:分布式事务解决方案与Seata框架实战

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们