基于Spring Cloud Alibaba用Seata和TCC模式搞定分布式事务

后端 潘老师 3周前 (03-31) 18 ℃ (0) 扫码查看

在微服务架构盛行的当下,分布式事务管理成为开发过程中绕不开的关键难题。今天,我们就借助Seata和TCC模式,并基于Spring Cloud Alibaba框架,通过一个电商案例来深入探讨如何实现分布式事务管理。

一、Seata与TCC模式基础介绍

1.1 Seata简介

Seata(Simple Extensible Autonomous Transaction Architecture)是一款开源的分布式事务解决方案,主要为微服务架构提供高性能且易用的分布式事务支持。它支持多种事务模式,其中TCC(Try-Confirm-Cancel)模式凭借其灵活性和高性能,在互联网场景尤其是电商、金融等领域得到广泛应用。

Seata包含几个核心组件:

  • TC(Transaction Coordinator):即事务协调者,负责对全局事务的状态进行管理,掌控整个事务的走向。
  • TM(Transaction Manager):也就是事务管理器,用于定义全局事务的范围,并负责发起事务的提交或回滚操作。
  • RM(Resource Manager):资源管理器,主要管理分支事务,承担与数据库交互的工作。

1.2 TCC模式解析

TCC模式是一种基于补偿机制的分布式事务模式,整个过程分为三个阶段:

  • Try阶段:尝试执行业务逻辑,这个阶段主要是预留相关资源。以电商场景为例,就是冻结库存,确保后续业务有资源可用。
  • Confirm阶段:确认阶段,用于提交业务逻辑。对应到电商场景,就是真正扣减库存,完成业务操作。
  • Cancel阶段:取消阶段,主要进行回滚操作。比如释放之前冻结的库存,保证数据的一致性。

TCC模式适用于对高并发和高性能有较高要求的场景,通过在业务代码中显式定义补偿逻辑,给予开发者更大的操作灵活性。

二、电商系统案例分析

2.1 业务场景与系统架构

假设我们正在构建一个电商系统,该系统包含以下几个关键微服务:

  • 订单服务(Order Service):负责创建订单,并记录用户相关信息。
  • 库存服务(Inventory Service):主要承担商品库存的管理工作。
  • 支付服务(Payment Service):用于处理用户的支付流程。

在用户下单时,系统需要完成一系列操作:创建订单记录、冻结商品库存、扣款并记录支付状态。只要其中任何一个步骤出现问题,就必须回滚所有操作,以此保证数据的一致性。

2.2 数据库表设计

为了支撑系统功能,设计了以下数据库表:

  • 订单服务表
CREATE TABLE `orders` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
  `user_id` BIGINT NOT NULL,
  `product_id` BIGINT NOT NULL,
  `quantity` INT NOT NULL,
  `total_amount` DECIMAL(10, 2) NOT NULL,
  `status` VARCHAR(20) NOT NULL DEFAULT 'PENDING' -- PENDING, CONFIRMED, CANCELLED
);

该表用于存储订单相关信息,包括订单编号、用户ID、商品ID、购买数量、总金额以及订单状态等。

  • 库存服务表
CREATE TABLE `inventory` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
  `product_id` BIGINT NOT NULL,
  `total_stock` INT NOT NULL,
  `frozen_stock` INT NOT NULL DEFAULT 0
);

此表记录商品库存情况,包含商品ID、总库存数量以及冻结库存数量。

  • 支付服务表
CREATE TABLE `payment` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
  `order_id` BIGINT NOT NULL,
  `user_id` BIGINT NOT NULL,
  `amount` DECIMAL(10, 2) NOT NULL,
  `status` VARCHAR(20) NOT NULL DEFAULT 'PENDING' -- PENDING, SUCCESS, FAILED
);

支付服务表主要存储支付相关信息,如订单ID、用户ID、支付金额以及支付状态。

2.3 服务设计与调用实现

我们借助Spring Cloud Alibaba、Seata TCC和Feign来实现服务间的调用。

  1. 订单服务(Order Service):订单服务在整个事务中扮演全局事务发起者的角色,负责协调库存和支付服务。
    • TCC接口定义
public interface OrderTccService {
    @TwoPhaseBusinessAction(name = "OrderTccAction", commitMethod = "confirm", rollbackMethod = "cancel")
    void tryCreate(OrderDTO orderDTO, @BusinessActionContextParameter(paramName = "orderId") Long orderId);

    boolean confirm(BusinessActionContext context);

    boolean cancel(BusinessActionContext context);
}

该接口定义了订单服务在TCC模式下的三个核心方法,tryCreate用于尝试创建订单,confirm用于确认订单操作,cancel用于取消订单操作。

- **接口实现**:
@Service
public class OrderTccServiceImpl implements OrderTccService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private InventoryClient inventoryClient;
    @Autowired
    private PaymentClient paymentClient;

    @Override
    @Transactional
    public void tryCreate(OrderDTO orderDTO, Long orderId) {
        Order order = new Order();
        order.setUserId(orderDTO.getUserId());
        order.setProductId(orderDTO.getProductId());
        order.setQuantity(orderDTO.getQuantity());
        order.setTotalAmount(orderDTO.getTotalAmount());
        order.setStatus("PENDING");
        orderMapper.insert(order);

        // 调用库存服务冻结库存
        inventoryClient.freezeStock(order.getProductId(), order.getQuantity());
        // 调用支付服务冻结金额
        paymentClient.freezePayment(order.getId(), order.getTotalAmount());
    }

    @Override
    public boolean confirm(BusinessActionContext context) {
        Long orderId = (Long) context.getActionContext("orderId");
        Order order = orderMapper.selectById(orderId);
        order.setStatus("CONFIRMED");
        orderMapper.updateById(order);
        return true;
    }

    @Override
    public boolean cancel(BusinessActionContext context) {
        Long orderId = (Long) context.getActionContext("orderId");
        Order order = orderMapper.selectById(orderId);
        order.setStatus("CANCELLED");
        orderMapper.updateById(order);
        return true;
    }
}

在实现类中,tryCreate方法在创建订单记录后,调用库存服务和支付服务进行资源预留。confirmcancel方法分别用于处理订单确认和取消时的逻辑。

- **Feign客户端**:
@FeignClient(name = "inventory-service")
public interface InventoryClient {
    @PostMapping("/inventory/freeze")
    void freezeStock(@RequestParam("productId") Long productId, @RequestParam("quantity") Integer quantity);
}

@FeignClient(name = "payment-service")
public interface PaymentClient {
    @PostMapping("/payment/freeze")
    void freezePayment(@RequestParam("orderId") Long orderId, @RequestParam("amount") BigDecimal amount);
}

Feign客户端用于实现订单服务与库存服务、支付服务之间的远程调用。

  1. 库存服务(Inventory Service):库存服务主要实现冻结、确认和取消库存的逻辑。
    • TCC接口
public interface InventoryTccService {
    @TwoPhaseBusinessAction(name = "InventoryTccAction", commitMethod = "confirm", rollbackMethod = "cancel")
    void freezeStock(@BusinessActionContextParameter(paramName = "productId") Long productId, Integer quantity);

    boolean confirm(BusinessActionContext context);

    boolean cancel(BusinessActionContext context);
}

定义了库存服务在TCC模式下的相关方法。

- **接口实现**:
@RestController
@RequestMapping("/inventory")
public class InventoryTccServiceImpl implements InventoryTccService {
    @Autowired
    private InventoryMapper inventoryMapper;

    @Override
    @PostMapping("/freeze")
    @Transactional
    public void freezeStock(Long productId, Integer quantity) {
        Inventory inventory = inventoryMapper.selectByProductId(productId);
        if (inventory.getTotalStock() - inventory.getFrozenStock() < quantity) {
            throw new RuntimeException("Insufficient stock");
        }
        inventory.setFrozenStock(inventory.getFrozenStock() + quantity);
        inventoryMapper.updateById(inventory);
    }

    @Override
    public boolean confirm(BusinessActionContext context) {
        Long productId = (Long) context.getActionContext("productId");
        Inventory inventory = inventoryMapper.selectByProductId(productId);
        Integer quantity = (Integer) context.getActionContext("quantity");
        inventory.setFrozenStock(inventory.getFrozenStock() - quantity);
        inventory.setTotalStock(inventory.getTotalStock() - quantity);
        inventoryMapper.updateById(inventory);
        return true;
    }

    @Override
    public boolean cancel(BusinessActionContext context) {
        Long productId = (Long) context.getActionContext("productId");
        Inventory inventory = inventoryMapper.selectByProductId(productId);
        Integer quantity = (Integer) context.getActionContext("quantity");
        inventory.setFrozenStock(inventory.getFrozenStock() - quantity);
        inventoryMapper.updateById(inventory);
        return true;
    }
}

freezeStock方法用于检查库存并冻结相应数量的库存,confirmcancel方法分别处理确认和取消操作时的库存变化。

  1. 支付服务(Payment Service):支付服务负责处理支付冻结和扣款操作。
    • TCC接口
public interface PaymentTccService {
    @TwoPhaseBusinessAction(name = "PaymentTccAction", commitMethod = "confirm", rollbackMethod = "cancel")
    void freezePayment(@BusinessActionContextParameter(paramName = "orderId") Long orderId, BigDecimal amount);

    boolean confirm(BusinessActionContext context);

    boolean cancel(BusinessActionContext context);
}

定义了支付服务在TCC模式下的操作方法。

- **接口实现**:
@RestController
@RequestMapping("/payment")
public class PaymentTccServiceImpl implements PaymentTccService {
    @Autowired
    private PaymentMapper paymentMapper;

    @Override
    @PostMapping("/freeze")
    @Transactional
    public void freezePayment(Long orderId, BigDecimal amount) {
        Payment payment = new Payment();
        payment.setOrderId(orderId);
        payment.setAmount(amount);
        payment.setStatus("PENDING");
        paymentMapper.insert(payment);
    }

    @Override
    public boolean confirm(BusinessActionContext context) {
        Long orderId = (Long) context.getActionContext("orderId");
        Payment payment = paymentMapper.selectByOrderId(orderId);
        payment.setStatus("SUCCESS");
        paymentMapper.updateById(payment);
        return true;
    }

    @Override
    public boolean cancel(BusinessActionContext context) {
        Long orderId = (Long) context.getActionContext("orderId");
        Payment payment = paymentMapper.selectByOrderId(orderId);
        payment.setStatus("FAILED");
        paymentMapper.updateById(payment);
        return true;
    }
}

freezePayment方法用于冻结支付金额,confirmcancel方法分别处理支付成功和取消时的逻辑。

2.4 全局事务发起

在订单服务的控制器中,使用@GlobalTransactional注解来发起全局事务:

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderTccService orderTccService;

    @PostMapping("/create")
    @GlobalTransactional
    public String createOrder(@RequestBody OrderDTO orderDTO) {
        orderTccService.tryCreate(orderDTO, null);
        return "Order created successfully";
    }
}

当用户发起创建订单请求时,@GlobalTransactional注解会开启一个全局事务,确保整个下单流程的数据一致性。

三、总结

通过Seata的TCC模式,我们成功在电商系统中实现了分布式事务管理。在这个过程中,订单服务作为TM发起全局事务,库存服务和支付服务作为RM分别处理各自的资源预留和补偿逻辑。借助Spring Cloud Alibaba的Feign完成服务间的调用,利用MyBatis实现数据库操作。这种方案在高并发场景下,既保证了数据的一致性,又具备良好的性能表现,为电商系统等分布式应用的开发提供了可靠的事务管理方式。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/16701.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】