分布式事务-tcc( 与 2PC 的对比与代码示例)

前一篇文章介绍了分布式事务2pc,本文先来作了一个简单对比,让大家有个整体感知。

一、2PC(两阶段提交协议)

核心思想:通过协调者统一管理所有参与者的提交或回滚操作,分为两个阶段:

  1. 准备阶段:协调者询问所有参与者是否可以提交。
  2. 提交阶段:根据参与者的反馈,协调者发送全局提交或回滚命令。
    特点:
    ● 强一致性:所有参与者要么全部提交,要么全部回滚。
    ● 同步阻塞:参与者在准备阶段会锁定资源,直到协调者发出最终指令。
    ● 单点故障:协调者故障可能导致事务阻塞。

二、TCC(Try-Confirm-Cancel)

核心思想:通过业务逻辑补偿实现最终一致性,分为三个阶段:

  1. Try:预留资源,检查业务约束。
  2. Confirm:确认执行业务操作。
  3. Cancel:取消操作并释放预留资源。
    特点:特点:
    ● 最终一致性:通过补偿操作保证最终一致性。
    ● 业务侵入性:需要显式实现补偿逻辑。
    ● 高并发:资源在 Try 阶段只是预留,而非锁定。

代码示例(电商场景):

// 订单服务
class OrderService {
    @Try
    public void tryCreateOrder(Order order) {
        order.setStatus("TRY");
        orderDao.save(order); // 创建预订单(未生效)
    }

    @Confirm
    public void confirmOrder(Long orderId) {
        orderDao.updateStatus(orderId, "CONFIRMED"); // 确认订单生效
    }

    @Cancel
    public void cancelOrder(Long orderId) {
        orderDao.delete(orderId); // 删除预订单
    }
}
// 库存服务
class InventoryService {
    @Try
    public void tryDeductStock(Long productId, int count) {
        // 预扣减库存(实际库存不变,冻结部分库存)
        inventoryDao.freeze(productId, count);
    }

    @Confirm
    public void confirmDeductStock(Long productId, int count) {
        // 实际扣减库存
        inventoryDao.deduct(productId, count);
        inventoryDao.unfreeze(productId, count);
    }

    @Cancel
    public void cancelDeductStock(Long productId, int count) {
        // 释放冻结的库存
        inventoryDao.unfreeze(productId, count);
    }
}

三、TCC 与 2PC 对比

在这里插入图片描述

四、关键差异说明

  1. 资源管理方式:
    ○ 2PC 在准备阶段锁定资源,可能导致死锁。
    ○ TCC 通过 Try 阶段预留资源(如冻结库存),Confirm 阶段实际提交。
    ○ 异常处理:
    ○ 2PC 依赖数据库回滚,无法处理网络分区等复杂故障。
    ○ TCC 要求业务实现幂等性(例如重复调用 Cancel 不会导致错误):
    @Cancel
    public void cancelOrder(Long orderId) {
    if (orderDao.exists(orderId)) { // 幂等检查
    orderDao.delete(orderId);
    }
    }
    ○ 业务灵活性:
    ○ TCC 允许自定义补偿逻辑(例如订单取消时发送通知):
    @Cancel
    public void cancelOrder(Long orderId) {
    orderDao.delete(orderId);
    notificationService.sendCancelMsg(orderId); // 附加业务逻辑
    }

五、选型建议

  • 使用 2PC 的场景:
    ○ 简单的跨数据库操作(例如银行转账)。
    ○ 事务执行时间短,对一致性要求极高。
    ○ 基础设施支持 XA 协议(如 MySQL XA)。
  • 使用 TCC 的场景:
    ○ 长事务(如电商下单涉及库存、优惠券、物流)。
    ○ 高并发场景(如秒杀活动)。
    ○ 需要自定义补偿逻辑(如订单取消后恢复积分)。

六、完整的电商TCC代码示例

1. 服务定义

// === 订单服务 ===
@Service
public class OrderService {
    @Transactional
    public boolean tryCreateOrder(Long txId, Order order) {
        // 幂等检查:防止重复Try
        if (orderDao.existsByTxId(txId)) {
            return true;
        }
        
        order.setStatus("TRY");
        order.setTxId(txId); // 绑定全局事务ID
        orderDao.save(order);
        return true;
    }

    @Transactional
    public boolean confirmOrder(Long txId) {
        Order order = orderDao.findByTxId(txId);
        if (order.getStatus().equals("CONFIRMED")) {
            return true; // 幂等处理
        }
        
        order.setStatus("CONFIRMED");
        orderDao.save(order);
        return true;
    }

    @Transactional
    public boolean cancelOrder(Long txId) {
        Order order = orderDao.findByTxId(txId);
        if (order == null) return true; // 可能Try未执行
        
        orderDao.delete(order);
        return true;
    }
}

// === 库存服务 ===
@Service
public class InventoryService {
    @Transactional
    public boolean tryDeductStock(Long txId, Long productId, int count) {
        // 幂等检查
        if (inventoryLogDao.existsByTxId(txId)) {
            return true;
        }
        
        // 冻结库存(实际库存不变)
        inventoryDao.freezeStock(productId, count);
        inventoryLogDao.save(new InventoryLog(txId, productId, count));
        return true;
    }

    @Transactional
    public boolean confirmDeductStock(Long txId) {
        InventoryLog log = inventoryLogDao.findByTxId(txId);
        if (log == null) return false;
        if (log.getStatus().equals("CONFIRMED")) return true; // 幂等
        
        // 实际扣减库存并解冻
        inventoryDao.deductStock(log.getProductId(), log.getCount());
        inventoryDao.unfreezeStock(log.getProductId(), log.getCount());
        log.setStatus("CONFIRMED");
        inventoryLogDao.save(log);
        return true;
    }

    @Transactional
    public boolean cancelDeductStock(Long txId) {
        InventoryLog log = inventoryLogDao.findByTxId(txId);
        if (log == null) return true;
        
        // 解冻库存
        inventoryDao.unfreezeStock(log.getProductId(), log.getCount());
        inventoryLogDao.delete(log);
        return true;
    }
}

// === 支付服务 ===
@Service
public class PaymentService {
    @Transactional
    public boolean tryPreAuth(Long txId, Long userId, BigDecimal amount) {
        if (paymentLogDao.existsByTxId(txId)) return true;
        
        // 预授权资金(冻结金额)
        accountService.freezeAmount(userId, amount);
        paymentLogDao.save(new PaymentLog(txId, userId, amount));
        return true;
    }

    @Transactional
    public boolean confirmPayment(Long txId) {
        PaymentLog log = paymentLogDao.findByTxId(txId);
        if (log.getStatus().equals("CONFIRMED")) return true;
        
        // 实际扣款
        accountService.deductAmount(log.getUserId(), log.getAmount());
        accountService.unfreezeAmount(log.getUserId(), log.getAmount());
        log.setStatus("CONFIRMED");
        paymentLogDao.save(log);
        return true;
    }

    @Transactional
    public boolean cancelPayment(Long txId) {
        PaymentLog log = paymentLogDao.findByTxId(txId);
        if (log == null) return true;
        
        // 释放预授权
        accountService.unfreezeAmount(log.getUserId(), log.getAmount());
        paymentLogDao.delete(log);
        return true;
    }
}

2. 事务协调器(核心逻辑)

@Service
public class TransactionCoordinator {
    @Autowired
    private OrderService orderService;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private TransactionLogDao transactionLogDao;

    /**
     * 开启全局事务
     */
    public void executeGlobalTransaction(OrderRequest request) {
        Long txId = generateTxId(); // 生成全局唯一事务ID
        
        try {
            // === Try阶段 ===
            boolean tryResult = 
                orderService.tryCreateOrder(txId, request.getOrder()) &&
                inventoryService.tryDeductStock(txId, request.getProductId(), request.getCount()) &&
                paymentService.tryPreAuth(txId, request.getUserId(), request.getAmount());

            if (!tryResult) {
                throw new RuntimeException("Try阶段失败");
            }
            
            // 记录事务状态为TRY_SUCCESS
            transactionLogDao.save(new TransactionLog(txId, "TRY_SUCCESS"));

            // === Confirm阶段 ===
            confirm(txId);
            
        } catch (Exception e) {
            // === Cancel阶段 ===
            cancel(txId);
            throw e;
        }
    }

    /**
     * Confirm提交(需幂等)
     */
    private void confirm(Long txId) {
        TransactionLog log = transactionLogDao.findById(txId).orElseThrow();
        if ("CONFIRMED".equals(log.getStatus())) return; // 幂等检查
        
        try {
            boolean confirmResult = 
                orderService.confirmOrder(txId) &&
                inventoryService.confirmDeductStock(txId) &&
                paymentService.confirmPayment(txId);

            if (!confirmResult) {
                throw new RuntimeException("Confirm阶段失败");
            }
            
            log.setStatus("CONFIRMED");
            transactionLogDao.save(log);
        } catch (Exception e) {
            // 记录异常,触发告警人工干预
            log.setStatus("CONFIRM_FAILED");
            transactionLogDao.save(log);
            throw e;
        }
    }

    /**
     * Cancel回滚(需幂等)
     */
    private void cancel(Long txId) {
        TransactionLog log = transactionLogDao.findById(txId).orElse(null);
        if (log == null || "CANCELED".equals(log.getStatus())) return;

        try {
            // 逆序回滚(建议按依赖顺序调整)
            boolean cancelResult = 
                paymentService.cancelPayment(txId) &&
                inventoryService.cancelDeductStock(txId) &&
                orderService.cancelOrder(txId);

            log.setStatus(cancelResult ? "CANCELED" : "CANCEL_FAILED");
            transactionLogDao.save(log);
        } catch (Exception e) {
            log.setStatus("CANCEL_FAILED");
            transactionLogDao.save(log);
            throw e;
        }
    }
    
    // 定时任务:处理悬挂事务(例如Try成功但未收到Confirm/Cancel)
    @Scheduled(fixedRate = 60000)
    public void recoverHangingTransactions() {
        List<TransactionLog> hangingTx = transactionLogDao.findByStatusAndCreateTimeBefore(
            "TRY_SUCCESS", LocalDateTime.now().minusMinutes(30));
        
        hangingTx.forEach(tx -> {
            // 根据业务规则决定提交或回滚(示例自动回滚)
            cancel(tx.getTxId());
        });
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值