【SpringCloud Alibaba】Seata实战:从零构建分布式事务订单系统 1. 为什么需要分布式事务想象一个电商下单场景用户支付成功后系统需要同时完成三个操作——创建订单、扣减库存、扣减账户余额。这三个操作分别由三个独立的微服务处理每个服务都有自己的数据库。如果创建订单成功但扣减库存失败就会出现订单已生成但库存未减少的数据不一致问题。这就是典型的分布式事务问题。在单体应用中我们可以用数据库的本地事务ACID特性保证一致性。但在微服务架构下由于服务拆分和数据源独立传统的本地事务就失效了。我去年参与的一个跨境电商项目就遇到过这个问题。当时用户投诉说支付成功后订单消失了排查发现是库存服务超时导致整个流程回滚。这就是没有处理好分布式事务的典型案例。2. Seata核心原理Seata的架构设计非常精妙它通过一ID三组件模型解决分布式事务问题2.1 核心组件TC (Transaction Coordinator)事务协调器相当于分布式事务的大脑。维护全局事务状态协调分支事务的提交或回滚。TM (Transaction Manager)事务管理器相当于发起者。定义全局事务边界决定何时开始/提交/回滚全局事务。RM (Resource Manager)资源管理器相当于执行者。管理分支事务资源向TC注册分支事务并报告状态。2.2 工作流程TM向TC申请开启全局事务生成全局唯一的XIDXID通过服务调用链传递通常放在请求头中RM向TC注册分支事务将其纳入XID对应的全局事务TM根据业务执行情况向TC发起全局提交或回滚决议TC调度所有分支事务完成最终操作提示XID的传递是Seata实现分布式事务的关键确保所有服务都能关联到同一个全局事务3. 环境搭建与配置3.1 Seata Server安装首先下载Seata Server当前稳定版1.8.0wget https://github.com/seata/seata/releases/download/v1.8.0/seata-server-1.8.0.tar.gz tar -zxvf seata-server-1.8.0.tar.gz3.2 数据库准备创建seata专用数据库并导入表结构CREATE DATABASE seata; USE seata; -- 执行seata-server解压目录下script/server/db/mysql.sql在业务库中创建undo_log表每个业务库都需要CREATE TABLE undo_log ( branch_id bigint NOT NULL, xid varchar(100) NOT NULL, context varchar(128) NOT NULL, rollback_info longblob NOT NULL, log_status int NOT NULL, log_created datetime NOT NULL, log_modified datetime NOT NULL, UNIQUE KEY ux_undo_log (xid,branch_id) ) ENGINEInnoDB DEFAULT CHARSETutf8;3.3 配置文件调整修改conf/application.yml关键配置seata: store: mode: db db: datasource: druid db-type: mysql url: jdbc:mysql://127.0.0.1:3306/seata?useSSLfalse user: root password: 123456 config: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: group: SEATA_GROUP registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 namespace: group: SEATA_GROUP4. 订单系统实战开发4.1 项目结构seata-demo ├── order-service # 订单服务 ├── account-service # 账户服务 └── inventory-service # 库存服务4.2 公共依赖配置每个服务的pom.xml都需要添加dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-seata/artifactId exclusions exclusion groupIdio.seata/groupId artifactIdseata-spring-boot-starter/artifactId /exclusion /exclusions /dependency dependency groupIdio.seata/groupId artifactIdseata-spring-boot-starter/artifactId version1.8.0/version /dependency4.3 数据源代理配置这是很多开发者容易忽略的关键点必须配置数据源代理Configuration public class DataSourceConfig { Bean ConfigurationProperties(prefix spring.datasource) public DataSource druidDataSource() { return new DruidDataSource(); } Bean public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } Bean public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSourceProxy); factoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return factoryBean.getObject(); } }4.4 业务实现订单服务核心代码Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Autowired private AccountFeignClient accountFeignClient; Autowired private InventoryFeignClient inventoryFeignClient; Override GlobalTransactional public void createOrder(OrderDTO orderDTO) { // 1. 创建订单 Order order convertToOrder(orderDTO); orderMapper.insert(order); // 2. 扣减库存 inventoryFeignClient.deduct(orderDTO.getProductId(), orderDTO.getCount()); // 3. 扣减余额 accountFeignClient.debit(orderDTO.getUserId(), orderDTO.getAmount()); // 4. 更新订单状态 order.setStatus(1); // 已支付 orderMapper.updateById(order); } }库存服务接口FeignClient(name inventory-service) public interface InventoryFeignClient { PostMapping(/inventory/deduct) void deduct(RequestParam(productId) Long productId, RequestParam(count) Integer count); }5. 测试与验证5.1 正常流程测试准备测试数据商品ID1库存100用户ID1余额1000调用下单接口POST /order/create { userId: 1, productId: 1, count: 2, amount: 200 }验证结果订单表新增一条状态为已支付的记录库存表商品1的库存变为98账户表用户1的余额变为8005.2 异常流程测试修改账户服务模拟余额不足异常Service public class AccountServiceImpl implements AccountService { Override public void debit(Long userId, BigDecimal amount) { if(amount.compareTo(new BigDecimal(1000)) 0) { throw new RuntimeException(余额不足); } accountMapper.debit(userId, amount); } }再次调用下单接口金额设为1200观察订单表没有新增记录库存表数量未变化账户表余额未变化控制台日志显示全局事务回滚6. 生产环境注意事项性能优化调整seata-server的线程池参数使用Redis替代数据库存储事务日志store.moderedis合理设置全局事务超时时间默认60秒高可用部署TC服务集群化部署注册中心使用Nacos集群数据库主从配置常见问题排查检查XID是否正常传递日志级别设为debug确认所有RM都正确注册到TC验证undo_log表是否正常生成和清理我在实际项目中遇到过最棘手的问题是网络分区导致TC与RM失联后来通过优化心跳检测机制和超时设置解决了这个问题。建议在重要业务中添加事务状态监控便于及时发现和处理异常。