使用 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 实现数据库操作。这种方案在高并发场景下既保证了数据一致性,又提供了良好的性能。

相关推荐
泉城老铁1 小时前
Spring Boot 对接阿里云 OSS 的详细步骤和流程
java·后端·程序员
Aurora_NeAr1 小时前
大数据之路:阿里巴巴大数据实践——元数据与计算管理
大数据·后端
喜欢板砖的牛马1 小时前
容器(docker container):你需要知道的一切
后端·docker
lichenyang4531 小时前
从零开始学Express,理解服务器,路由于中间件
后端
EnigmaGcl1 小时前
领域驱动设计,到底在讲什么?
后端·架构
丘山子2 小时前
API Gateway 工作原理介绍
前端·后端·面试
砌玉成璧2 小时前
Flask一个用户同时只能在一处登录实现
后端·python·flask
元闰子2 小时前
分离还是统一,这是个问题
后端·面试·架构
寻月隐君2 小时前
Rust Scoped Threads 实战:更安全、更简洁的并发编程
后端·rust·github
爷_3 小时前
手把手教程:用腾讯云新平台搞定专属开发环境,永久免费薅羊毛!
前端·后端·架构