微服务架构下分布式事务的必要性
单体应用拆分为微服务后,本地事务无法覆盖跨服务的数据一致性需求。订单服务创建订单、库存服务扣减库存、账户服务扣减余额,三个操作分布在三个独立数据库中,任何一步失败都需要全部回滚。Seata作为目前最活跃的开源分布式事务框架,提供了AT、TCC、Saga、XA四种模式,其中AT模式对业务代码侵入最小,是Spring Boot项目的首选方案。
Seata AT模式原理与架构
AT模式的核心是”两阶段提交+全局锁”。一阶段:拦截业务SQL,生成前镜像和后镜像,写入undo_log表,提交本地事务并释放本地锁。二阶段:全局提交时异步清理undo_log;全局回滚时根据undo_log反向补偿数据。
Seata Server(TC)作为事务协调器独立部署,TM(事务管理器)和RM(资源管理器)集成在业务应用中。生产环境TC需集群部署,使用Nacos或Redis作为注册中心。
Spring Boot 3.x集成Seata完整步骤
1. 引入依赖
<!-- pom.xml -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.8.0</version>
</dependency>
<!-- 数据源代理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2023.0.1.0</version>
</dependency>
2. 配置Seata客户端
# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: my-tx-group
service:
vgroup-mapping:
my-tx-group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: seata
group: SEATA_GROUP
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: seata
group: SEATA_GROUP
3. 每个参与事务的数据库创建undo_log表
-- undo_log表(每个业务库都要创建)
CREATE TABLE IF NOT EXISTS undo_log (
id BIGINT NOT NULL AUTO_INCREMENT,
branch_id BIGINT NOT NULL,
xid VARCHAR(128) NOT NULL,
context MEDIUMBLOB NOT NULL,
rollback_info MEDIUMBLOB NOT NULL,
log_status INT NOT NULL,
log_created DATETIME NOT NULL,
log_modified DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
4. 业务代码使用@GlobalTransactional
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryClient inventoryClient;
@Autowired
private AccountClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Order createOrder(OrderDTO dto) {
// 1. 创建订单(本地事务,Seata自动代理)
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setAmount(dto.getAmount());
order.setStatus("INIT");
orderMapper.insert(order);
// 2. 扣减库存(远程调用)
inventoryClient.deduct(dto.getProductId(), dto.getQuantity());
// 3. 扣减余额(远程调用)
accountClient.debit(dto.getUserId(), dto.getAmount());
// 4. 更新订单状态
order.setStatus("PAID");
orderMapper.updateById(order);
return order;
}
}
@GlobalTransactional标注的方法内,所有数据库操作和Feign远程调用自动纳入分布式事务。任何一步抛出异常,Seata TC通知所有参与分支回滚。
数据源代理配置避坑
Seata AT模式必须代理数据源才能拦截SQL生成undo_log。Spring Boot 3.x使用HikariCP时,常见错误是代理顺序不对:
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource(DataSourceProperties properties) {
// 先创建原始数据源
HikariDataSource hikariDataSource = properties
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
// Seata代理包装
return new DataSourceProxy(hikariDataSource);
}
}
注意:不要在Seata代理外再套一层Druid代理,双层代理会导致SQL解析失败。如果需要Druid监控,建议在Seata代理的内层使用DruidDataSource。
生产环境高频故障排查
1. 全局锁等待超时
两个分布式事务操作同一行数据时,后到的事务等待全局锁直到超时。默认超时30秒:
# 调整全局锁超时(Seata配置中心)
client.rm.lock.retry-times=10
client.rm.lock.retry-interval=50
2. undo_log未清理导致表膨胀
正常情况下Seata自动清理undo_log。但TC宕机时可能出现残留记录。定期清理超过24小时的记录:
-- 定时清理脚本
DELETE FROM undo_log
WHERE log_created < DATE_SUB(NOW(), INTERVAL 24 HOUR)
AND log_status = 1;
3. 全局事务悬挂
分支事务回滚通知先于注册通知到达时,全局事务处于悬挂状态。开启悬挂防护:
# Seata配置
client.rm.async-commit-buffer-limit=10000
client.rm.report-retry-count=5
client.rm.table-meta-check-enable=true
4. XID未传递到下游服务
Feign调用时XID通过HTTP Header传递。确认拦截器已注册:
@Bean
public RequestInterceptor seataFeignInterceptor() {
return template -> {
String xid = RootContext.getXID();
if (xid != null) {
template.header(RootContext.KEY_XID, xid);
}
};
}
Seata分布式事务的本质是用性能换一致性。AT模式通过SQL解析和自动补偿降低了开发成本,但全局锁机制在高并发写场景下有性能瓶颈。写入冲突频繁的业务(如秒杀库存扣减),优先考虑TCC模式或业务层幂等设计,AT模式适合读多写少、冲突概率低的常规业务流程。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3x-fen-bu-shi-shi-wu-seata-ji-cheng-yu-sheng-chan/