使用 Seata 和 TCC 模式实现分布式事务:基于 Spring Cloud Alibaba 的电商案例

使用 Seata 和 TCC 模式实现分布式事务:基于 Spring Cloud Alibaba 的电商案例

Seata 简介

Seata(Simple Extensible Autonomous Transaction Architecture)是一个开源的分布式事务解决方案,旨在为微服务架构提供高性能和易用的分布式事务支持。Seata 支持多种事务模式,包括 AT(Automatic Transaction)、TCC(Try-Confirm-Cancel)、SAGA 和 XA,其中 TCC 模式以其灵活性和高性能在互联网场景中应用广泛。

Seata 的核心组件包括:

  • TC(Transaction Coordinator):事务协调者,负责管理全局事务的状态。
  • TM(Transaction Manager):事务管理器,定义全局事务的范围并发起提交或回滚。
  • RM(Resource Manager):资源管理器,管理分支事务并与数据库交互。

TCC 模式简介

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

  1. Try:尝试执行业务逻辑,预留资源(如冻结库存)。
  2. Confirm:确认阶段,提交业务逻辑(如扣减库存)。
  3. Cancel:取消阶段,回滚操作(如释放冻结的库存)。

TCC 模式适用于需要高并发、高性能的场景,尤其是在互联网电商、金融等领域。它通过业务代码显式定义补偿逻辑,提供了更大的灵活性。

真实案例:电商订单服务

假设我们正在开发一个电商系统,涉及以下微服务:

  • 订单服务(Order Service):创建订单并记录用户信息。
  • 库存服务(Inventory Service):管理商品库存。
  • 支付服务(Payment Service):处理用户支付。

业务场景

用户下单时,系统需要:

  1. 创建订单记录。
  2. 冻结商品库存。
  3. 扣款并记录支付状态。 如果任一步骤失败,则回滚所有操作。

数据库表设计

订单服务表
sql 复制代码
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
);
库存服务表
sql 复制代码
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
);
支付服务表
sql 复制代码
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
);

服务设计与调用

我们使用 Spring Cloud Alibaba、Seata TCC 和 Feign 实现服务间调用。

1. 订单服务(Order Service)

订单服务作为全局事务的发起者,协调库存和支付服务。

TCC 接口定义
java 复制代码
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);
}
实现
java 复制代码
@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;
    }
}
Feign 客户端
java 复制代码
@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);
}

2. 库存服务(Inventory Service)

库存服务实现冻结、确认和取消逻辑。

TCC 接口
java 复制代码
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);
}
实现
java 复制代码
@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;
    }
}

3. 支付服务(Payment Service)

支付服务处理支付冻结和扣款。

TCC 接口
java 复制代码
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);
}
实现
java 复制代码
@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;
    }
}

全局事务发起

在订单服务中,使用 @GlobalTransactional 注解发起全局事务:

java 复制代码
@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";
    }
}

总结

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

相关推荐
苏三说技术5 分钟前
Excel百万数据如何快速导入?
后端
昵称为空C6 分钟前
SpringBoot编码技巧-ScheduledExecutorService轮询
java·spring boot·后端
huangyingying202537 分钟前
03-分支结构
后端
00后程序员39 分钟前
【Flutter -- 基础组件】Flutter 导航栏
后端
bobz96542 分钟前
ovs internal port 对比 veth-pair 性能
后端
Auroral15643 分钟前
基于RabbitMQ的异步通知系统设计与实现
前端·后端
易元1 小时前
设计模式-代理模式
java·后端
嘻嘻哈哈开森1 小时前
Java开发工程师转AI工程师
人工智能·后端
LTPP1 小时前
自动化 Rust 开发的革命性工具:lombok-macros
前端·后端·github
一个热爱生活的普通人1 小时前
Go语言中 Mutex 的实现原理
后端·go