1. 项目概述
最近在重构公司电商平台的订单系统时,遇到了一个棘手的问题:当用户下单后需要同时更新库存、生成物流单和扣减优惠券,这三个操作分布在不同的微服务中,经常出现部分成功部分失败的情况。为了解决这个分布式事务问题,我决定引入Seata这个开源的分布式事务解决方案。本文将分享如何在Spring Boot 3.2.5中整合Seata 1.8.0的完整过程。
Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务中间件,它提供了AT、TCC、SAGA和XA四种事务模式。我们这次主要使用AT模式,因为它对业务代码侵入性最小,只需要在方法上添加@GlobalTransactional注解即可实现分布式事务。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与依赖配置
2.1 基础环境要求
在开始整合前,请确保你的开发环境满足以下要求:
- JDK 17+(Spring Boot 3.x要求)
- Maven 3.6+或Gradle 7.x
- MySQL 5.7+/PostgreSQL(作为业务数据库)
- Nacos 2.x(作为配置中心和注册中心)
- Seata Server 1.8.0
注意:Seata 1.8.0对Spring Boot 3.x的支持是通过seata-spring-boot-starter实现的,不再需要早期的seata-all依赖。
2.2 Maven依赖配置
在pom.xml中添加以下关键依赖:
xml复制<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Seata Starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.8.0</version>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2022.0.0.0</version>
</dependency>
</dependencies>
2.3 Seata Server安装与配置
- 从Seata官网下载1.8.0版本的服务端
- 解压后修改conf/registry.conf文件,配置注册中心(这里使用Nacos):
conf复制registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
namespace = ""
cluster = "default"
}
}
- 初始化Seata所需的数据库表(执行conf/mysql.sql脚本)
- 启动Seata Server:
sh bin/seata-server.sh
3. Spring Boot项目配置
3.1 配置文件详解
在application.yml中添加以下Seata相关配置:
yaml复制spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
datasource:
url: jdbc:mysql://localhost:3306/order_db?useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
disable-global-transaction: false
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace: ""
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
3.2 数据库表结构调整
为了支持Seata的AT模式,需要在每个业务数据库中创建undo_log表:
sql复制CREATE TABLE IF NOT EXISTS `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT(20) NOT NULL,
`xid` VARCHAR(100) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` INT(11) 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 = utf8;
4. 核心功能实现
4.1 全局事务注解使用
在需要分布式事务的方法上添加@GlobalTransactional注解:
java复制@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private CouponFeignClient couponFeignClient;
@GlobalTransactional(name = "createOrder", timeoutMills = 300000)
public Order createOrder(OrderDTO orderDTO) {
// 1. 创建本地订单
Order order = createLocalOrder(orderDTO);
// 2. 调用库存服务扣减库存
inventoryFeignClient.deduct(orderDTO.getProductId(), orderDTO.getQuantity());
// 3. 调用优惠券服务核销优惠券
if(orderDTO.getCouponId() != null) {
couponFeignClient.useCoupon(orderDTO.getUserId(), orderDTO.getCouponId());
}
return order;
}
}
4.2 Feign客户端配置
为了确保Feign调用能传递Seata的XID(全局事务ID),需要添加拦截器:
java复制@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor seataFeignInterceptor() {
return template -> {
String xid = RootContext.getXID();
if (StringUtils.isNotBlank(xid)) {
template.header(RootContext.KEY_XID, xid);
}
};
}
}
4.3 事务异常处理
在全局异常处理中,需要特别注意Seata相关异常:
java复制@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(TransactionException.class)
public ResponseEntity<String> handleTransactionException(TransactionException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Transaction failed: " + e.getMessage());
}
}
5. 测试与验证
5.1 正常流程测试
- 发送创建订单请求
- 观察各服务日志,确认:
- 订单记录创建成功
- 库存扣减成功
- 优惠券核销成功
- 检查各数据库的undo_log表,事务完成后应自动清除记录
5.2 异常回滚测试
模拟库存不足情况,强制抛出异常:
java复制@GlobalTransactional
public Order createOrder(OrderDTO orderDTO) {
// 创建订单
Order order = createLocalOrder(orderDTO);
// 扣减库存(模拟库存不足)
if(orderDTO.getQuantity() > 100) {
throw new RuntimeException("Insufficient inventory");
}
// 核销优惠券
couponFeignClient.useCoupon(orderDTO.getUserId(), orderDTO.getCouponId());
return order;
}
验证:
- 订单记录被回滚(数据库无新增)
- 库存未扣减
- 优惠券状态未改变
6. 生产环境注意事项
6.1 性能优化建议
- 调整Seata server的线程池参数(conf/file.conf):
conf复制store {
mode = "db"
db {
datasource = "druid"
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata?useSSL=false"
user = "root"
password = "123456"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
}
}
- 客户端配置优化:
yaml复制seata:
client:
rm:
report-retry-count: 5
table-meta-check-enable: false
tm:
commit-retry-count: 5
rollback-retry-count: 5
6.2 常见问题排查
-
问题:全局事务不生效
- 检查@GlobalTransactional是否添加到入口方法
- 确认Seata配置正确,特别是tx-service-group和vgroup-mapping的对应关系
- 检查Nacos中是否有seata-server的服务注册
-
问题:分支事务未回滚
- 确认所有参与事务的服务都正确配置了Seata客户端
- 检查undo_log表是否创建成功
- 查看Seata server日志,确认收到所有分支事务的回滚请求
-
问题:性能瓶颈
- 考虑将store.mode改为redis(需1.8.0+版本支持)
- 调整客户端和服务端的retry-count参数
- 对于高频小事务,考虑使用TCC模式替代AT模式
7. 高级特性探索
7.1 多数据源支持
对于使用多数据源的项目,需要为每个数据源配置Seata的DataSourceProxy:
java复制@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSource dataSource(DataSource primaryDataSource) {
return new DataSourceProxy(primaryDataSource);
}
}
7.2 Saga模式实践
对于长事务场景,可以使用Seata的Saga模式:
- 定义状态机JSON文件
- 实现每个服务节点的补偿操作
- 通过@SagaStart注解启动Saga事务
java复制@SagaStart
public void placeOrder(OrderDTO orderDTO) {
// Saga流程步骤
}
7.3 与Spring Cloud Stream集成
在消息队列场景下确保事务一致性:
java复制@GlobalTransactional
public void processOrder(OrderEvent event) {
// 处理订单
orderService.process(event);
// 发送库存消息
streamBridge.send("inventory-out-0",
MessageBuilder.withPayload(event)
.setHeader(RootContext.KEY_XID, RootContext.getXID())
.build());
}
在实际项目中整合Seata时,我发现最大的挑战不是技术实现,而是事务边界的设计。过度使用分布式事务会导致系统性能下降,而事务粒度太粗又会影响系统可用性。我的经验是:能不用分布式事务就不用,优先考虑最终一致性方案;必须使用时,尽量缩小事务范围和持续时间。
