1. 为什么需要分布式事务
在微服务架构中,一个业务操作往往需要跨多个服务完成。以电商下单为例,这个操作需要调用订单服务、库存服务和支付服务。如果其中某个服务操作失败,就会导致数据不一致的问题。
传统单体应用中使用本地事务可以保证ACID特性,但在微服务场景下,这种方案不再适用。我们需要引入分布式事务协调器来保证跨服务的数据一致性。这就是Seata这类框架存在的意义。
提示:分布式事务不是银弹,它会带来性能开销。实际项目中需要根据业务场景权衡是否真的需要强一致性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Seata核心概念解析
2.1 事务模式
Seata支持四种事务模式:
- AT模式(默认):基于全局锁的自动补偿机制
- TCC模式:需要业务代码实现Try/Confirm/Cancel接口
- SAGA模式:长事务解决方案
- XA模式:传统XA协议实现
对于大多数Spring Boot项目,AT模式是最佳选择,因为它对代码侵入性最小。
2.2 核心组件
| 组件 | 作用 | 部署方式 |
|---|---|---|
| TC (Transaction Coordinator) | 事务协调器 | 独立部署 |
| TM (Transaction Manager) | 事务管理器 | 客户端集成 |
| RM (Resource Manager) | 资源管理器 | 客户端集成 |
3. 环境准备
3.1 版本兼容性
本次整合使用以下版本组合:
- Spring Boot 3.2.5
- Seata 1.8.0
- JDK 17+
- MySQL 8.0+
注意:Seata 1.8.0需要JDK 17+支持,与Spring Boot 3.x的Java版本要求一致。
3.2 数据库准备
需要创建三个数据库表:
- 业务库中的业务表
- Seata的全局事务表(global_table)
- Seata的分支事务表(branch_table)
sql复制-- 创建Seata所需表
CREATE TABLE IF NOT EXISTS `global_table` (
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
);
-- branch_table和lock_table结构类似,此处省略
4. Spring Boot集成Seata
4.1 添加依赖
xml复制<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.2.3</version>
</dependency>
4.2 配置文件
application.yml关键配置:
yaml复制seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: 127.0.0.1:8091
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
cluster: default
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
4.3 数据源代理配置
java复制@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean("dataSource")
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
5. 事务使用实战
5.1 全局事务示例
java复制@Service
public class OrderServiceImpl implements OrderService {
@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {
// 1. 扣减库存
storageFeignClient.deduct(orderDTO.getCommodityCode(), orderDTO.getCount());
// 2. 创建订单
orderMapper.create(orderDTO);
// 3. 扣减余额
accountFeignClient.debit(orderDTO.getUserId(), orderDTO.getMoney());
}
}
5.2 常见问题排查
-
事务不生效:
- 检查@GlobalTransactional是否添加在public方法上
- 确认数据源是否被正确代理
- 检查Seata服务端是否正常启动
-
连接池耗尽:
- 适当增加druid连接池大小
- 减少全局事务超时时间
-
性能问题:
- 考虑使用TCC模式替代AT模式
- 优化业务逻辑,减少全局锁持有时间
6. 生产环境优化建议
6.1 配置优化
properties复制# 减少全局锁竞争
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
# 适当调大事务超时时间
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
6.2 监控集成
建议集成Prometheus监控Seata运行状态:
xml复制<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-metrics-prometheus</artifactId>
<version>1.8.0</version>
</dependency>
配置Prometheus scrape配置:
yaml复制scrape_configs:
- job_name: 'seata'
static_configs:
- targets: ['seata-server:9898']
7. 替代方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Seata AT | 代码侵入小,学习成本低 | 性能开销大 | 中小型系统 |
| Seata TCC | 性能较好 | 需要实现三个接口 | 高性能场景 |
| 本地消息表 | 无单点问题 | 实现复杂 | 最终一致性场景 |
| SAGA | 适合长事务 | 调试困难 | 跨系统业务流程 |
在实际项目中,我们团队发现对于80%的场景,Seata AT模式已经足够。只有在特别注重性能的支付核心系统中,才会考虑使用TCC模式。
8. 踩坑经验分享
-
Nacos配置问题:
第一次使用时,容易忽略Nacos中需要预先上传Seata的配置。建议直接从Seata的conf目录下获取config.txt并上传到Nacos。 -
MySQL驱动兼容性:
Seata 1.8.0对MySQL Connector/J 8.0.23+有更好的支持,使用旧版本可能导致XA事务异常。 -
Spring事务传播:
避免在@GlobalTransactional方法内部再使用@Transactional,这会导致意外行为。 -
超时设置:
生产环境中,全局事务超时时间(timeout)应该根据业务操作的最长时间合理设置,默认60秒可能不够。 -
表主键要求:
Seata AT模式要求业务表必须有主键,否则无法生成undo_log记录。
我在实际项目中遇到过一个典型问题:当使用MySQL的JSON类型字段时,Seata的undo_log记录会非常大。解决方案是重写对应的TypeHandler,或者在业务设计上避免在事务边界内操作大JSON字段。
