MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

基于 TCC 模式的微服务事务管理

2022-06-101.3k 阅读

微服务架构中的事务管理挑战

在传统的单体应用架构中,事务管理相对较为简单。数据库提供了诸如 ACID(原子性、一致性、隔离性、持久性)特性来确保事务的正确执行。应用程序可以在单个数据库连接中执行一系列操作,并通过数据库的事务机制来保证这些操作要么全部成功,要么全部失败。

然而,随着微服务架构的兴起,事务管理变得复杂得多。微服务架构将一个大型应用拆分成多个小型、自治的服务,每个服务有自己独立的数据库。这种分布式的架构导致了经典的 ACID 事务模型难以直接应用。主要挑战如下:

网络问题

在分布式环境中,网络不稳定是常态。服务之间通过网络进行通信,请求可能会超时、延迟或丢失。例如,当一个微服务尝试调用另一个微服务来完成事务的一部分操作时,网络故障可能导致调用失败,使得事务无法完整执行。

服务自治性

每个微服务都有自己独立的数据库,不同的微服务可能使用不同类型的数据库(如关系型数据库、NoSQL 数据库等)。这意味着无法像单体应用那样在单个数据库连接中管理事务,因为涉及多个数据库的操作无法直接利用传统数据库的事务机制。

一致性要求

在微服务架构中,数据一致性变得更加难以保证。由于服务之间的异步通信和数据的分散存储,一个服务的数据更新可能不会立即反映到其他相关服务,从而导致数据在一定时间内处于不一致状态。

TCC 模式概述

TCC(Try - Confirm - Cancel)模式是一种针对分布式事务的解决方案,它通过业务层面的补偿机制来模拟事务的原子性。TCC 模式将一个事务分成三个阶段:

Try 阶段

尝试执行业务操作。在这个阶段,主要完成对业务资源的初步检查和预留。例如,在一个涉及订单创建和库存扣减的事务中,Try 阶段会检查库存是否足够,并锁定相应的库存数量,但并不真正扣减库存。Try 阶段的操作应该是幂等的,即多次执行相同的 Try 操作,其结果应该是一致的。

Confirm 阶段

确认执行业务操作。当 Try 阶段所有相关服务都成功执行后,进入 Confirm 阶段。在这个阶段,真正执行对业务资源的操作,如扣减库存。Confirm 阶段的操作同样应该是幂等的,因为在网络不稳定等情况下,可能会重复调用 Confirm 操作。

Cancel 阶段

取消执行业务操作。如果 Try 阶段有任何一个服务失败,或者在 Confirm 阶段部分服务失败,就需要执行 Cancel 阶段。Cancel 阶段会释放 Try 阶段预留的业务资源,如解锁之前锁定的库存。Cancel 阶段的操作也必须是幂等的。

TCC 模式的实现原理

事务协调器

在 TCC 模式中,通常需要一个事务协调器来管理整个事务的生命周期。事务协调器负责协调各个参与事务的微服务,跟踪 Try、Confirm 和 Cancel 阶段的执行状态。

事务协调器可以采用集中式或分布式的方式实现。集中式的事务协调器容易实现,但存在单点故障问题;分布式的事务协调器则可以提高系统的可用性和扩展性,但实现起来更为复杂。

资源管理器

每个参与事务的微服务都充当资源管理器的角色。资源管理器负责实现 Try、Confirm 和 Cancel 三个接口,以完成对本地业务资源的操作。

例如,在一个电商系统中,订单服务和库存服务都是资源管理器。订单服务的 Try 接口会检查订单信息的合法性并预留订单号,Confirm 接口会正式创建订单,Cancel 接口会释放预留的订单号;库存服务的 Try 接口会检查库存并锁定库存,Confirm 接口会扣减库存,Cancel 接口会解锁库存。

幂等性实现

如前文所述,Try、Confirm 和 Cancel 操作都必须是幂等的。实现幂等性的常见方法有:

  1. 使用唯一标识符:在每次请求中携带一个唯一的标识符,服务端根据这个标识符来判断请求是否已经处理过。例如,在订单创建事务中,订单号可以作为唯一标识符。当接收到创建订单的请求时,先检查数据库中是否已经存在该订单号对应的记录,如果存在则直接返回成功,不再重复执行创建操作。
  2. 状态机控制:通过状态机来记录业务操作的状态。例如,订单的状态可以分为“待创建”“创建中”“已创建”等。在执行创建订单操作时,首先检查订单状态,如果已经是“已创建”,则不重复执行创建操作。

基于 TCC 模式的微服务事务管理示例

为了更好地理解 TCC 模式的实际应用,我们以一个简单的电商系统为例,该系统包含订单服务和库存服务,实现一个下单并扣减库存的事务。

订单服务

  1. Try 阶段
@Service
public class OrderServiceTry {
    @Autowired
    private OrderRepository orderRepository;

    public boolean tryCreateOrder(Order order) {
        // 检查订单信息合法性
        if (order == null || order.getUserId() == null || order.getProductId() == null) {
            return false;
        }
        // 预留订单号
        Order preOrder = new Order();
        preOrder.setOrderNo(UUID.randomUUID().toString());
        preOrder.setUserId(order.getUserId());
        preOrder.setProductId(order.getProductId());
        preOrder.setStatus("PRE_CREATED");
        orderRepository.save(preOrder);
        return true;
    }
}
  1. Confirm 阶段
@Service
public class OrderServiceConfirm {
    @Autowired
    private OrderRepository orderRepository;

    public boolean confirmCreateOrder(String orderNo) {
        Order order = orderRepository.findByOrderNo(orderNo);
        if (order == null ||!"PRE_CREATED".equals(order.getStatus())) {
            return false;
        }
        order.setStatus("CREATED");
        orderRepository.save(order);
        return true;
    }
}
  1. Cancel 阶段
@Service
public class OrderServiceCancel {
    @Autowired
    private OrderRepository orderRepository;

    public boolean cancelCreateOrder(String orderNo) {
        Order order = orderRepository.findByOrderNo(orderNo);
        if (order == null ||!"PRE_CREATED".equals(order.getStatus())) {
            return false;
        }
        orderRepository.delete(order);
        return true;
    }
}

库存服务

  1. Try 阶段
@Service
public class InventoryServiceTry {
    @Autowired
    private InventoryRepository inventoryRepository;

    public boolean tryDeductInventory(String productId, int quantity) {
        Inventory inventory = inventoryRepository.findByProductId(productId);
        if (inventory == null || inventory.getQuantity() < quantity) {
            return false;
        }
        inventory.setQuantity(inventory.getQuantity() - quantity);
        inventory.setStatus("LOCKED");
        inventoryRepository.save(inventory);
        return true;
    }
}
  1. Confirm 阶段
@Service
public class InventoryServiceConfirm {
    @Autowired
    private InventoryRepository inventoryRepository;

    public boolean confirmDeductInventory(String productId) {
        Inventory inventory = inventoryRepository.findByProductId(productId);
        if (inventory == null ||!"LOCKED".equals(inventory.getStatus())) {
            return false;
        }
        inventory.setStatus("DEDUCTED");
        inventoryRepository.save(inventory);
        return true;
    }
}
  1. Cancel 阶段
@Service
public class InventoryServiceCancel {
    @Autowired
    private InventoryRepository inventoryRepository;

    public boolean cancelDeductInventory(String productId, int quantity) {
        Inventory inventory = inventoryRepository.findByProductId(productId);
        if (inventory == null ||!"LOCKED".equals(inventory.getStatus())) {
            return false;
        }
        inventory.setQuantity(inventory.getQuantity() + quantity);
        inventory.setStatus("NORMAL");
        inventoryRepository.save(inventory);
        return true;
    }
}

事务协调器

假设使用 Spring Boot 和 Spring Cloud 实现一个简单的事务协调器:

@Service
public class TransactionCoordinator {
    @Autowired
    private OrderServiceTry orderServiceTry;
    @Autowired
    private OrderServiceConfirm orderServiceConfirm;
    @Autowired
    private OrderServiceCancel orderServiceCancel;
    @Autowired
    private InventoryServiceTry inventoryServiceTry;
    @Autowired
    private InventoryServiceConfirm inventoryServiceConfirm;
    @Autowired
    private InventoryServiceCancel inventoryServiceCancel;

    public boolean executeTransaction(Order order) {
        // Try 阶段
        boolean orderTryResult = orderServiceTry.tryCreateOrder(order);
        boolean inventoryTryResult = inventoryServiceTry.tryDeductInventory(order.getProductId(), order.getQuantity());
        if (!orderTryResult ||!inventoryTryResult) {
            // Try 失败,执行 Cancel
            orderServiceCancel.cancelCreateOrder(order.getOrderNo());
            inventoryServiceCancel.cancelDeductInventory(order.getProductId(), order.getQuantity());
            return false;
        }
        // Confirm 阶段
        boolean orderConfirmResult = orderServiceConfirm.confirmCreateOrder(order.getOrderNo());
        boolean inventoryConfirmResult = inventoryServiceConfirm.confirmDeductInventory(order.getProductId());
        if (!orderConfirmResult ||!inventoryConfirmResult) {
            // Confirm 失败,执行 Cancel
            orderServiceCancel.cancelCreateOrder(order.getOrderNo());
            inventoryServiceCancel.cancelDeductInventory(order.getProductId(), order.getQuantity());
            return false;
        }
        return true;
    }
}

TCC 模式的优缺点

优点

  1. 灵活性高:TCC 模式通过业务层面的补偿机制实现事务管理,不需要依赖底层数据库的分布式事务支持,适用于各种类型的数据库和微服务架构。
  2. 性能较好:与传统的分布式事务(如 XA 事务)相比,TCC 模式在 Try 阶段只进行资源预留,不进行实际的业务操作,减少了资源锁定的时间,提高了系统的并发性能。
  3. 易于实现:TCC 模式的实现相对简单,只需要在业务代码中添加 Try、Confirm 和 Cancel 三个接口的实现,不需要引入复杂的分布式事务框架。

缺点

  1. 代码侵入性:TCC 模式需要在业务代码中添加额外的 Try、Confirm 和 Cancel 逻辑,增加了业务代码的复杂度和维护成本。
  2. 依赖幂等性:TCC 模式的正确性高度依赖于 Try、Confirm 和 Cancel 操作的幂等性。如果幂等性实现不当,可能会导致事务执行错误。
  3. 网络问题敏感:由于 TCC 模式依赖网络通信来协调各个服务的操作,网络故障可能导致事务状态不一致,需要额外的机制来处理网络异常情况。

TCC 模式的应用场景

  1. 电商系统:如订单创建、库存扣减、支付等场景,这些场景对数据一致性要求较高,同时涉及多个微服务之间的交互。
  2. 金融系统:在转账、支付结算等业务中,确保资金的准确流转和数据的一致性是至关重要的,TCC 模式可以有效地解决分布式事务问题。
  3. 物流系统:例如在订单分配、货物运输等过程中,需要协调多个服务来完成一个完整的业务流程,TCC 模式可以保证业务操作的原子性和数据一致性。

在实际应用中,需要根据具体的业务需求和系统架构特点来选择是否使用 TCC 模式进行微服务事务管理。同时,要充分考虑 TCC 模式的优缺点,采取相应的措施来优化和保障系统的性能与稳定性。

TCC 模式与其他事务管理模式的比较

与 XA 事务比较

  1. 实现原理:XA 事务是基于数据库层面的分布式事务协议,通过数据库的两阶段提交(2PC)来保证事务的原子性。而 TCC 模式是在业务层面实现的补偿型事务,通过 Try、Confirm 和 Cancel 操作来模拟事务。
  2. 性能:XA 事务在执行过程中会长期锁定资源,因为在准备阶段(第一阶段)数据库资源就被锁定,直到提交阶段(第二阶段)结束才释放,这会降低系统的并发性能。TCC 模式在 Try 阶段只进行资源预留,不进行实际业务操作,资源锁定时间较短,并发性能相对较好。
  3. 适用场景:XA 事务适用于对数据一致性要求极高,且参与事务的数据库支持 XA 协议的场景。TCC 模式更适用于微服务架构中,各服务使用不同类型数据库,且对性能和灵活性要求较高的场景。

与本地消息表模式比较

  1. 实现原理:本地消息表模式是通过在每个微服务的数据库中创建消息表,将事务操作记录在消息表中,然后通过定时任务或消息队列来异步处理消息,以实现事务的最终一致性。TCC 模式则是通过业务层面的补偿机制来保证事务的原子性。
  2. 一致性保证:本地消息表模式只能保证最终一致性,在消息处理过程中,数据可能会在一段时间内处于不一致状态。TCC 模式可以通过 Try、Confirm 和 Cancel 操作,在一定程度上保证事务的强一致性。
  3. 复杂度:本地消息表模式实现相对简单,只需要在数据库中添加消息表和相应的消息处理逻辑。TCC 模式需要在业务代码中添加 Try、Confirm 和 Cancel 三个接口的实现,对业务代码的侵入性较大,实现复杂度较高。

TCC 模式的优化策略

异步执行

在 TCC 模式中,可以将 Confirm 和 Cancel 操作异步化执行。例如,在事务协调器完成 Try 阶段并确认所有服务的 Try 操作成功后,可以将 Confirm 操作放入消息队列中,由专门的消费者异步执行。这样可以减少事务的响应时间,提高系统的并发性能。

批量处理

对于一些频繁的事务操作,可以采用批量处理的方式。例如,在库存扣减场景中,如果有多个订单同时需要扣减库存,可以将这些订单的库存扣减操作合并成一个批量操作,在 Try 阶段一次性检查和预留库存,在 Confirm 阶段一次性扣减库存。这样可以减少数据库的交互次数,提高系统性能。

缓存优化

在 TCC 模式中,可以利用缓存来优化资源检查和预留操作。例如,在库存服务的 Try 阶段,可以先从缓存中获取库存信息进行检查和预留,只有在缓存操作成功后,再更新数据库中的库存状态。这样可以减少数据库的负载,提高系统的响应速度。

TCC 模式的故障处理

网络故障

  1. 重试机制:当网络故障导致服务调用失败时,可以采用重试机制。例如,在事务协调器调用微服务的 Try、Confirm 或 Cancel 接口失败后,可以根据一定的策略进行重试,如固定间隔重试、指数退避重试等。
  2. 补偿日志:为了防止重试过程中出现重复操作,可以记录补偿日志。在每次执行 Try、Confirm 或 Cancel 操作时,先检查补偿日志,确保操作没有重复执行。

服务故障

  1. 服务熔断:当某个微服务出现故障时,可以采用服务熔断机制。例如,在事务协调器调用某个微服务多次失败后,暂时停止对该微服务的调用,直接返回失败结果,并执行 Cancel 操作。
  2. 备用服务:可以为关键的微服务设置备用服务。当主服务出现故障时,事务协调器可以切换到备用服务继续执行事务。

通过合理的优化策略和故障处理机制,可以进一步提高基于 TCC 模式的微服务事务管理系统的性能和稳定性。在实际应用中,需要根据系统的具体需求和特点,灵活选择和组合这些策略,以实现高效、可靠的分布式事务管理。