微服务场景下为什么需要分布式事务
单体应用拆分为微服务后,一个业务操作往往涉及多个服务的数据写入。订单创建需要同时写订单库、扣减库存、扣减账户余额,任何一步失败都会导致数据不一致。本地事务无法跨服务,消息最终一致性方案在核心链路上又过于复杂。Seata的AT模式通过拦截SQL自动生成回滚日志,用最小的代码侵入解决分布式事务问题,是目前Spring Boot微服务体系中落地成本最低的方案。
Seata AT模式工作原理
AT模式的核心流程分为两个阶段:
一阶段:拦截业务SQL,在执行前保存数据快照(before image),执行SQL后保存数据快照(after image),生成回滚日志(undo log)。一阶段结束时本地事务提交,数据对其他事务可见。
二阶段:事务协调者根据各分支事务的执行结果决定提交或回滚。提交时异步清理undo log,回滚时根据undo log反向补偿数据。
Seata Server部署
使用Docker部署Seata Server:
version: '3'
services:
seata-server:
image: seataio/seata-server:2.2
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_IP=192.168.1.100
volumes:
- ./seata-config:/seata-server/resources
Seata Server配置(application.yml):
server:
port: 7091
seata:
config:
type: nacos
nacos:
server-addr: 192.168.1.100:8848
namespace: seata
group: SEATA_GROUP
registry:
type: nacos
nacos:
server-addr: 192.168.1.100:8848
namespace: seata
group: SEATA_GROUP
store:
mode: db
db:
datasource: druid
url: jdbc:mysql://192.168.1.100:3306/seata?useSSL=false
user: seata
password: seata_pwd
Spring Boot微服务接入Seata
添加依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2023.0.1.2</version>
</dependency>
数据源代理配置
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new HikariDataSource();
}
@Bean
@Primary
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
}
Seata通过DataSourceProxy拦截所有SQL执行,自动管理undo log。必须确保业务数据源被代理,否则事务回滚失效。
undo_log表创建
每个参与分布式事务的数据库都必须创建undo_log表:
CREATE TABLE IF NOT EXISTS `undo_log` (
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT NOT NULL COMMENT '0:normal status, 1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB COMMENT = 'AT transaction mode undo log';
业务代码实战:订单-库存-账户
订单服务(TM端,事务发起方)
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockClient stockClient;
@Autowired
private AccountClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Order createOrder(OrderDTO dto) {
// 1. 创建订单
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setCount(dto.getCount());
order.setAmount(dto.getAmount());
order.setStatus("INIT");
orderMapper.insert(order);
// 2. 扣减库存
stockClient.deduct(dto.getProductId(), dto.getCount());
// 3. 扣减账户余额
accountClient.debit(dto.getUserId(), dto.getAmount());
// 4. 更新订单状态
order.setStatus("SUCCESS");
orderMapper.updateById(order);
return order;
}
}
@GlobalTransactional注解标记全局事务边界。当stockClient或accountClient调用失败抛出异常时,Seata自动回滚所有分支事务。
库存服务(RM端,事务分支)
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
public void deduct(String productId, int count) {
Stock stock = stockMapper.selectByProductId(productId);
if (stock == null || stock.getRemain() < count) {
throw new BusinessException("库存不足");
}
stock.setRemain(stock.getRemain() - count);
stockMapper.updateById(stock);
}
}
RM端不需要加@GlobalTransactional注解。Seata通过DataSourceProxy自动拦截SQL,在一阶段保存undo log。回滚时根据undo log恢复原始数据。
踩坑记录与解决方案
坑1:数据源代理不生效
Spring Boot自动配置的DataSource如果优先级高于代理配置,会导致SQL不被拦截。解决方式:排除Seata的自动配置,手动配置代理:
@SpringBootApplication(exclude = {
SeataDataSourceAutoConfiguration.class
})
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
坑2:undo_log序列化失败
当表中存在JSON、BLOB等特殊类型字段时,Jackson序列化undo_log可能失败。配置Kryo序列化:
seata:
client:
undo:
data-serialization: kryo
坑3:全局事务超时
默认超时60秒,涉及多个远程调用的长链路事务容易超时。调整超时时间:
@GlobalTransactional(
name = "create-order",
rollbackFor = Exception.class,
timeoutMills = 300000
)
坑4:读隔离问题
AT模式一阶段提交后数据对其他事务可见,可能读到中间状态数据。解决方案:在Seata配置中开启全局锁:
seata:
client:
undo:
log-serialization: kryo
only-care-update-columns: true
lock:
retry-times: 30
retry-interval: 10
全局锁确保一阶段提交后、二阶段提交前,其他事务无法修改同一行数据,避免脏读。
生产环境监控与性能调优
Seata内置指标暴露,对接Prometheus:
seata:
metrics:
enabled: true
exporter-type: prometheus
exporter-prometheus-port: 9898
关键监控指标:seata.transaction.active(活跃事务数)、seata.transaction.commit.rate(提交成功率)、seata.transaction.rollback.rate(回滚率)。当回滚率超过10%时触发告警,排查是否有业务逻辑异常或网络超时。
性能调优建议
- 将Seata Server的存储模式从file改为db,支持集群部署
- undo_log表数据量增长后需要定期清理,建议设置定时任务删除7天前的记录
- 远程调用使用OpenFeign时配置超时和重试,避免因网络抖动导致全局事务回滚
- 高并发场景下将Seata Server从单节点扩展为3节点集群,通过Nacos做服务发现
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/seataat-mo-shi-fen-bu-shi-shi-wu-shi-zhan-springboot-wei-fu/