Java中的TCC模式:Try/Confirm/Cancel三个阶段的业务逻辑实现与状态管理

Java中的TCC模式:Try/Confirm/Cancel三个阶段的业务逻辑实现与状态管理

大家好,今天我们来聊聊分布式事务中一种重要的解决方案——TCC(Try-Confirm-Cancel)模式。在微服务架构下,服务间的调用变得频繁,数据一致性面临着严峻的挑战。TCC模式通过将一个分布式事务拆分为三个阶段,允许我们在业务层面实现最终一致性。

TCC模式简介

TCC模式是一种补偿型事务,它将一个完整的业务逻辑拆分成三个阶段:

  1. Try阶段: 尝试执行业务,完成所有业务检查(一致性),预留必须的业务资源(准隔离性)。Try阶段的目标是尽量尝试执行,但允许失败。
  2. Confirm阶段: 在Try阶段执行成功后,真正执行业务,不作任何业务检查。Confirm阶段必须保证执行成功,如果失败,需要重试。
  3. Cancel阶段: 在Try阶段执行失败或Confirm阶段未执行时,释放Try阶段预留的业务资源。Cancel阶段同样必须保证执行成功,如果失败,需要重试。

TCC与ACID的对比:

特性 ACID TCC
隔离性 严格隔离(通常使用锁) 准隔离(预留资源)
一致性 强一致性 最终一致性
实现难度 数据库原生支持,相对简单 业务侵入性强,实现复杂
适用场景 对数据一致性要求极高的场景 允许最终一致性,资源允许预留的场景

TCC模式的优势与劣势

优势:

  • 性能较高: 相对于XA等强一致性事务方案,TCC模式在Try阶段已经完成了大部分业务逻辑,减少了Confirm阶段的耗时,提高了性能。
  • 资源锁定时间短: Try阶段预留资源,允许其他事务访问,减少了锁的持有时间,提高了并发能力。
  • 灵活性高: TCC模式允许在业务层面定义事务行为,可以根据实际业务场景进行定制。

劣势:

  • 业务侵入性强: 需要对每个参与事务的服务进行改造,实现Try、Confirm、Cancel三个接口。
  • 实现复杂: 需要考虑各种异常情况,例如Try阶段失败、Confirm阶段失败、Cancel阶段失败等,需要进行重试、幂等性控制等处理。
  • 开发成本高: 相对于使用数据库提供的事务支持,TCC模式需要编写大量的代码,增加了开发成本。

TCC模式的适用场景

TCC模式适用于以下场景:

  • 跨数据库事务: 多个服务访问不同的数据库,无法使用XA等分布式事务协议。
  • 资源允许预留: 业务允许在Try阶段预留资源,例如库存、金额等。
  • 性能要求较高: 对事务的响应时间有较高要求。
  • 允许最终一致性: 业务允许数据在一段时间内不一致,最终达到一致状态。

TCC模式的实现要点

在实现TCC模式时,需要考虑以下几个关键点:

  1. 幂等性: Confirm和Cancel操作必须保证幂等性,即多次执行的结果与执行一次的结果相同。可以通过唯一标识符(例如事务ID)来保证幂等性。
  2. 空回滚: 如果Try阶段未执行,Cancel阶段被执行,需要保证Cancel操作不会出错。
  3. 悬挂处理: 如果Confirm/Cancel操作先于Try操作执行,需要拒绝执行。

TCC模式的实现示例:商品购买

假设我们有一个简单的商品购买场景,涉及两个服务:

  • 库存服务: 负责管理商品的库存。
  • 订单服务: 负责创建订单。

1. 库存服务:

// 库存服务接口
interface InventoryService {
    boolean tryDeduct(String productId, int quantity, String transactionId);
    boolean confirmDeduct(String productId, int quantity, String transactionId);
    boolean cancelDeduct(String productId, int quantity, String transactionId);
}

// 库存服务实现类
class InventoryServiceImpl implements InventoryService {

    private ConcurrentHashMap<String, Integer> inventory = new ConcurrentHashMap<>(); // 模拟库存数据
    private ConcurrentHashMap<String, DeductRecord> deductRecords = new ConcurrentHashMap<>(); // 记录Try阶段的扣减记录

    // 内部类,记录Try阶段的扣减信息
    private static class DeductRecord {
        String productId;
        int quantity;
        boolean confirmed; // 标识是否已经confirm
    }

    @Override
    public boolean tryDeduct(String productId, int quantity, String transactionId) {
        synchronized (productId.intern()) { // 针对每个productId加锁,避免并发问题
            int currentInventory = inventory.getOrDefault(productId, 0);
            if (currentInventory < quantity) {
                System.out.println("库存不足,productId: " + productId + ", quantity: " + quantity + ", currentInventory: " + currentInventory);
                return false;
            }

            // 预留库存:不是真的扣减,而是记录下来
            DeductRecord record = new DeductRecord();
            record.productId = productId;
            record.quantity = quantity;
            record.confirmed = false;
            deductRecords.put(transactionId, record);

            System.out.println("Try 扣减库存成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
            return true;
        }
    }

    @Override
    public boolean confirmDeduct(String productId, int quantity, String transactionId) {
        synchronized (productId.intern()) { // 针对每个productId加锁,避免并发问题
            DeductRecord record = deductRecords.get(transactionId);
            if (record == null || record.confirmed) {
                System.out.println("Confirm 扣减库存:记录不存在或已确认,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
                return true; // 幂等性:已经confirm过了,直接返回成功
            }

            int currentInventory = inventory.getOrDefault(productId, 0);
            inventory.put(productId, currentInventory - quantity); // 真正扣减库存
            record.confirmed = true;
            System.out.println("Confirm 扣减库存成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId + ", currentInventory: " + inventory.get(productId));
            return true;
        }
    }

    @Override
    public boolean cancelDeduct(String productId, int quantity, String transactionId) {
        synchronized (productId.intern()) { // 针对每个productId加锁,避免并发问题
            DeductRecord record = deductRecords.get(transactionId);
            if (record == null) {
                System.out.println("Cancel 扣减库存:记录不存在,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
                return true; // 幂等性:已经cancel过了,或者根本没有try过,直接返回成功
            }

            if (record.confirmed) {
                System.out.println("Cancel 扣减库存:已经confirm,不允许cancel,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
                return false; // 已经confirm过了,不能cancel
            }

            // 恢复库存:因为Try阶段没有真正扣减,所以这里也不需要增加库存,只需要删除记录即可
            deductRecords.remove(transactionId);

            System.out.println("Cancel 扣减库存成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
            return true;
        }
    }

    // 初始化库存数据,方便测试
    public void initInventory(String productId, int initialInventory) {
        inventory.put(productId, initialInventory);
    }
}

2. 订单服务:

// 订单服务接口
interface OrderService {
    boolean tryCreateOrder(String productId, int quantity, String transactionId);
    boolean confirmCreateOrder(String productId, int quantity, String transactionId);
    boolean cancelCreateOrder(String productId, int quantity, String transactionId);
}

// 订单服务实现类
class OrderServiceImpl implements OrderService {

    private ConcurrentHashMap<String, Order> orders = new ConcurrentHashMap<>(); // 模拟订单数据
    private InventoryService inventoryService; // 注入库存服务

    public OrderServiceImpl(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    private static class Order {
        String productId;
        int quantity;
        String transactionId;
        boolean created;
    }

    @Override
    public boolean tryCreateOrder(String productId, int quantity, String transactionId) {
        synchronized (transactionId.intern()) { // 针对每个transactionId加锁,避免并发问题

            // 1. 创建订单记录(但未真正创建)
            Order order = new Order();
            order.productId = productId;
            order.quantity = quantity;
            order.transactionId = transactionId;
            order.created = false;
            orders.put(transactionId, order);

            // 2. 调用库存服务的Try阶段
            boolean deductResult = inventoryService.tryDeduct(productId, quantity, transactionId);
            if (!deductResult) {
                System.out.println("创建订单:Try 扣减库存失败,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
                return false;
            }

            System.out.println("创建订单:Try 成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
            return true;
        }
    }

    @Override
    public boolean confirmCreateOrder(String productId, int quantity, String transactionId) {
        synchronized (transactionId.intern()) { // 针对每个transactionId加锁,避免并发问题
            Order order = orders.get(transactionId);
            if (order == null || order.created) {
                System.out.println("Confirm 创建订单:订单不存在或已创建,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
                return true; // 幂等性
            }

            // 1. 真正创建订单
            order.created = true;

            // 2. 调用库存服务的Confirm阶段
            boolean confirmDeductResult = inventoryService.confirmDeduct(productId, quantity, transactionId);
            if (!confirmDeductResult) {
                System.out.println("Confirm 创建订单:Confirm 扣减库存失败,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
                //  理论上Confirm应该重试,这里简单处理
                return false;
            }

            System.out.println("Confirm 创建订单成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
            return true;
        }
    }

    @Override
    public boolean cancelCreateOrder(String productId, int quantity, String transactionId) {
        synchronized (transactionId.intern()) { // 针对每个transactionId加锁,避免并发问题
            Order order = orders.get(transactionId);
            if (order == null) {
                System.out.println("Cancel 创建订单:订单不存在,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
                return true; // 幂等性
            }

            // 调用库存服务的Cancel阶段
            boolean cancelDeductResult = inventoryService.cancelDeduct(productId, quantity, transactionId);
            if (!cancelDeductResult) {
                System.out.println("Cancel 创建订单:Cancel 扣减库存失败,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
                // 理论上Cancel应该重试,这里简单处理
                return false;
            }

            // 删除订单记录
            orders.remove(transactionId);

            System.out.println("Cancel 创建订单成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
            return true;
        }
    }
}

3. 模拟事务管理器(简化版)

class TransactionManager {

    private OrderService orderService;

    public TransactionManager(OrderService orderService) {
        this.orderService = orderService;
    }

    public boolean purchase(String productId, int quantity, String transactionId) {
        boolean tryResult = orderService.tryCreateOrder(productId, quantity, transactionId);

        if (tryResult) {
            boolean confirmResult = orderService.confirmCreateOrder(productId, quantity, transactionId);
            if (!confirmResult) {
                System.out.println("Confirm 失败,开始回滚");
                orderService.cancelCreateOrder(productId, quantity, transactionId);
                return false;
            }
            return true;
        } else {
            System.out.println("Try 失败,无需回滚");
            return false;
        }
    }
}

4. 测试代码

public class Main {
    public static void main(String[] args) {
        InventoryServiceImpl inventoryService = new InventoryServiceImpl();
        OrderServiceImpl orderService = new OrderServiceImpl(inventoryService);
        TransactionManager transactionManager = new TransactionManager(orderService);

        // 初始化库存
        inventoryService.initInventory("product001", 10);

        // 模拟购买商品
        String transactionId = UUID.randomUUID().toString();
        boolean purchaseResult = transactionManager.purchase("product001", 5, transactionId);

        if (purchaseResult) {
            System.out.println("购买成功,transactionId: " + transactionId);
        } else {
            System.out.println("购买失败,transactionId: " + transactionId);
        }

        // 模拟购买商品,库存不足
        transactionId = UUID.randomUUID().toString();
        purchaseResult = transactionManager.purchase("product001", 15, transactionId);

        if (purchaseResult) {
            System.out.println("购买成功,transactionId: " + transactionId);
        } else {
            System.out.println("购买失败,transactionId: " + transactionId);
        }
    }
}

代码解释:

  • InventoryServiceOrderService 分别定义了库存服务和订单服务的接口,包含 tryDeductconfirmDeductcancelDeducttryCreateOrderconfirmCreateOrdercancelCreateOrder 三个方法,分别对应 TCC 模式的三个阶段。
  • InventoryServiceImplOrderServiceImpl 分别实现了库存服务和订单服务的接口。
  • TransactionManager 模拟了一个简单的事务管理器,负责协调库存服务和订单服务,完成商品购买的事务。
  • tryDeduct 中,我们只是预留了库存,并没有真正扣减库存。
  • confirmDeduct 中,我们真正扣减了库存。
  • cancelDeduct 中,我们释放了预留的库存。
  • 为了保证幂等性,我们在 confirmDeductcancelDeduct 中都进行了判断,如果已经执行过,则直接返回成功。
  • OrderServiceImpl 中,我们先调用库存服务的 tryDeduct 方法,如果成功,则创建订单,并调用库存服务的 confirmDeduct 方法。如果 tryDeduct 失败,则直接返回失败。如果 confirmDeduct 失败,则调用库存服务的 cancelDeduct 方法,进行回滚。

关键点:

  • 锁: 为了避免并发问题,在 InventoryServiceImplOrderServiceImpl 的方法中使用了 synchronized 关键字进行加锁。 实际应用中,可以使用更细粒度的锁,例如行锁。
  • 幂等性: 使用了 transactionId 作为唯一标识符,判断操作是否已经执行过。
  • 状态记录: deductRecords 记录了Try阶段的扣减信息,用于Confirm和Cancel阶段。
  • 异常处理: 代码中只进行了简单的异常处理,实际应用中需要更完善的异常处理机制,例如重试、告警等。

TCC模式的进阶应用

除了上述简单的示例外,TCC模式还可以应用到更复杂的场景中,例如:

  • 分库分表: 在分库分表的场景下,需要将TCC事务拆分成多个子事务,分别在不同的数据库中执行。
  • 消息队列: 可以使用消息队列来异步执行Confirm和Cancel操作,提高系统的吞吐量。
  • 状态机: 可以使用状态机来管理TCC事务的状态,简化代码的编写。

总结:业务层面的最终一致性策略

TCC模式是一种在业务层面实现最终一致性的分布式事务解决方案。它通过将一个事务拆分为Try、Confirm、Cancel三个阶段,允许在业务层面进行资源预留和补偿操作。虽然TCC模式实现较为复杂,但它在性能、灵活性和资源锁定时间等方面具有优势,适用于对数据一致性要求不是特别严格,但对性能要求较高的场景。 在实际应用中,需要根据具体的业务场景选择合适的分布式事务解决方案。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注