12.6、AT模式实战
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:正常提交
测试步骤:
-
启动所有服务:
# 启动 Seata Server cd mall-microservices/docker/seata docker-compose -f docker-compose-nacos.yml up -d # 启动订单服务、库存服务、支付服务 -
创建订单:
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 }' -
验证事务提交:
- 订单创建成功
- 库存扣减成功
- 支付订单创建成功
- 所有操作都在同一个全局事务中
测试场景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; }
测试步骤:
-
创建大额订单(触发异常):
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 }' -
验证事务回滚:
- 订单创建失败(回滚)
- 库存扣减失败(回滚)
- 支付订单创建失败(回滚)
- 所有操作都回滚,数据保持一致
验证分布式事务
查看 Seata 控制台:
- 访问 Seata 控制台(如果有)
- 查看全局事务列表
- 查看事务状态和分支事务
查看数据库:
-
查看 undo_log 表:
SELECT * FROM undo_log ORDER BY log_created DESC LIMIT 10; -
验证数据一致性:
- 订单表:订单应该存在或不存在(取决于事务是否提交)
- 库存表:库存应该扣减或未扣减(取决于事务是否提交)
- 支付表:支付订单应该存在或不存在(取决于事务是否提交)
官方资源
根据 Seata 快速开始指南:
-
AT 模式原理:官方文档详细说明了 AT 模式的工作原理,包括如何通过数据源代理自动管理事务、如何记录 undo_log、如何实现自动回滚等。文档强调,AT 模式对业务代码零侵入,开发者只需使用
@GlobalTransactional注解即可。 -
快速开始:官方文档提供了完整的快速开始指南,包括如何搭建 Seata Server、如何配置数据源代理、如何编写业务代码等。文档提供了详细的步骤说明和代码示例,帮助开发者快速上手。
-
最佳实践:官方文档总结了 AT 模式的最佳实践,包括如何选择合适的事务模式、如何优化性能、如何处理异常等。文档特别强调了 undo_log 表的设计和优化建议。
参考资源:
- Seata AT 模式实战:https://seata.io/docs/user/quickstart/
本节小结
在本节中,我们学习了:
第一个是添加 Seata 依赖。 添加 Seata 相关依赖。
第二个是配置文件配置。 配置 Seata 连接信息。
第三个是数据源代理配置。 配置数据源。
第四个是全局事务注解。 使用 @GlobalTransactional 注解。
第五个是事务测试。 测试分布式事务。
这就是 AT 模式实战。通过实战,我们掌握了如何使用 Seata AT 模式。
在下一节,我们将学习 TCC 模式实现。