12.6AT模式实战

分类: Seata分布式事务

AT 模式实战

本节将通过实战演示,学习如何在项目中使用 Seata AT 模式。本节将学习 AT 模式实战。

本节将学习:添加 Seata 依赖、配置文件配置、数据源代理配置、全局事务注解,以及事务测试。

在商城项目中集成 Seata AT 模式

步骤1:添加 Seata 依赖

文件路径: mall-microservices/order-service/pom.xml

<!-- Seata --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency> <!-- Seata 数据源代理 --> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </dependency>

同样需要在库存服务和支付服务中添加 Seata 依赖。

步骤2:配置 Seata

订单服务配置

文件路径: mall-microservices/order-service/src/main/resources/application.yml

spring: cloud: alibaba: seata: tx-service-group: mall_tx_group seata: enabled: true application-id: order-service tx-service-group: mall_tx_group config: type: nacos nacos: server-addr: localhost:8848 group: SEATA_GROUP namespace: "" data-id: seataServer.properties registry: type: nacos nacos: application: seata-server server-addr: localhost:8848 group: SEATA_GROUP namespace: "" cluster: default

库存服务配置

文件路径: mall-microservices/inventory-service/src/main/resources/application.yml

spring: cloud: alibaba: seata: tx-service-group: mall_tx_group seata: enabled: true application-id: inventory-service tx-service-group: mall_tx_group # 配置同订单服务

支付服务配置

文件路径: mall-microservices/payment-service/src/main/resources/application.yml

spring: cloud: alibaba: seata: tx-service-group: mall_tx_group seata: enabled: true application-id: payment-service tx-service-group: mall_tx_group # 配置同订单服务

步骤3:配置数据源代理

订单服务数据源配置

文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/config/DataSourceConfig.java

package com.mall.orderservice.config; import com.zaxxer.hikari.HikariDataSource; import io.seata.rm.datasource.DataSourceProxy; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties("spring.datasource") public HikariDataSource dataSource() { return new HikariDataSource(); } @Primary @Bean("dataSource") public DataSource dataSourceProxy(HikariDataSource dataSource) { return new DataSourceProxy(dataSource); } }

注意:库存服务和支付服务也需要同样的数据源代理配置。

创建 undo_log 表

每个参与分布式事务的数据库都需要创建 undo_log 表:

文件路径: mall-microservices/order-service/src/main/resources/db/undo_log.sql

-- Seata AT 模式需要的 undo_log 表 CREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id', `xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id', `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization', `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info', `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status', `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime', `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime', UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AT transaction mode undo table';

同样需要在库存服务和支付服务的数据库中创建 undo_log 表。

步骤4:使用 @GlobalTransactional 注解

订单创建分布式事务

文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/service/impl/OrderServiceImpl.java

package com.mall.orderservice.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mall.orderservice.entity.Order; import com.mall.orderservice.feign.InventoryServiceClient; import com.mall.orderservice.feign.PaymentServiceClient; import com.mall.orderservice.mapper.OrderMapper; import com.mall.orderservice.service.OrderService; import io.seata.spring.annotation.GlobalTransactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.UUID; @Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService { @Autowired private InventoryServiceClient inventoryServiceClient; @Autowired private PaymentServiceClient paymentServiceClient; @Override @GlobalTransactional(rollbackFor = Exception.class, timeoutMills = 30000) public Order createOrder(Order order) { // 1. 创建订单(本地事务) order.setOrderNo(generateOrderNo()); order.setStatus(0); // 待支付 save(order); // 2. 扣减库存(远程调用,参与分布式事务) inventoryServiceClient.deductStock(order.getProductId(), order.getQuantity()); // 3. 创建支付订单(远程调用,参与分布式事务) PaymentCreateDTO paymentDTO = new PaymentCreateDTO(); paymentDTO.setOrderId(order.getId()); paymentDTO.setAmount(order.getPayAmount()); paymentServiceClient.createPayment(paymentDTO); // 4. 如果任何一步失败,所有操作都会回滚 return order; } private String generateOrderNo() { return "ORD" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } }

库存服务扣减库存

文件路径: mall-microservices/inventory-service/src/main/java/com/mall/inventoryservice/service/impl/InventoryServiceImpl.java

package com.mall.inventoryservice.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mall.inventoryservice.entity.Inventory; import com.mall.inventoryservice.mapper.InventoryMapper; import com.mall.inventoryservice.service.InventoryService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class InventoryServiceImpl extends ServiceImpl<InventoryMapper, Inventory> implements InventoryService { @Override @Transactional(rollbackFor = Exception.class) public void deductStock(Long productId, Integer quantity) { Inventory inventory = baseMapper.selectOne( new LambdaQueryWrapper<Inventory>() .eq(Inventory::getProductId, productId) ); if (inventory == null) { throw new RuntimeException("Inventory not found for product: " + productId); } if (inventory.getAvailableStock() < quantity) { throw new RuntimeException("Insufficient stock"); } // 扣减库存(Seata 会自动记录 undo_log) inventory.setAvailableStock(inventory.getAvailableStock() - quantity); inventory.setLockedStock(inventory.getLockedStock() + quantity); updateById(inventory); } }

支付服务创建支付订单

文件路径: mall-microservices/payment-service/src/main/java/com/mall/paymentservice/service/impl/PaymentServiceImpl.java

package com.mall.paymentservice.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mall.paymentservice.entity.PaymentOrder; import com.mall.paymentservice.mapper.PaymentMapper; import com.mall.paymentservice.service.PaymentService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, PaymentOrder> implements PaymentService { @Override @Transactional(rollbackFor = Exception.class) public PaymentOrder createPayment(PaymentCreateDTO paymentDTO) { PaymentOrder payment = new PaymentOrder(); payment.setOrderId(paymentDTO.getOrderId()); payment.setAmount(paymentDTO.getAmount()); payment.setStatus(0); // 待支付 payment.setPaymentNo(generatePaymentNo()); // 创建支付订单(Seata 会自动记录 undo_log) save(payment); return payment; } private String generatePaymentNo() { return "PAY" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } }

步骤5:测试分布式事务

测试场景1:正常提交

测试步骤:

  1. 启动所有服务

    # 启动 Seata Server cd mall-microservices/docker/seata docker-compose -f docker-compose-nacos.yml up -d # 启动订单服务、库存服务、支付服务
  2. 创建订单

    curl -X POST http://localhost:8083/api/orders \ -H "Content-Type: application/json" \ -d '{ "userId": 1, "productId": 1, "quantity": 2, "totalAmount": 200.00, "payAmount": 200.00 }'
  3. 验证事务提交

    • 订单创建成功
    • 库存扣减成功
    • 支付订单创建成功
    • 所有操作都在同一个全局事务中

测试场景2:异常回滚

修改支付服务,模拟异常:

文件路径: mall-microservices/payment-service/src/main/java/com/mall/paymentservice/service/impl/PaymentServiceImpl.java

@Override @Transactional(rollbackFor = Exception.class) public PaymentOrder createPayment(PaymentCreateDTO paymentDTO) { PaymentOrder payment = new PaymentOrder(); payment.setOrderId(paymentDTO.getOrderId()); payment.setAmount(paymentDTO.getAmount()); payment.setStatus(0); payment.setPaymentNo(generatePaymentNo()); save(payment); // 模拟异常,触发回滚 if (paymentDTO.getAmount().compareTo(new BigDecimal("1000")) > 0) { throw new RuntimeException("Payment amount too large"); } return payment; }

测试步骤:

  1. 创建大额订单(触发异常):

    curl -X POST http://localhost:8083/api/orders \ -H "Content-Type: application/json" \ -d '{ "userId": 1, "productId": 1, "quantity": 2, "totalAmount": 2000.00, "payAmount": 2000.00 }'
  2. 验证事务回滚

    • 订单创建失败(回滚)
    • 库存扣减失败(回滚)
    • 支付订单创建失败(回滚)
    • 所有操作都回滚,数据保持一致

验证分布式事务

查看 Seata 控制台:

  1. 访问 Seata 控制台(如果有)
  2. 查看全局事务列表
  3. 查看事务状态和分支事务

查看数据库:

  1. 查看 undo_log 表

    SELECT * FROM undo_log ORDER BY log_created DESC LIMIT 10;
  2. 验证数据一致性

    • 订单表:订单应该存在或不存在(取决于事务是否提交)
    • 库存表:库存应该扣减或未扣减(取决于事务是否提交)
    • 支付表:支付订单应该存在或不存在(取决于事务是否提交)

官方资源

根据 Seata 快速开始指南

  1. AT 模式原理:官方文档详细说明了 AT 模式的工作原理,包括如何通过数据源代理自动管理事务、如何记录 undo_log、如何实现自动回滚等。文档强调,AT 模式对业务代码零侵入,开发者只需使用 @GlobalTransactional 注解即可。

  2. 快速开始:官方文档提供了完整的快速开始指南,包括如何搭建 Seata Server、如何配置数据源代理、如何编写业务代码等。文档提供了详细的步骤说明和代码示例,帮助开发者快速上手。

  3. 最佳实践:官方文档总结了 AT 模式的最佳实践,包括如何选择合适的事务模式、如何优化性能、如何处理异常等。文档特别强调了 undo_log 表的设计和优化建议。

参考资源

本节小结

在本节中,我们学习了:

第一个是添加 Seata 依赖。 添加 Seata 相关依赖。

第二个是配置文件配置。 配置 Seata 连接信息。

第三个是数据源代理配置。 配置数据源。

第四个是全局事务注解。 使用 @GlobalTransactional 注解。

第五个是事务测试。 测试分布式事务。

这就是 AT 模式实战。通过实战,我们掌握了如何使用 Seata AT 模式。

在下一节,我们将学习 TCC 模式实现。