【若依微服务实战】Seata AT模式集成:从零到一构建分布式事务解决方案

张开发
2026/4/4 13:48:52 15 分钟阅读
【若依微服务实战】Seata AT模式集成:从零到一构建分布式事务解决方案
1. 为什么需要分布式事务在微服务架构中随着业务规模的扩大我们常常需要对数据库进行分库分表。比如在若依微服务项目中用户表和部门表可能被拆分到不同的数据库实例上。这时候就面临一个棘手的问题当我们需要同时更新用户信息和部门信息时如何保证这两个操作要么全部成功要么全部失败想象一下这样的场景用户修改了自己的部门信息系统需要先更新用户表再调用部门服务更新部门表。如果在更新部门表时发生异常而用户表已经更新成功就会导致数据不一致。这就是典型的分布式事务问题。我去年在一个电商项目中就遇到过类似情况。当时订单服务和库存服务是分开的用户下单后需要同时扣减库存和创建订单。有几次系统异常导致库存扣减成功但订单创建失败结果用户付了钱却没生成订单差点引发客诉。后来我们引入了Seata的AT模式才彻底解决了这个问题。2. Seata AT模式工作原理2.1 核心组件解析Seata AT模式就像个事务管家它由三个关键角色组成TC事务协调器相当于管家本人负责协调全局事务。它会记录哪些服务参与了事务以及事务的最终状态。TM事务管理器相当于下达指令的主人。我们在代码中使用GlobalTransactional注解的方法就是TM它告诉TC我要开始一个全局事务了。RM资源管理器相当于具体干活的仆人。每个微服务中的数据库操作都会被RM监控它会自动生成回滚日志。2.2 两阶段提交的魔法Seata AT模式采用两阶段提交机制这就像网购时的下单-付款流程第一阶段当服务A调用服务B时Seata会在业务数据库创建undo_log记录相当于购物车执行实际SQL操作相当于选好商品向TC注册分支事务相当于加入购物车第二阶段如果所有服务都执行成功TC会通知所有RM提交事务相当于付款成功如果任一服务失败TC会通知所有RM根据undo_log回滚相当于取消订单我特别喜欢这个设计因为它对业务代码侵入性很小。你只需要加个注解Seata就会自动帮你处理复杂的分布式事务逻辑。3. 搭建Seata服务端3.1 环境准备在开始前确保你已经准备好JDK 1.8MySQL 5.7Nacos服务注册中心若依默认使用Nacos若依微服务项目版本建议4.7.0这里有个小技巧我建议在Linux服务器上部署Seata Server因为生产环境通常都是Linux。但开发时可以在本地先测试。3.2 详细安装步骤下载和解压wget https://github.com/seata/seata/releases/download/v1.7.1/seata-server-1.7.1.tar.gz tar -xzf seata-server-1.7.1.tar.gz cd seata配置注册中心 修改conf/registry.conf使用Nacos作为注册中心registry { type nacos nacos { serverAddr localhost:8848 namespace cluster default } }配置事务日志存储 修改conf/file.conf使用MySQL存储事务日志store { mode db db { datasource druid dbType mysql driverClassName com.mysql.cj.jdbc.Driver url jdbc:mysql://localhost:3306/seata?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai user root password your_password } }初始化数据库 创建seata数据库并执行初始化脚本CREATE DATABASE seata; USE seata; -- 执行脚本 conf/db_store.sql启动Seata Serversh bin/seata-server.sh -p 8091 -h 127.0.0.1 -m db启动后你可以在Nacos控制台看到seata-server的服务实例。如果看不到检查下Nacos地址是否配置正确。4. 集成Seata到若依微服务4.1 添加依赖配置首先在每个需要参与分布式事务的微服务模块的pom.xml中添加依赖dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-seata/artifactId version2021.0.1.0/version exclusions exclusion groupIdio.seata/groupId artifactIdseata-spring-boot-starter/artifactId /exclusion /exclusions /dependency dependency groupIdio.seata/groupId artifactIdseata-spring-boot-starter/artifactId version1.7.1/version /dependency注意这里排除了starter中的旧版本然后显式指定了1.7.1版本。这样可以避免版本冲突问题。4.2 配置文件详解在每个微服务的application.yml中添加配置spring: cloud: alibaba: seata: tx-service-group: ruoyi_tx_group # 事务组名称建议按项目命名 seata: enabled: true registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: group: SEATA_GROUP config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: service: vgroup-mapping: ruoyi_tx_group: default # 事务组映射到Seata集群 enable-auto-data-source-proxy: true # 自动代理数据源这里有几个关键点需要注意tx-service-group建议按项目命名避免与其他项目冲突vgroup-mapping中的值要与Seata Server的集群名称一致enable-auto-data-source-proxy必须为true4.3 数据源代理配置在若依框架中我们需要手动配置数据源代理。创建一个配置类package com.ruoyi.framework.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; 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(prefix spring.datasource.druid) public DataSource druidDataSource() { return DruidDataSourceBuilder.create().build(); } Bean Primary public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } }这个配置做了两件事创建原始的Druid数据源用DataSourceProxy包装原始数据源注意一定要加Primary注解确保Spring使用代理后的数据源。4.4 创建undo_log表在每个业务数据库中都需要创建undo_log表CREATE TABLE undo_log ( id bigint(20) NOT NULL AUTO_INCREMENT, branch_id bigint(20) NOT NULL, xid varchar(100) NOT NULL, context varchar(128) NOT NULL, rollback_info longblob NOT NULL, log_status int(11) NOT NULL, log_created datetime NOT NULL, log_modified datetime NOT NULL, ext varchar(100) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY ux_undo_log (xid,branch_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个表用于存储事务回滚需要的信息。Seata在执行SQL前会先记录修改前的数据到这里如果需要回滚就根据这些数据恢复。5. 实战用户与部门联调5.1 全局事务示例现在我们来实现一个经典场景更新用户信息的同时更新部门信息。假设用户服务和部门服务是分开的。在用户服务的UserServiceImpl中添加Service public class SysUserServiceImpl implements ISysUserService { Autowired private SysUserMapper userMapper; Autowired private RemoteDeptService remoteDeptService; Override GlobalTransactional(timeoutMills 300000, name updateUserAndDept) Transactional public int updateUserAndDept(SysUser user) { // 更新本地用户表 int result userMapper.updateUser(user); // 远程调用更新部门信息 remoteDeptService.updateDeptById(user.getDeptId(), user.getDeptName()); // 测试回滚时可以取消下面注释 // if (System.currentTimeMillis() 0) { // throw new RuntimeException(测试分布式事务回滚); // } return result; } }关键点说明GlobalTransactional是Seata的注解用于标识这是一个全局事务Transactional是Spring的事务注解仍然需要保留timeoutMills设置全局事务超时时间单位毫秒方法内先执行本地事务再执行远程调用5.2 Feign客户端配置创建调用部门服务的Feign客户端FeignClient(contextId remoteDeptService, value ServiceNameConstants.SYSTEM_SERVICE) public interface RemoteDeptService { PutMapping(/dept/update) RBoolean updateDeptById(RequestParam(deptId) Long deptId, RequestParam(deptName) String deptName, RequestHeader(SecurityConstants.FROM_SOURCE) String source); }Feign调用默认已经集成了Seata的XID传播所以不需要额外配置。5.3 测试与验证测试时可以按照以下步骤正常流程测试确保用户和部门信息都能更新成功异常流程测试取消注释中的异常代码验证是否都能回滚查看Seata控制台http://localhost:7091 可以查看事务执行情况检查undo_log表了解Seata如何记录回滚信息我在测试时发现一个常见问题如果忘记在某个服务中配置数据源代理会导致那个服务的事务无法回滚。所以一定要检查所有参与事务的服务配置是否正确。6. 生产环境优化建议6.1 性能调优在高并发场景下Seata可能会成为性能瓶颈。以下是一些优化建议调整线程池参数 在file.conf中调整transport { thread-factory { boss-thread-prefix NettyBoss worker-thread-prefix NettyServerNIOWorker server-executor-thread-prefix NettyServerBizHandler share-boss-worker false client-selector-thread-prefix NettyClientSelector client-selector-thread-size 1 client-worker-thread-prefix NettyClientWorkerThread worker-thread-size default # 根据CPU核心数调整 boss-thread-size 1 } }使用Redis存储模式 对于高并发场景可以使用Redis代替数据库存储事务日志store { mode redis redis { host 127.0.0.1 port 6379 password database 0 minConn 1 maxConn 10 queryLimit 100 } }6.2 高可用部署生产环境建议部署Seata集群部署多个Seata Server实例使用Nacos等注册中心做服务发现为每个实例配置相同的store.mode使用负载均衡访问Seata集群6.3 监控与告警建议集成Prometheus监控Seata运行状态在Seata Server配置中启用metricsmetrics { enabled true registry-type compact exporter-list prometheus exporter-prometheus-port 9898 }配置Grafana仪表盘监控关键指标如全局事务提交/回滚率平均处理时间活跃事务数7. 常见问题排查7.1 事务不生效排查步骤检查Seata Server是否正常运行日志是否有错误确认所有微服务都能连接到Seata Server检查每个微服务的数据源是否被正确代理确认undo_log表在每个业务库中都存在检查GlobalTransactional注解是否添加在入口方法上查看Seata控制台的事务日志7.2 典型错误解决方案问题1Could not register branch into seata原因网络问题或Seata Server不可用解决检查Seata Server状态检查微服务与Seata Server的网络连接增加Seata客户端超时时间seata: transport: rpc: rm: timeout: 30000 tm: timeout: 30000问题2UndoLog table not found原因业务数据库缺少undo_log表解决在所有业务库中创建undo_log表问题3Global transaction timeout原因事务执行时间超过配置的超时时间解决调整超时时间GlobalTransactional(timeoutMills 600000) // 10分钟8. 进阶使用技巧8.1 与本地事务结合有时候我们需要在全局事务中包含本地事务。这时需要注意本地事务要用Transactional(propagation Propagation.REQUIRED)不要在本地事务中捕获异常后不抛出本地事务的隔离级别要与全局事务协调8.2 大事务优化对于执行时间较长的大事务拆分为多个小事务适当增加超时时间考虑使用SAGA模式避免在事务中进行远程调用或IO操作8.3 多数据源配置若依项目有时会配置多数据源。这时需要为每个数据源创建代理确保所有数据源都参与全局事务注意事务传播行为配置示例Bean Primary public DataSourceProxy dataSourceProxy1(Qualifier(dataSource1) DataSource dataSource) { return new DataSourceProxy(dataSource); } Bean public DataSourceProxy dataSourceProxy2(Qualifier(dataSource2) DataSource dataSource) { return new DataSourceProxy(dataSource); }9. 替代方案对比虽然Seata AT模式很好用但也不是银弹。以下是几种常见分布式事务方案的对比方案优点缺点适用场景Seata AT对代码侵入小使用简单性能开销较大一般业务场景Seata TCC性能好无锁需要手动实现try/confirm/cancel高性能要求场景本地消息表简单可靠需要额外维护消息表最终一致性场景SAGA适合长事务需要设计补偿机制跨系统长流程在若依项目中如果业务不是很复杂AT模式通常是最佳选择。但对于性能要求极高的场景可以考虑TCC模式。10. 最佳实践总结经过多个项目的实践我总结了以下Seata集成的最佳实践版本一致性确保所有微服务使用的Seata客户端版本与Server一致命名规范事务组名称使用项目名_tx_group格式避免冲突超时设置根据业务特点设置合理的全局事务超时时间监控完备建立完善的监控体系及时发现和处理问题压测验证上线前进行充分的压力测试评估Seata对系统性能的影响回滚测试定期测试异常场景下的回滚功能是否正常文档维护记录各服务的分布式事务参与情况方便后续维护最后提醒一点分布式事务虽然能解决数据一致性问题但也会带来性能开销。在设计系统时应该优先考虑通过业务设计避免分布式事务比如使用最终一致性方案。只有在确实需要强一致性的场景下才使用Seata这样的分布式事务框架。

更多文章