Spring Boot集成Seata分布式事务实战指南
微服务架构下,跨服务的数据一致性是后端开发无法回避的核心问题。订单创建需要同时操作订单服务、库存服务和账户服务,任何一个环节失败都需要全部回滚。Seata作为阿里开源的分布式事务框架,提供AT、TCC、Saga、XA四种事务模式,其中AT模式对业务代码侵入最小,是Spring Boot微服务架构的首选方案。
Seata AT模式运行原理
AT模式的核心是两阶段提交加自动SQL解析:
- 一阶段:拦截业务SQL,生成前镜像(before image)→执行SQL→生成后镜像(after image)→生成回滚日志(undo log)→提交本地事务
- 二阶段提交:异步删除undo log,事务完成
- 二阶段回滚:根据undo log的前镜像反向补偿SQL,恢复数据
AT模式对业务SQL有要求:必须支持本地ACID事务,且SQL对应的表必须有主键或唯一索引——Seata依赖主键生成前镜像的WHERE条件。
Seata Server部署与配置
Seata Server(TC事务协调器)支持三种存储模式:file、db、redis。生产环境必须使用db或redis模式保证事务日志持久化。
MySQL存储模式配置:
# seata-server application.yml
server:
port: 7091
seata:
config:
type: nacos
nacos:
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
registry:
type: nacos
nacos:
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true
user: seata
password: seata_pwd
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
Seata Server依赖的数据库表结构需要提前创建,SQL脚本位于seata-server的script/server/db目录下。初始化后启动Seata Server:
# Docker部署
docker run -d --name seata-server \
-p 7091:7091 \
-p 8091:8091 \
-v /opt/seata/config:/seata-server/resources \
seataio/seata-server:2.0.0
Spring Boot微服务集成Seata客户端
每个参与分布式事务的微服务都需要集成Seata客户端。以订单服务为例:
Maven依赖:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2023.0.1.0</version>
</dependency>
客户端配置:
# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: order-tx-group
service:
vgroup-mapping:
order-tx-group: default
stock-tx-group: default
account-tx-group: default
registry:
type: nacos
nacos:
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
config:
type: nacos
nacos:
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
# 数据源代理配置(AT模式必须使用Seata代理数据源)
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/order_db
username: root
password: root_pwd
关键点:Seata AT模式通过代理数据源拦截SQL,自动生成undo log。Spring Boot自动配置会检测Seata依赖并创建DataSourceProxy,但手动配置更可控:
@Configuration
public class SeataDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean("dataSource")
public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
分布式事务代码实现
订单服务作为事务发起方,使用@GlobalTransactional注解标注事务入口:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockFeignClient stockClient;
@Autowired
private AccountFeignClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
public OrderCreateResult createOrder(OrderCreateRequest request) {
// 1. 创建订单
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setTotalAmount(request.getAmount());
order.setStatus("CREATED");
orderMapper.insert(order);
// 2. 扣减库存(远程调用库存服务)
stockClient.deduct(request.getProductId(), request.getQuantity());
// 3. 扣减账户余额(远程调用账户服务)
accountClient.debit(request.getUserId(), request.getAmount());
return new OrderCreateResult(order.getId(), "SUCCESS");
}
}
库存服务作为事务参与方,不需要添加全局事务注解,Seata通过XID传播自动加入全局事务:
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
@Transactional(rollbackFor = Exception.class)
public void deduct(Long productId, Integer quantity) {
Stock stock = stockMapper.selectByProductId(productId);
if (stock == null) {
throw new BusinessException("商品不存在: " + productId);
}
if (stock.getAvailable() < quantity) {
throw new BusinessException("库存不足: 可用" + stock.getAvailable() + ", 需要" + quantity);
}
stockMapper.deduct(productId, quantity);
}
}
XID传播通过Feign拦截器自动完成:
@Configuration
public class SeataFeignConfig {
@Bean
public RequestInterceptor seataFeignInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
String xid = RootContext.getXID();
if (xid != null) {
template.header(RootContext.KEY_XID, xid);
template.header("XID", xid);
}
}
};
}
}
异常场景与回滚验证
分布式事务最难处理的是异常场景的一致性保障。以下三个场景需要逐一验证:
场景一:库存服务抛出异常,订单和账户数据应自动回滚。
@Test
public void testStockInsufficient_Rollback() {
// 准备: 库存仅剩5件
stockService.initStock(productId, 5);
// 执行: 下单10件
assertThrows(BusinessException.class, () -> {
orderService.createOrder(new OrderCreateRequest(userId, productId, 10, amount));
});
// 验证: 订单表无记录,库存不变
assertEquals(0, orderMapper.selectByUserId(userId).size());
assertEquals(5, stockMapper.selectByProductId(productId).getAvailable());
}
场景二:账户余额不足,订单和库存数据应自动回滚。
场景三:网络超时导致远程调用失败。Feign默认超时时间需要合理配置:
# application.yml
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 10000
circuitbreaker:
enabled: true
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 15s
服务治理与性能优化
Seata AT模式在性能上的主要瓶颈是一阶段写入undo log带来的IO开销。优化策略:
- undo log表索引优化:为branch_table和lock_table添加合理的复合索引,减少锁冲突
- 批量操作优化:单事务内避免循环调用远程服务,改为批量接口减少RPC次数
- 全局事务超时配置:默认60秒,长事务场景需适当延长
- 读写隔离策略:AT模式默认全局锁读已提交,对一致性要求不高的场景可降级为读未提交
全局事务监控也是服务治理的重要环节。Seata提供全局事务查询API,配合Prometheus指标采集可实现事务成功率、平均耗时等核心指标的监控:
@Component
public class SeataMetrics {
private final Counter txTotal = Counter.build()
.name("seata_global_tx_total")
.help("Total global transactions")
.labelNames("status")
.register();
@EventListener
public void onGlobalTransactionEvent(GlobalTransactionEvent event) {
String status = event.getStatus().name();
txTotal.labels(status).inc();
}
}
分布式事务不是银弹。业务设计时应优先考虑通过事件驱动、最终一致性方案(如消息表+定时补偿)规避分布式事务。仅在强一致性场景(如资金扣减、库存扣减)下才引入Seata,这是微服务架构中服务治理的基本原则。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-ji-cheng-seata-fen-bu-shi-shi-wu-shi-zhan-at-mo/