
1. 项目概述为什么我们需要Seata这样的分布式事务框架在单体应用时代我们处理数据一致性通常依赖数据库自带的事务能力。一个Transactional注解就能保证一组数据库操作要么全部成功要么全部失败回滚这被称为本地事务。它的核心是ACID特性由数据库本身在单数据源环境下强力保障。然而当业务发展到一定规模微服务架构成为主流选择时事情就变得复杂了。订单服务、库存服务、账户服务各自独立部署拥有独立的数据库。一个“创建订单并扣减库存”的业务就需要跨多个服务、多个数据库进行操作。这时传统的本地事务就完全失效了因为你无法让不同数据库上的操作参与到一个数据库事务中。这就是分布式事务问题它本质上是如何在分布式环境下保证跨多个服务、多个数据源的数据操作具有一致性。分布式事务的解决方案有很多比如基于XA协议的两阶段提交2PC、TCCTry-Confirm-Cancel、基于消息队列的最终一致性方案如最大努力通知、以及Saga长事务模式等。每种方案都有其适用的场景和优缺点。例如2PC强一致但性能差、有同步阻塞问题TCC性能好但业务侵入性强需要开发三个接口消息队列方案最终一致实现相对简单但链路长有数据延迟。对于大多数业务开发者而言从零开始实现一套健壮、高可用的分布式事务方案成本极高且容易踩坑。正是在这种背景下像Seata这样的分布式事务中间件应运而生。它封装了多种分布式事务模式提供了一套统一的API和配置让开发者能够以较低的成本在微服务体系中引入可靠的事务保障。简单来说Seata的目标是让分布式事务的使用像本地事务一样简单。2. Seata核心架构与事务模式深度解析Seata的架构设计清晰地划分了三个核心角色事务协调者TC、事务管理器TM和资源管理器RM。这种设计借鉴了经典的分布式事务处理模型并做了适应微服务场景的优化。TCTransaction Coordinator这是Seata的服务端需要独立部署。它是整个分布式事务的“大脑”和“调度中心”。所有全局事务的开启、提交、回滚指令都由TC发出。它还负责维护全局事务和分支事务的状态。TC的高可用至关重要生产环境通常需要集群部署。TMTransaction Manager事务的发起方。在微服务中通常就是那个开启了全局事务的服务。TM负责定义全局事务的边界即告诉TC“我要开始一个全局事务了”GlobalTransactional并在业务逻辑执行完成后向TC发起全局提交或回滚的决议。RMResource Manager事务的参与方。每一个涉及数据库操作的微服务都是一个RM。RM负责管理分支事务向TC注册分支事务、报告分支事务状态并接收TC的指令来执行本地事务的提交或回滚。RM会与业务应用部署在一起通过拦截SQL生成undo log回滚日志来实现反向补偿。Seata支持四种主要的事务模式以适应不同的业务场景2.1 AT模式默认模式自动补偿的魔法AT模式是Seata的招牌也是默认模式。它对业务代码的侵入性极低开发者几乎像使用本地事务一样使用它。其核心原理分为两个阶段第一阶段业务SQL执行时RM会拦截SQL解析其语义生成一份“前置镜像”Before Image和“后置镜像”After Image并将这些数据作为undo log与业务数据在同一个本地事务中提交到数据库。然后RM向TC报告分支状态。此时业务数据的本地锁会被释放这极大地提升了并发性能是AT模式相比传统2PC的关键优化。第二阶段根据TC的决议进行全局提交或回滚。提交TC异步通知各RM删除对应的undo log即可因为一阶段已经提交了数据二阶段提交非常快速。回滚TC通知各RM进行回滚。RM收到指令后根据一阶段保存的undo log生成一条反向的补偿SQL例如将update操作回滚为原来的数据并执行它。执行前Seata会通过全局锁存储在TC或独立的如Redis中校验数据是否被其他全局事务修改防止脏回滚。注意AT模式强依赖数据库的undo log能力MySQL的binlog格式必须是ROW且默认只支持DML操作INSERT, UPDATE, DELETE。对于SELECT FOR UPDATE这类操作需要通过GlobalLock注解来处理。2.2 TCC模式柔性事务的经典实现TCC模式将业务逻辑显式地拆分为三个操作Try、Confirm、Cancel。它属于补偿型事务最终一致性较强性能好适用于对一致性要求高、且业务逻辑可以明确拆分的场景如资金账户、库存等。Try阶段尝试执行完成所有业务的检查并预留必要的业务资源。例如扣减库存的Try操作不是真实扣减而是将库存从一个“可用”状态冻结到一个“冻结”状态。Confirm阶段确认执行。真正执行业务使用Try阶段预留的资源。此阶段需保证幂等性。如上例将“冻结”的库存彻底扣减掉。Cancel阶段取消执行。释放Try阶段预留的资源。同样需要保证幂等性。如上例将“冻结”的库存释放回“可用”状态。TCC模式对业务代码侵入性强需要开发者手动编写三个接口的逻辑但正因为如此它不依赖于数据库事务可以适用于更广泛的资源类型如Redis、MQ等并且通过资源预留避免了二阶段提交时的长期资源锁定。2.3 Saga模式长流程事务的解决方案Saga模式适用于业务流程长、参与者多的场景例如一个订单流程可能涉及几十个服务。它将一个分布式事务拆分为一系列本地事务每个本地事务都有对应的补偿操作。Saga的执行有两种协调方式编排Choreography每个服务执行完后发布事件触发下一个服务执行补偿逻辑也由服务自己监听事件触发。事件链复杂不易维护。编制Orchestration由一个中央协调器Seata的TC可以充当来按预定流程依次调用参与者并在失败时调用对应的补偿操作。Seata的Saga模式通常指这种需要开发者用状态机如JSON定义流程来描述业务逻辑和补偿逻辑。Saga模式是最终一致性的且一阶段就提交本地事务不存在锁资源吞吐量高。但缺点是补偿动作不一定能100%成功可能需要人工介入且由于事务周期长读数据可能不一致脏读。2.4 XA模式传统标准的回归XA模式是分布式事务的工业标准基于两阶段提交协议。Seata的XA模式实现了这个标准。在第一阶段RM执行SQL但不提交等待TC指令第二阶段TC根据所有RM的反馈决定全局提交或回滚。XA模式的优势是强一致业务侵入性低类似AT。但其缺点是资源锁定时间长在整个事务周期内相关数据行都被锁定对高并发性能影响大且依赖数据库对XA协议的支持。模式选型速查表特性模式一致性性能业务侵入性适用场景AT强一致读未提交隔离级别高一阶段释放本地锁低近乎零侵入绝大部分基于关系型数据库的CRUD场景TCC最终一致强于最终高无长期锁高需实现3个接口对一致性要求高、可明确拆分的核心业务资金、库存Saga最终一致高无锁中需定义状态机业务流程长、参与者多、可接受补偿的场景XA强一致低长锁等待低需要强一致、且并发压力不大的传统银行、金融内部系统3. 从零搭建Spring Cloud Nacos Seata的完整实操理论讲得再多不如动手搭一遍。我们以一个经典的“下单扣库存”场景为例搭建一个包含订单服务order-service和库存服务stock-service的微服务Demo使用Spring Cloud Alibaba生态注册中心用Nacos配置中心也用NacosSeata使用AT模式。3.1 环境与组件准备首先确保你的开发环境已安装JDK 8(推荐JDK 11或17)Maven 3.6MySQL 5.7(Seata AT模式需要)Nacos Server 2.x从官网下载单机模式启动即可 (sh startup.sh -m standalone)。接下来我们需要部署Seata Server (TC)。下载与启动Seata Server 从Seata官网的Release页面下载最新版本的Server包如seata-server-1.7.1.tar.gz。解压后关键目录是conf和scripts。配置Seata Server AT模式需要存储全局事务会话、全局锁等信息。我们使用MySQL作为Seata Server的存储库。在MySQL中创建一个数据库例如seata。执行conf目录下的数据库脚本mysql.sql创建所需的表global_table, branch_table, lock_table等。修改conf/application.yml文件。主要修改存储模式和注册中心配置。seata: config: # 使用nacos作为配置中心 type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: group: SEATA_GROUP username: password: ># Linux/Mac sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP # Windows nacos-config.bat -h 127.0.0.1 -p 8848 -g SEATA_GROUP执行成功后在Nacos控制台的“配置管理”中可以看到Group为SEATA_GROUP下多了一堆以seata.server.开头的配置项。启动Seata Server 进入bin目录执行启动脚本。# Linux/Mac sh seata-server.sh -h 0.0.0.0 -p 8091 # Windows seata-server.bat -h 0.0.0.0 -p 8091-p指定TC服务的端口默认8091。启动后可以在Nacos的服务列表里看到名为seata-server的服务。3.2 业务数据库与Undo Log表准备为订单服务和库存服务分别创建数据库order_db和stock_db。 在每个业务数据库中除了业务表order_tbl,stock_tbl必须创建Seata AT模式所需的undo_log表。建表SQL在Seata Server包的script/client/at/db/mysql.sql中。这张表用于存储RM生成的前后镜像数据是回滚的关键。3.3 微服务应用搭建与集成我们创建两个Spring Boot应用order-service和stock-service。1. 引入依赖 在两个服务的pom.xml中引入关键依赖。dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependency !-- Seata Starter 版本与Seata Server保持一致 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-seata/artifactId version2021.0.5.0/version !-- 版本需与Spring Cloud Alibaba版本对应 -- /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId /dependency2. 配置文件(application.yml) 以订单服务为例库存服务类似。server: port: 8081 spring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: public config: server-addr: 127.0.0.1:8848 file-extension: yaml namespace: public group: DEFAULT_GROUP datasource: url: jdbc:mysql://localhost:3306/order_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver # Seata 配置 seata: enabled: true application-id: ${spring.application.name} # 事务分组需与Seata Server的service.vgroup-mapping配置对应在Nacos配置中 tx-service-group: my_test_tx_group registry: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: group: SEATA_GROUP config: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: group: SEATA_GROUP service: # 禁用Seata自带的DataSourceProxy自动配置如果使用SpringBoot 2.x Seata 1.5通常需要禁用 disable-global-transaction: false # 需要确保Nacos中有 dataIdservice.vgroupMapping.my_test_tx_group, groupSEATA_GROUP 的配置其内容为 default指向TC集群。关键点是tx-service-group它需要与推送到Nacos的配置service.vgroupMapping.my_test_tx_group的值这里是default对应这样客户端才知道去找哪个TC集群。3. 业务代码实现订单服务 (OrderService)Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Autowired private RestTemplate restTemplate; // 或使用OpenFeign // 开启全局事务的注解 GlobalTransactional(name create-order, timeoutMills 60000, rollbackFor Exception.class) Override public void createOrder(Order order) { // 1. 创建本地订单分支事务一 orderMapper.insert(order); log.info(订单创建成功订单号{}, order.getOrderNo()); // 2. 远程调用库存服务扣减库存分支事务二 String url http://stock-service/stock/decrease?productId order.getProductId() count order.getCount(); ResponseEntityString response restTemplate.getForEntity(url, String.class); if (!response.getStatusCode().is2xxSuccessful() || !success.equals(response.getBody())) { throw new RuntimeException(扣减库存失败); } log.info(库存扣减成功); // 3. 模拟一个业务异常触发全局回滚 // int i 1 / 0; } }库存服务 (StockService)Service public class StockServiceImpl implements StockService { Autowired private StockMapper stockMapper; // 这里不需要 GlobalTransactional但需要参与事务 Override public void decrease(Long productId, Integer count) { // 查询当前库存 Stock stock stockMapper.selectById(productId); if (stock.getCount() count) { throw new RuntimeException(库存不足); } // 扣减库存 stock.setCount(stock.getCount() - count); stockMapper.updateById(stock); log.info(库存扣减商品ID{} 扣减数量{}, productId, count); } }GlobalTransactional注解只需加在全局事务的发起方TM的方法上。参与方RM的方法会被Seata的数据源代理自动拦截加入到全局事务中。4. 数据源代理配置 这是集成中最容易出错的一环。Seata需要通过DataSourceProxy来代理业务数据源以拦截SQL生成undo log。在Spring Boot中需要手动配置。创建一个配置类Configuration public class DataSourceProxyConfig { Bean ConfigurationProperties(prefix spring.datasource) public DataSource dataSource() { return new DruidDataSource(); // 或 HikariDataSource } Primary // 务必设置为主数据源 Bean public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } // 如果使用MyBatis-Plus需要将SqlSessionFactory的数据源设置为代理后的数据源 Bean public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactoryBean new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); // ... 其他配置如mapperLocation return sqlSessionFactoryBean.getObject(); } }确保DataSourceProxy是Primary的这样所有数据库操作都会经过它。3.4 测试与验证依次启动Nacos、Seata Server、库存服务、订单服务。调用订单服务的创建接口。正常流程观察数据库订单表和库存表的数据都成功更新。查看Seata Server控制台默认http://localhost:7091或日志可以看到全局事务和分支事务的状态为“已提交”。异常回滚流程将订单服务createOrder方法中模拟异常的注释打开 (int i 1 / 0;)。再次调用接口。你会发现订单记录没有插入库存也没有扣减。查看数据库的undo_log表可以看到生成的回滚日志并且随后被删除。在TC的控制台可以看到事务状态为“已回滚”。至此一个完整的Spring Cloud Nacos Seata AT模式分布式事务Demo就搭建并验证成功了。你可以清晰地看到在订单服务的方法上添加一个注解就实现了跨服务、跨数据库的事务一致性。4. 生产环境部署与核心配置调优指南Demo环境跑通只是第一步要将Seata应用于生产必须考虑高可用、性能、监控和稳定性。以下是关键点。1. TC Server高可用部署 单点TC是巨大的风险。必须集群化部署。部署多个TC实例在不同机器上启动多个Seata Server使用相同的application.yml配置连接到同一个Nacos集群和数据库。集群配置在application.yml中seata.registry.nacos.cluster属性可以指定集群名多个TC可以属于同一个集群如default。客户端通过vgroupMapping映射到集群名Nacos会进行负载均衡。数据库高可用TC的后端存储数据库如MySQL也需要主从或集群部署避免单点故障。2. 存储模式选择与优化生产推荐db模式文件模式file仅用于测试。db模式使用MySQL等关系数据库稳定可靠。可以考虑对global_table,branch_table,lock_table进行分库分表设计如果事务量极大。Redis存储全局锁可选但推荐AT模式的全局锁默认存储在TC的数据库里。在高并发场景下频繁的锁检查可能对TC数据库造成压力。Seata支持将全局锁存储到Redis中提升性能。需要在Seata Server的配置中store.redis.*和客户端的配置中启用Redis存储。3. 关键客户端参数调优 在客户端的seata.conf或Nacos中的配置里有一些关键参数client.rm.report.retry.countRM上报分支事务状态的重试次数网络不稳定时可适当增加。client.rm.async.commit.buffer.limit二阶段异步提交的缓冲区大小影响提交性能。client.rm.lock.retry.internal和client.rm.lock.retry.times获取全局锁的重试间隔和次数在热点数据竞争激烈时可适当调整。transport.thread-factory.boss-thread-size和worker-thread-sizeNetty通信线程池大小根据机器核数和并发数调整。4. 与Sentinel等流量治理组件整合 在微服务架构中Sentinel负责流量控制、熔断降级。需要处理好Sentinel熔断与Seata事务回滚的关系。例如当远程调用被Sentinel熔断时应抛出异常触发GlobalTransactional回滚。通常需要自定义BlockExceptionHandler将流控异常转换为业务异常。5. 监控与运维Seata Server监控暴露JMX或使用Micrometer集成将指标如全局事务数、成功率、耗时接入Prometheus Grafana。业务日志确保Seata客户端和服务端的日志级别合理INFO或WARN并接入ELK等日志系统。通过seata.tx-group和xid全局事务ID可以串联整个分布式事务链路的日志这对排查问题至关重要。控制台Seata Server自带控制台可以查看事务状态、锁信息等是运维的重要工具。5. 实战避坑与疑难问题排查实录在实际使用Seata的过程中你会遇到各种各样的问题。下面是我和团队踩过的一些坑以及解决方案。5.1 数据源代理失效问题现象添加了GlobalTransactional注解但事务不生效不回滚。排查检查数据源代理这是最常见的原因。确保你的配置类中DataSourceProxy的Bean被标记为Primary。在Spring Boot 2.x Seata 1.5版本中如果使用了spring-boot-starter-jdbc或mybatis-spring-boot-starter它们可能会自动创建数据源。你需要排除其自动配置或者确保你的DataSourceProxyBean优先级最高。SpringBootApplication(exclude {DataSourceAutoConfiguration.class}) // 排除自动配置检查依赖冲突检查项目中是否有多个数据源相关的依赖或配置导致代理数据源未被正确注入到SqlSessionFactory或JdbcTemplate中。检查TC连接查看客户端日志确认RM和TM是否能成功连接到TC。日志中应有“register TM success”和“register RM success”的信息。5.2 全局锁冲突与脏写现象在高并发更新同一条记录时出现大量BranchTransactionException或超时提示“Global lock conflict”。原因AT模式通过全局锁来保证隔离性。当两个全局事务同时更新同一行数据时后发起的事务会尝试获取全局锁如果锁被前一个事务持有则会等待或重试超时则失败。解决方案优化业务逻辑尽量避免或减少对同一热点数据的高并发更新。例如库存扣减可以考虑使用“库存预扣”类似TCC的Try阶段或“批量合并”的方式。调整锁参数适当增加client.rm.lock.retry.times和client.rm.lock.retry.internal给事务更多获取锁的机会。使用Redis存储锁将全局锁存储从数据库切换到Redis可以极大提升锁操作的性能。评估隔离级别AT模式默认是读未提交隔离级别。如果业务能接受可以考虑使用GlobalLockSELECT ... FOR UPDATE来实现更灵活的锁控制或者直接使用TCC/Saga模式。5.3 Undo Log表相关问题现象回滚失败日志报错“undo log not found”或“undo log insert failed”。排查表结构是否正确确认每个业务数据库中都创建了正确的undo_log表字段类型和长度与Seata版本匹配。字符集问题undo_log表的字符集建议与业务表一致使用utf8mb4避免存储镜像数据时出现乱码。镜像数据过大如果更新的字段包含超长文本如TEXT, BLOB生成的undo log可能会非常大导致插入失败。需要评估业务或考虑使用TCC模式避免大字段的镜像存储。清理机制Seata会异步清理已提交事务的undo log。如果清理失败或积压可能导致表空间增长。需要监控undo_log表大小。5.4 网络超时与重试机制现象事务悬挂Hanging长时间不结束。排查TC与RM/TM网络不稳定增加transport.rpc.rm-request-timeout和transport.rpc.tm-request-timeout的值。二阶段回调失败TC通知RM提交或回滚时RM可能因为网络或自身原因没有响应。TC有重试机制但需要确认RM的client.rm.report.retry.count配置。同时RM需要保证二阶段操作删除undo log或执行补偿SQL的幂等性。事务上下文传递在微服务调用链中Seata的XID全局事务ID需要通过HTTP HeaderSeata-Xid或Feign拦截器进行传递。如果传递失败下游服务就无法正确参与到全局事务中。务必检查调用链中是否有自定义的HTTP客户端或过滤器破坏了上下文传递。5.5 与分库分表中间件整合现象使用了ShardingSphere、MyCat等分库分表中间件Seata事务异常。解决方案这是一个高级话题。原则是**“先分片后代理”**。即先让ShardingSphere对SQL进行解析和路由得到最终要执行的实际数据源和SQL然后再让Seata的DataSourceProxy去代理这个实际的数据源。通常的集成方式是不使用Seata的自动数据源代理。手动创建ShardingDataSource。遍历ShardingDataSource获取的所有真实DataSource为每个都创建一个DataSourceProxy。将这些DataSourceProxy包装成一个新的DataSource并设置为PrimaryBean。 这样Seata就能正确拦截到分片后的真实SQL了。具体实现需要参考ShardingSphere和Seata的官方集成示例。分布式事务没有银弹Seata提供了一个优秀的框架来降低实现复杂度但并不能消除分布式事务本身的理论复杂度。理解其原理根据业务场景选择合适的模式进行充分的测试和监控是保证系统数据最终一致性的关键。在微服务的世界里我们往往需要在一致性、可用性和性能之间做出权衡而Seata给了我们更多选择的工具和可能。