每天一道面试题之架构篇|深度解析 TCC 分布式事务及其实战应用

79 阅读6分钟

面试官:"请详细解释TCC分布式事务原理,并说明在电商系统中如何实际应用TCC解决订单创建时的分布式事务问题?"

TCC(Try-Confirm-Cancel)是分布式事务领域的重要解决方案,通过业务逻辑的拆解来实现最终一致性,特别适合高并发场景。

一、TCC核心架构原理

TCC三阶段模型

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│     Try阶段      │    │    Confirm阶段    │    │     Cancel阶段    │
│  资源预留与检查    │───▶│  确认执行业务逻辑  │───▶│  取消预留的资源   │
│  (预扣减/预锁定)   │    │  (实际扣减/确认)  │    │  (释放/回滚)     │
└─────────────────┘    └─────────────────┘    └─────────────────┘

与传统2PC对比

特性2PCTCC
锁定时长长(整个事务期间)短(仅Try阶段)
性能较低
业务侵入性
一致性强一致性最终一致性
适用场景数据库操作业务操作

二、TCC三阶段详细实现

1. Try阶段 - 资源预留

/**
 * 订单服务Try接口
 * 预创建订单状态为"待确认"
 */
@Transactional
public class OrderTccService {
    
    @TccTransactional
    public boolean tryCreateOrder(OrderDTO orderDTO) {
        // 检查业务约束
        if (!checkBusinessConstraints(orderDTO)) {
            throw new BusinessException("业务约束检查失败");
        }
        
        // 预创建订单(状态为TRYING)
        Order order = new Order();
        order.setStatus(OrderStatus.TRYING);
        order.setUserId(orderDTO.getUserId());
        order.setAmount(orderDTO.getAmount());
        order.setFreezeAmount(orderDTO.getAmount()); // 冻结金额
        order.setCreateTime(new Date());
        
        orderMapper.insert(order);
        
        // 保存事务上下文
        TccContext context = new TccContext();
        context.setXid(TransactionContext.getCurrentXid());
        context.setOrderId(order.getId());
        context.setAction("create_order");
        
        tccContextManager.saveContext(context);
        
        return true;
    }
}

2. Confirm阶段 - 确认执行

/**
 * Confirm阶段实现
 * 将订单状态更新为"已确认"
 */
public class OrderTccConfirmService {
    
    public boolean confirmCreateOrder(String xid) {
        // 获取事务上下文
        TccContext context = tccContextManager.getContext(xid);
        if (context == null) {
            log.warn("事务上下文不存在: xid={}", xid);
            return false;
        }
        
        try {
            Order order = orderMapper.selectById(context.getOrderId());
            if (order != null && order.getStatus() == OrderStatus.TRYING) {
                // 更新订单状态为已确认
                order.setStatus(OrderStatus.CONFIRMED);
                order.setFreezeAmount(BigDecimal.ZERO); // 释放冻结金额
                order.setConfirmTime(new Date());
                orderMapper.updateById(order);
                
                log.info("订单确认成功: orderId={}"order.getId());
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("Confirm阶段执行失败: xid={}", xid, e);
            throw new TccConfirmException("订单确认失败", e);
        }
    }
}

3. Cancel阶段 - 取消回滚

/**
 * Cancel阶段实现
 * 回滚订单创建操作
 */
public class OrderTccCancelService {
    
    public boolean cancelCreateOrder(String xid) {
        TccContext context = tccContextManager.getContext(xid);
        if (context == null) {
            log.warn("事务上下文不存在: xid={}", xid);
            return false;
        }
        
        try {
            Order order = orderMapper.selectById(context.getOrderId());
            if (order != null) {
                if (order.getStatus() == OrderStatus.TRYING) {
                    // 删除预创建订单
                    orderMapper.deleteById(order.getId());
                    log.info("订单创建已回滚: orderId={}"order.getId());
                } else if (order.getStatus() == OrderStatus.CONFIRMED) {
                    // 已确认订单需要业务补偿(这里简化处理)
                    log.warn("订单已确认,无法简单回滚: orderId={}"order.getId());
                    // 触发补偿业务流程
                    compensateConfirmedOrder(order);
                }
            }
            return true;
        } catch (Exception e) {
            log.error("Cancel阶段执行失败: xid={}", xid, e);
            throw new TccCancelException("订单回滚失败", e);
        }
    }
}

三、完整电商下单TCC实战

分布式事务场景

用户下单 → 订单服务(TCC) → 库存服务(TCC) → 积分服务(TCC)

1. 库存服务TCC实现

/**
 * 库存服务TCC实现
 */
@Service
@Slf4j
public class InventoryTccService {
    
    @TccTransactional
    public boolean tryReduceInventory(Long productId, Integer quantity) {
        // 检查库存是否充足
        Inventory inventory = inventoryMapper.selectByProductId(productId);
        if (inventory.getAvailable() < quantity) {
            throw new InventoryNotEnoughException("库存不足");
        }
        
        // 预扣减库存(冻结库存)
        int affected = inventoryMapper.freezeInventory(
            productId, quantity, inventory.getVersion());
        
        if (affected == 0) {
            throw new ConcurrentUpdateException("库存并发更新冲突");
        }
        
        // 保存事务上下文
        TccContext context = new TccContext();
        context.setXid(TransactionContext.getCurrentXid());
        context.setProductId(productId);
        context.setQuantity(quantity);
        context.setAction("reduce_inventory");
        
        tccContextManager.saveContext(context);
        
        return true;
    }
    
    public boolean confirmReduceInventory(String xid) {
        TccContext context = tccContextManager.getContext(xid);
        // 实际扣减库存
        inventoryMapper.reduceInventory(
            context.getProductId(), 
            context.getQuantity());
        return true;
    }
    
    public boolean cancelReduceInventory(String xid) {
        TccContext context = tccContextManager.getContext(xid);
        // 释放冻结的库存
        inventoryMapper.releaseFrozenInventory(
            context.getProductId(), 
            context.getQuantity());
        return true;
    }
}

2. 积分服务TCC实现

/**
 * 积分服务TCC实现
 */
@Service
@Slf4j
public class PointsTccService {
    
    @TccTransactional
    public boolean tryAddPoints(Long userId, Integer points) {
        // 预增加积分(标记为待确认)
        UserPoints userPoints = pointsMapper.selectByUserId(userId);
        userPoints.setFrozenPoints(userPoints.getFrozenPoints() + points);
        pointsMapper.update(userPoints);
        
        // 保存事务上下文
        TccContext context = new TccContext();
        context.setXid(TransactionContext.getCurrentXid());
        context.setUserId(userId);
        context.setPoints(points);
        context.setAction("add_points");
        
        tccContextManager.saveContext(context);
        
        return true;
    }
    
    public boolean confirmAddPoints(String xid) {
        TccContext context = tccContextManager.getContext(xid);
        // 实际增加积分
        UserPoints userPoints = pointsMapper.selectByUserId(context.getUserId());
        userPoints.setTotalPoints(userPoints.getTotalPoints() + context.getPoints());
        userPoints.setFrozenPoints(userPoints.getFrozenPoints() - context.getPoints());
        pointsMapper.update(userPoints);
        return true;
    }
    
    public boolean cancelAddPoints(String xid) {
        TccContext context = tccContextManager.getContext(xid);
        // 取消积分增加
        UserPoints userPoints = pointsMapper.selectByUserId(context.getUserId());
        userPoints.setFrozenPoints(userPoints.getFrozenPoints() - context.getPoints());
        pointsMapper.update(userPoints);
        return true;
    }
}

四、TCC事务协调器实现

/**
 * TCC事务协调器
 * 负责管理分布式事务状态
 */
@Component
@Slf4j
public class TccTransactionCoordinator {
    
    @Autowired
    private List<TccParticipant> participants;
    
    @Autowired
    private TccContextManager contextManager;
    
    /**
     * 开启TCC事务
     */
    public String beginTransaction() {
        String xid = generateXid();
        TransactionContext.bind(xid);
        return xid;
    }
    
    /**
     * 提交事务(Confirm阶段)
     */
    public boolean commit(String xid) {
        try {
            List<TccContext> contexts = contextManager.getContextsByXid(xid);
            
            for (TccContext context : contexts) {
                TccParticipant participant = findParticipant(context.getAction());
                if (participant != null) {
                    participant.confirm(xid);
                }
            }
            
            // 清理事务上下文
            contextManager.cleanContexts(xid);
            return true;
            
        } catch (Exception e) {
            log.error("事务提交失败: xid={}", xid, e);
            // 触发回滚
            rollback(xid);
            return false;
        }
    }
    
    /**
     * 回滚事务(Cancel阶段)
     */
    public boolean rollback(String xid) {
        try {
            List<TccContext> contexts = contextManager.getContextsByXid(xid);
            
            // 逆序回滚(后try的先cancel)
            Collections.reverse(contexts);
            
            for (TccContext context : contexts) {
                TccParticipant participant = findParticipant(context.getAction());
                if (participant != null) {
                    try {
                        participant.cancel(xid);
                    } catch (Exception e) {
                        log.error("回滚失败: xid={}, action={}", xid, context.getAction(), e);
                        // 记录异常但继续回滚其他操作
                    }
                }
            }
            
            // 清理事务上下文
            contextManager.cleanContexts(xid);
            return true;
            
        } catch (Exception e) {
            log.error("事务回滚异常: xid={}", xid, e);
            return false;
        }
    }
}

五、TCC异常处理与容错

/**
 * TCC异常处理管理器
 * 处理网络超时、服务宕机等异常情况
 */
@Component
@Slf4j
public class TccExceptionHandler {
    
    @Autowired
    private TccTransactionCoordinator coordinator;
    
    @Scheduled(fixedDelay = 30000// 每30秒执行一次
    public void handleTimeoutTransactions() {
        // 查找超时未完成的事务
        List<String> timeoutXids = contextManager.findTimeoutXids(30 * 60 * 1000); // 30分钟超时
        
        for (String xid : timeoutXids) {
            try {
                log.warn("处理超时事务: xid={}", xid);
                
                // 检查事务状态并决定提交或回滚
                if (shouldCommit(xid)) {
                    coordinator.commit(xid);
                } else {
                    coordinator.rollback(xid);
                }
                
            } catch (Exception e) {
                log.error("处理超时事务失败: xid={}", xid, e);
            }
        }
    }
    
    /**
     * 空回滚和防悬挂处理
     */
    public void processEmptyRollback(String xid, String action) {
        TccContext context = contextManager.getContext(xid, action);
        if (context == null) {
            // 没有Try记录,可能是网络超时后的空回滚
            log.warn("空回滚检测: xid={}, action={}", xid, action);
            // 记录空回滚,防止后续Try操作(防悬挂)
            contextManager.recordEmptyRollback(xid, action);
        }
    }
}

六、TCC适用场景分析

适合场景

  1. 电商订单系统:创建订单、扣库存、加积分
  2. 银行转账:扣款、收款、记录流水
  3. 酒店预订:预定房间、锁库存、创建订单
  4. 票务系统:锁定座位、创建订单、支付

不适合场景

  1. 简单的数据库CRUD操作(用本地事务即可)
  2. 实时性要求极高的金融交易
  3. 无法实现业务补偿的操作

七、面试深度问答

Q1:TCC和2PC的主要区别是什么? A: TCC是业务层面的2PC,通过业务逻辑实现资源预留和确认,锁定时长更短,性能更好,但业务侵入性更强。

Q2:TCC如何保证最终一致性? A: 通过Try阶段的资源预留,Confirm阶段的确认执行,Cancel阶段的回滚补偿,配合重试机制和事务日志实现最终一致性。

Q3:遇到网络分区或服务宕机时TCC如何处理? A: 通过事务协调器的重试机制、超时回滚、事务状态查询和人工干预等方式保证事务的最终一致性。

Q4:TCC的空回滚和防悬挂是什么? A: 空回滚:Try未执行但收到Cancel请求;防悬挂:先收到Cancel后收到Try请求。通过事务状态记录和检查避免这些问题。

Q5:在实际项目中如何选择TCC和其他分布式事务方案? A: 根据业务场景选择:强一致性用2PC,最终一致性用TCC或消息队列,简单场景用本地消息表。

面试技巧

  1. 先说明TCC的基本原理和三阶段模型
  2. 结合具体业务场景举例说明
  3. 强调TCC的优缺点和适用场景
  4. 展示对异常处理和容错机制的理解
  5. 对比其他分布式事务方案

本文由微信公众号"程序员小胖"整理发布,转载请注明出处。