一、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事务执行流程
2.2 TCC异常处理流程
三、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 服务而设计的。
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