Seata AT模式详细实例:电商下单场景
1. Seata AT模式核心原理
1.1 核心概念
- AT模式:Automatic Transaction(自动事务),非侵入式的分布式事务解决方案
- TC(Transaction Coordinator):事务协调器,管理全局事务状态
- TM(Transaction Manager):事务管理器,发起和结束全局事务
- RM(Resource Manager):资源管理器,管理分支事务,与TC交互
- 全局事务ID(XID):唯一标识一个全局事务
- Undo Log:回滚日志,用于事务回滚
1.2 工作流程
- 全局事务开启:TM向TC请求开启全局事务,获取XID
- 分支事务注册:RM向TC注册分支事务,关联XID
- SQL执行:RM执行SQL,生成Undo Log并写入数据库
- 分支事务提交:RM向TC报告分支事务状态
- 全局事务提交/回滚:TC根据分支事务状态,决定全局提交或回滚
- 分支事务提交/回滚:TC通知RM执行分支事务的提交或回滚
2. 项目结构设计
2.1 业务场景
电商下单流程:
- 创建订单(订单服务)
- 扣减库存(库存服务)
- 扣减账户余额(账户服务)
2.2 项目结构
seata-at-demo/ # 父项目 ├── order-service/ # 订单服务(8081) ├── inventory-service/ # 库存服务(8082) └── account-service/ # 账户服务(8083)3. 环境准备
3.1 组件版本
| 组件 | 版本 |
|---|---|
| Spring Boot | 2.7.18 |
| Spring Cloud | 2021.0.8 |
| Seata | 1.7.1 |
| MySQL | 8.0+ |
| Nacos | 2.2.3 |
3.2 数据库初始化
3.2.1 创建Seata所需数据库和表
-- 创建seata数据库CREATEDATABASEseata;USEseata;-- 创建undo_log表(Seata AT模式必须)CREATETABLEundo_log(idBIGINTAUTO_INCREMENTPRIMARYKEY,branch_idBIGINTNOTNULL,xidVARCHAR(100)NOTNULL,contextVARCHAR(128)NOTNULL,rollback_infoLONGBLOBNOTNULL,log_statusINTNOTNULL,log_createdDATETIMENOTNULL,log_modifiedDATETIMENOTNULL,UNIQUEKEYux_undo_log(xid,branch_id));3.2.2 业务数据库初始化
order-service数据库:
CREATEDATABASEorder_db;USEorder_db;CREATETABLEorders(idBIGINTAUTO_INCREMENTPRIMARYKEY,user_idBIGINTNOTNULL,product_idBIGINTNOTNULL,countINTNOTNULL,moneyDECIMAL(10,2)NOTNULL,statusINTDEFAULT0COMMENT'0:创建中,1:已创建,2:已取消');CREATETABLEundo_log(-- 同上,每个业务库都需要undo_log表);inventory-service数据库:
CREATEDATABASEinventory_db;USEinventory_db;CREATETABLEinventory(idBIGINTAUTO_INCREMENTPRIMARYKEY,product_idBIGINTNOTNULL,countINTNOTNULLDEFAULT0);INSERTINTOinventory(product_id,count)VALUES(1,100);CREATETABLEundo_log(-- 同上);account-service数据库:
CREATEDATABASEaccount_db;USEaccount_db;CREATETABLEaccount(idBIGINTAUTO_INCREMENTPRIMARYKEY,user_idBIGINTNOTNULL,moneyDECIMAL(10,2)NOTNULLDEFAULT0.00);INSERTINTOaccount(user_id,money)VALUES(1,1000.00);CREATETABLEundo_log(-- 同上);4. 代码实现
4.1 父项目POM.xml
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version></parent><properties><spring-cloud.version>2021.0.8</spring-cloud.version><seata.version>1.7.1</seata.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>${seata.version}</version></dependency></dependencies></dependencyManagement>4.2 订单服务实现
4.2.1 POM.xml
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies>4.2.2 application.yml
server:port:8081spring:application:name:order-servicedatasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=UTCusername:rootpassword:rootcloud:nacos:discovery:server-addr:localhost:8848openfeign:client:config:default:connectTimeout:5000readTimeout:5000jpa:hibernate:ddl-auto:updateshow-sql:trueseata:enabled:truetx-service-group:my_test_tx_groupregistry:type:nacosnacos:application:seata-serverserver-addr:localhost:8848group:SEATA_GROUPconfig:type:nacosnacos:server-addr:localhost:8848group:SEATA_GROUPdata-source-proxy-mode:AT4.2.3 订单实体
@Entity@Table(name="orders")@DatapublicclassOrder{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;privateLonguserId;privateLongproductId;privateIntegercount;privateBigDecimalmoney;privateIntegerstatus=0;}4.2.4 Feign客户端
@FeignClient(name="inventory-service")publicinterfaceInventoryFeignClient{@PostMapping("/inventory/deduct")Stringdeduct(@RequestParamLongproductId,@RequestParamIntegercount);}@FeignClient(name="account-service")publicinterfaceAccountFeignClient{@PostMapping("/account/deduct")Stringdeduct(@RequestParamLonguserId,@RequestParamBigDecimalmoney);}4.2.5 订单服务实现(核心)
@ServicepublicclassOrderService{@AutowiredprivateOrderRepositoryorderRepository;@AutowiredprivateInventoryFeignClientinventoryFeignClient;@AutowiredprivateAccountFeignClientaccountFeignClient;/** * 创建订单,Seata AT模式分布式事务 * @GlobalTransactional:标记为全局事务 */@GlobalTransactional(name="create-order-tx",rollbackFor=Exception.class)publicStringcreateOrder(Orderorder){System.out.println("开始创建订单...");// 1. 创建订单orderRepository.save(order);System.out.println("订单创建成功:"+order.getId());// 2. 扣减库存StringinventoryResult=inventoryFeignClient.deduct(order.getProductId(),order.getCount());System.out.println("库存扣减结果:"+inventoryResult);// 3. 扣减账户余额BigDecimaltotalMoney=order.getMoney().multiply(newBigDecimal(order.getCount()));StringaccountResult=accountFeignClient.deduct(order.getUserId(),totalMoney);System.out.println("账户扣减结果:"+accountResult);// 模拟异常,测试回滚// if (true) throw new RuntimeException("模拟异常,测试回滚");System.out.println("订单创建完成!");return"订单创建成功:"+order.getId();}}4.2.6 订单控制器
@RestController@RequestMapping("/order")publicclassOrderController{@AutowiredprivateOrderServiceorderService;@PostMapping("/create")publicStringcreateOrder(@RequestBodyOrderorder){returnorderService.createOrder(order);}}4.3 库存服务实现
4.3.1 application.yml
server:port:8082spring:application:name:inventory-servicedatasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/inventory_db?useSSL=false&serverTimezone=UTCusername:rootpassword:rootcloud:nacos:discovery:server-addr:localhost:8848jpa:hibernate:ddl-auto:updateshow-sql:trueseata:enabled:truetx-service-group:my_test_tx_group# 其他配置同order-service4.3.2 库存服务实现
@ServicepublicclassInventoryService{@AutowiredprivateInventoryRepositoryinventoryRepository;/** * 扣减库存,分支事务 */@Transactional(rollbackFor=Exception.class)publicStringdeduct(LongproductId,Integercount){System.out.println("开始扣减库存...");// 查询库存Inventoryinventory=inventoryRepository.findByProductId(productId);if(inventory==null){thrownewRuntimeException("库存不存在");}// 检查库存是否充足if(inventory.getCount()<count){thrownewRuntimeException("库存不足");}// 扣减库存inventory.setCount(inventory.getCount()-count);inventoryRepository.save(inventory);System.out.println("库存扣减成功:"+productId);return"库存扣减成功";}}4.4 账户服务实现
4.4.1 application.yml
server:port:8083spring:application:name:account-servicedatasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/account_db?useSSL=false&serverTimezone=UTCusername:rootpassword:rootcloud:nacos:discovery:server-addr:localhost:8848jpa:hibernate:ddl-auto:updateshow-sql:trueseata:enabled:truetx-service-group:my_test_tx_group# 其他配置同order-service4.4.2 账户服务实现
@ServicepublicclassAccountService{@AutowiredprivateAccountRepositoryaccountRepository;/** * 扣减账户余额,分支事务 */@Transactional(rollbackFor=Exception.class)publicStringdeduct(LonguserId,BigDecimalmoney){System.out.println("开始扣减账户余额...");// 查询账户Accountaccount=accountRepository.findByUserId(userId);if(account==null){thrownewRuntimeException("账户不存在");}// 检查余额是否充足if(account.getMoney().compareTo(money)<0){thrownewRuntimeException("余额不足");}// 扣减余额account.setMoney(account.getMoney().subtract(money));accountRepository.save(account);System.out.println("账户余额扣减成功:"+userId);return"账户余额扣减成功";}}5. Seata Server部署
5.1 下载Seata Server
从Seata官网下载Seata Server 1.7.1:https://github.com/seata/seata/releases
5.2 配置Seata Server
5.2.1 registry.conf
registry { type = "nacos" nacos { application = "seata-server" serverAddr = "localhost:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "nacos" password = "nacos" } } config { type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" group = "SEATA_GROUP" username = "nacos" password = "nacos" dataId = "seataServer.properties" } }5.2.2 seataServer.properties
在Nacos中创建配置:
- Data ID: seataServer.properties
- Group: SEATA_GROUP
- 配置内容:
store.mode=db store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.cj.jdbc.Driver store.db.url=jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC store.db.user=root store.db.password=root store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.lockTable=lock_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000
5.3 启动Seata Server
# 解压后进入bin目录./seata-server.sh-h127.0.0.1-p8091-mdb6. 启动Nacos
# 解压后进入bin目录./startup.sh-mstandalone7. 测试Seata AT模式
7.1 启动服务
- 启动Nacos
- 启动Seata Server
- 启动order-service
- 启动inventory-service
- 启动account-service
7.2 测试正常流程
7.2.1 发送请求
curl-XPOST-H"Content-Type: application/json"-d'{"userId":1,"productId":1,"count":10,"money":100.00}'http://localhost:8081/order/create7.2.2 预期结果
- 订单服务:创建订单成功
- 库存服务:库存从100减到90
- 账户服务:账户余额从1000减到0
- 全局事务提交成功
7.3 测试回滚流程
7.3.1 修改订单服务代码
在OrderService.createOrder方法中添加异常:
// 模拟异常,测试回滚if(true)thrownewRuntimeException("模拟异常,测试回滚");7.3.2 发送请求
同7.2.1
7.3.3 预期结果
- 订单服务:订单创建后回滚
- 库存服务:库存从100减到90后回滚到100
- 账户服务:账户余额从1000减到0后回滚到1000
- 全局事务回滚成功
8. Seata AT模式核心机制
8.1 SQL执行与Undo Log生成
8.1.1 执行SQL
UPDATEinventorySETcount=count-10WHEREproduct_id=1;8.1.2 生成Undo Log
Seata会自动拦截SQL,生成Undo Log并写入数据库:
INSERTINTOundo_log(branch_id,xid,context,rollback_info,log_status,log_created,log_modified)VALUES(123456,'xid-123','serializable','{"beforeImage":{"count":100},"afterImage":{"count":90}}',1,NOW(),NOW());8.2 事务回滚机制
8.2.1 回滚流程
- TC通知RM执行回滚
- RM读取Undo Log
- RM执行回滚SQL:
UPDATE inventory SET count = 100 WHERE product_id = 1 AND count = 90; - RM删除Undo Log
8.2.2 回滚条件
- 乐观锁机制:回滚SQL包含当前值条件,确保数据未被其他事务修改
- 幂等性:多次回滚操作结果一致
9. 监控与管理
9.1 Seata控制台
Seata提供了Web控制台,用于监控全局事务状态:
- 访问地址:http://localhost:7091
- 默认用户名/密码:seata/seata
9.2 核心监控指标
- 全局事务数量:当前活跃的全局事务数量
- 分支事务数量:当前活跃的分支事务数量
- 事务成功率:全局事务成功率
- 事务平均耗时:全局事务平均执行时间
- 回滚事务数量:回滚的全局事务数量
10. 最佳实践
10.1 配置优化
- 数据库连接池:增大连接池大小,支持更多并发事务
- Undo Log清理:定期清理过期的Undo Log,避免磁盘空间占用过大
- 超时设置:合理设置全局事务超时时间,避免事务长时间占用资源
10.2 开发注意事项
- @GlobalTransactional:仅在事务发起方添加,分支事务无需添加
- 本地事务:分支事务仍需添加
@Transactional注解 - 幂等性:业务逻辑需考虑幂等性,避免重复执行
- 异常处理:合理处理异常,确保事务能正确回滚
- 避免长事务:全局事务应尽可能短,减少资源占用
10.3 部署建议
- Seata Server高可用:部署多个Seata Server实例,避免单点故障
- 数据库分离:Seata数据库与业务数据库分离,提高性能
- 网络优化:确保Seata Server与业务服务网络通畅,减少网络延迟
11. 常见问题与解决方案
11.1 问题:全局事务未生效
解决方案:
- 检查@GlobalTransactional注解是否添加
- 检查XID是否正确传递(通过Feign拦截器)
- 检查Seata配置是否正确
11.2 问题:事务回滚失败
解决方案:
- 检查Undo Log表是否正确创建
- 检查数据库权限是否足够
- 检查乐观锁条件是否满足(数据是否被其他事务修改)
11.3 问题:性能问题
解决方案:
- 优化SQL,减少执行时间
- 增大Seata Server的线程池大小
- 考虑拆分大事务为多个小事务
12. 总结
Seata AT模式是一种非侵入式的分布式事务解决方案,通过自动生成Undo Log和两阶段提交,实现了分布式事务的自动管理。其核心优势包括:
- 非侵入式:无需修改业务代码,只需添加@GlobalTransactional注解
- 自动回滚:自动生成Undo Log,支持事务回滚
- 高性能:异步提交,减少锁竞争
- 易于集成:与Spring Cloud、Dubbo等框架无缝集成
- 可靠性高:支持事务回滚,确保数据一致性
通过本实例,您可以深入理解Seata AT模式的工作原理和实现细节,并在实际项目中应用Seata AT模式解决分布式事务问题。