在分布式系统中,跨服务调用场景(如订单创建→库存扣减→支付扣钱)会导致数据一致性问题 ------ 若某一步操作失败,已执行的操作无法回滚,会出现「订单创建成功但库存未扣减」「库存扣减成功但支付失败」等数据不一致问题。分布式事务的核心是保证跨服务操作的原子性:要么全部成功,要么全部失败。
Seata 是阿里开源的分布式事务框架,支持 AT、TCC、SAGA 等模式,其中 AT 模式(自动补偿)因「无侵入、易用性高」成为企业级首选。本文聚焦 SpringBoot 集成 Seata AT 模式,从环境搭建、依赖配置、代码实现到避坑指南,全程嵌入可复用代码,帮你快速落地分布式事务,解决跨服务数据一致性问题。
一、核心认知:分布式事务与 Seata 核心原理
1. 分布式事务的核心问题
分布式系统中,每个服务拥有独立数据库,无法通过本地事务保证跨服务操作的原子性,主要面临三大挑战:
- 原子性:跨服务操作要么全部成功,要么全部回滚;
- 一致性:事务执行后,所有服务数据处于一致状态;
- 隔离性:并发事务执行时,互不干扰。
2. Seata AT 模式核心原理(自动补偿)
AT 模式基于「两阶段提交」实现,无需手动编写补偿逻辑,对业务代码无侵入,适合大多数业务场景:
- 一阶段(执行阶段):
- 拦截 SQL,解析并记录数据修改前的快照(undo_log);
- 执行 SQL 并提交本地事务;
- 向 Seata 服务器注册分支事务。
- 二阶段(提交 / 回滚阶段):
- 提交:Seata 协调器通知所有分支事务提交,删除 undo_log;
- 回滚:Seata 协调器通知所有分支事务回滚,通过 undo_log 恢复数据至修改前状态。
3. Seata 核心角色
- Transaction Coordinator(TC):事务协调器(独立服务),负责协调全局事务的提交 / 回滚;
- Transaction Manager(TM):事务管理器(发起事务的服务),负责开启、提交、回滚全局事务;
- Resource Manager(RM):资源管理器(参与事务的服务),负责执行本地事务、注册分支事务、接收回滚 / 提交指令。
二、核心实战一:Seata 环境搭建(Docker 快速部署)
1. 部署 Seata TC 服务器(事务协调器)
bash
运行
# 1. 拉取 Seata 镜像(最新稳定版)
docker pull seataio/seata-server:latest
# 2. 启动 Seata 容器
docker run -d --name seata-server \
-p 8091:8091 \
-e SEATA_IP=127.0.0.1 \ # 本机IP,确保服务能访问
-e SEATA_PORT=8091 \
seataio/seata-server:latest
- 端口 8091:Seata TC 与 TM/RM 通信的端口;
- 生产环境需配置持久化(MySQL),避免重启后事务数据丢失。
2. 数据库准备(每个参与服务的数据库需创建 undo_log 表)
Seata AT 模式依赖 undo_log 表记录数据快照,用于回滚时恢复数据,所有参与分布式事务的服务数据库均需执行此 SQL:
sql
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL COMMENT '分支事务ID',
`xid` varchar(100) NOT NULL COMMENT '全局事务ID',
`context` varchar(128) NOT NULL COMMENT '上下文信息',
`rollback_info` longblob NOT NULL COMMENT '回滚信息',
`log_status` int NOT NULL COMMENT '日志状态:0-未提交,1-已提交',
`log_created` datetime NOT NULL COMMENT '创建时间',
`log_modified` datetime NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Seata 回滚日志表';
三、核心实战二:SpringBoot 集成 Seata(AT 模式)
本次实战模拟「订单服务(order-service)→ 库存服务(stock-service)」跨服务调用场景,通过 Seata 保证订单创建与库存扣减的原子性。
1. 引入依赖(两个服务均需引入)
xml
<!-- Seata 依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Spring Cloud Alibaba Seata 适配(若使用Spring Cloud) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2022.0.0.0-RC2</version>
</dependency>
<!-- MyBatis-Plus 依赖(数据访问层,可选) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
2. 配置文件(application.yml,两个服务均需配置)
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: order-service # 库存服务为 stock-service
# Seata 配置
seata:
enabled: true
application-id: ${spring.application.name} # 应用ID,与服务名一致
tx-service-group: my_test_tx_group # 事务组名称(需与TC配置一致)
registry:
type: file # 注册中心类型(生产环境用nacos/eureka)
config:
type: file # 配置中心类型
service:
vgroup-mapping:
my_test_tx_group: default # 事务组与TC集群映射(默认default)
client:
rm:
report-success-enable: true
table-meta-check-enable: false # 关闭表元数据检查
tm:
commit-retry-count: 3 # 提交重试次数
rollback-retry-count: 3 # 回滚重试次数
3. 数据源代理配置(关键,Seata 需拦截 SQL 生成快照)
Seata AT 模式需对数据源进行代理,才能拦截 SQL 并记录 undo_log,需配置数据源代理类:
java
运行
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class DataSourceProxyConfig {
// 数据源代理:Seata 对原始数据源进行代理
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
// 配置 MyBatis SqlSessionFactory,使用 Seata 代理数据源
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSourceProxy);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/**/*.xml"));
factoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
}
四、核心实战三:分布式事务代码实现(跨服务调用)
1. 库存服务(stock-service):提供库存扣减接口
(1)实体类与 Mapper
java
运行
// 库存实体类
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
@Data
@TableName("t_stock")
public class Stock {
private Long id;
private Long productId; // 商品ID
private Integer num; // 库存数量
}
// 库存 Mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.stock.entity.Stock;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StockMapper extends BaseMapper<Stock> {
}
(2)Service 层:库存扣减业务
java
运行
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.stock.entity.Stock;
import com.example.stock.mapper.StockMapper;
import javax.annotation.Resource;
@Service
public class StockService {
@Resource
private StockMapper stockMapper;
// 库存扣减方法(本地事务,Seata 会自动管理分支事务)
@Transactional
public void deductStock(Long productId, Integer num) {
// 1. 查询库存
Stock stock = stockMapper.selectOne(new UpdateWrapper<Stock>()
.eq("product_id", productId));
if (stock == null || stock.getNum() < num) {
throw new RuntimeException("库存不足");
}
// 2. 扣减库存
stockMapper.update(null, new UpdateWrapper<Stock>()
.eq("product_id", productId)
.setSql("num = num - " + num));
}
}
(3)Controller 层:提供接口供订单服务调用
java
运行
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.stock.service.StockService;
import javax.annotation.Resource;
@RestController
@RequestMapping("/stock")
public class StockController {
@Resource
private StockService stockService;
// 库存扣减接口
@PostMapping("/deduct")
public void deductStock(
@RequestParam Long productId,
@RequestParam Integer num
) {
stockService.deductStock(productId, num);
}
}
2. 订单服务(order-service):发起分布式事务
(1)远程调用库存服务(Feign)
java
运行
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
// 远程调用库存服务
@FeignClient(name = "stock-service") // 库存服务名
public interface StockFeignClient {
@PostMapping("/stock/deduct")
void deductStock(
@RequestParam("productId") Long productId,
@RequestParam("num") Integer num
);
}
(2)Service 层:发起全局事务
java
运行
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.order.entity.Order;
import com.example.order.mapper.OrderMapper;
import com.example.order.feign.StockFeignClient;
import javax.annotation.Resource;
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private StockFeignClient stockFeignClient;
// @GlobalTransactional:Seata 全局事务注解,标记为事务发起者
@GlobalTransactional(name = "create-order-transaction", rollbackFor = Exception.class)
@Transactional
public void createOrder(Long userId, Long productId, Integer num) {
try {
// 1. 远程调用库存服务,扣减库存(分支事务1)
stockFeignClient.deductStock(productId, num);
// 2. 创建订单(本地事务,分支事务2)
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setNum(num);
order.setStatus(1); // 订单状态:1-已创建
orderMapper.insert(order);
// 模拟异常:测试事务回滚(生产环境删除)
// throw new RuntimeException("订单创建失败,测试回滚");
} catch (Exception e) {
// 抛出异常,Seata 会触发全局回滚
throw new RuntimeException("订单创建失败:" + e.getMessage());
}
}
}
(3)Controller 层:订单创建接口
java
运行
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.order.service.OrderService;
import javax.annotation.Resource;
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource
private OrderService orderService;
// 创建订单接口(发起分布式事务)
@PostMapping("/create")
public String createOrder(
@RequestParam Long userId,
@RequestParam Long productId,
@RequestParam Integer num
) {
orderService.createOrder(userId, productId, num);
return "订单创建成功";
}
}
3. 测试分布式事务
- 正常场景:库存充足,订单创建成功,库存扣减成功,事务提交;
- 异常场景:在订单创建后抛出异常,Seata 会触发全局回滚,库存恢复至扣减前状态,订单不保存。
五、Seata 进阶配置(生产环境必备)
1. 持久化配置(MySQL)
默认 Seata TC 数据存储在内存,重启后丢失,生产环境需配置 MySQL 持久化:
- 在 Seata 服务器中执行初始化 SQL(Seata 官网提供);
- 修改 Seata 配置文件
registry.conf和file.conf,指定数据库连接信息。
2. 事务超时配置
yaml
seata:
client:
tm:
default-global-transaction-timeout: 60000 # 全局事务超时时间(60秒)
3. 重试机制配置
yaml
seata:
client:
tm:
commit-retry-count: 3 # 提交重试次数
rollback-retry-count: 3 # 回滚重试次数
rm:
lock:
retry-interval: 100 # 锁重试间隔(毫秒)
retry-times: 3 # 锁重试次数
六、避坑指南
坑点 1:数据源未代理,undo_log 表无数据,事务无法回滚
表现:事务异常时,库存扣减后无法回滚,undo_log 表无记录;✅ 解决方案:确保配置了 DataSourceProxy,且 MyBatis 使用代理后的数据源,避免直接使用原始数据源。
坑点 2:事务组名称配置不一致,无法注册分支事务
表现:启动时报错「no available service」,分支事务无法注册到 TC;✅ 解决方案:确保所有服务的 tx-service-group 与 Seata TC 配置的事务组名称一致。
坑点 3:Feign 调用超时,导致事务回滚
表现:跨服务调用耗时过长,触发 Feign 超时,Seata 事务回滚;✅ 解决方案:调整 Feign 超时时间,确保大于 Seata 全局事务超时时间。
坑点 4:本地事务未开启,数据无法提交
表现:业务逻辑执行成功,但数据未入库;✅ 解决方案:参与事务的 Service 方法需添加 @Transactional 注解,开启本地事务。
七、终极总结:分布式事务的核心是「权衡一致性与性能」
Seata AT 模式以「无侵入、自动补偿」的优势,成为分布式事务的主流解决方案,但其本质是通过两阶段提交实现数据一致性,会带来一定性能损耗。企业级实战中,需在「数据一致性」与「系统性能」之间权衡:
- 非核心场景:可放弃强一致性,采用最终一致性(如消息队列异步补偿),提升性能;
- 核心场景(订单、支付):必须保证强一致性,使用 Seata AT 模式,确保数据安全。
核心原则总结:
- 最小化事务范围:分布式事务仅包含必要的跨服务操作,减少锁持有时间,提升性能;
- 异常快速失败:业务逻辑中尽早判断异常场景(如库存不足),避免事务执行过半后回滚;
- 配置适配生产:开启持久化、合理设置超时与重试次数,避免因配置不当导致事务失败;
- 监控运维保障:生产环境需集成 Seata 监控,实时监控事务状态,快速排查异常。
记住:分布式事务不是「银弹」,需结合业务场景选择合适的方案,Seata AT 模式为核心业务提供了简单高效的一致性保障,是后端开发者必备的分布式技能。