Seata使用
Seata 分布式事务使用指南:从集成到实战
Seata 作为分布式事务解决方案,其使用流程简洁高效,核心通过注解标记全局事务、代理数据源实现自动回滚。本文基于 Spring Cloud Alibaba 生态,详细介绍 Seata 的集成步骤与实战要点。
环境准备
在使用 Seata 前,需确保:
Seata Server 已启动:作为事务协调者(TC),需提前配置并启动(参考前文 Seata Server 配置);
数据库准备:所有参与分布式事务的微服务数据库需创建undo_log表(AT 模式用于回滚日志存储),SQL 脚本如下:
CREATE TABLE `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;
微服务集成 Seata
1. 引入依赖
在各参与分布式事务的微服务(如订单服务、库存服务、账户服务)中添加 Seata 依赖,注意版本需与 Seata Server 一致:
<!-- Spring Cloud Alibaba Seata starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<!-- 排除默认Seata依赖,避免版本冲突 -->
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入与Seata Server一致的版本(如1.1.0) -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.1.0</version>
</dependency>
2. 配置文件(application.yml)
核心配置事务分组与注册中心,确保微服务能关联到 Seata Server:
spring:
application:
name: springcloudalibaba-seata-order # 服务名
cloud:
alibaba:
seata:
tx-service-group: my_test_tx_group # 事务分组(需与Seata Server的vgroupMapping一致)
nacos:
discovery:
server-addr: localhost:8848 # 注册中心地址(与Seata Server一致)
datasource:
druid: # 本地数据库配置
username: root
password: 123456
url: jdbc:mysql://localhost:3306/seata_order # 订单库(示例)
driver-class-name: com.mysql.jdbc.Driver
# MyBatis配置(可选,根据实际ORM框架调整)
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml
type-aliases-package: com.zhanghe.study.springcloudalibaba.model
mapper-locations: classpath:mybatis/mapper/**/*.xml
关键说明:
tx-service-group需与 Seata Server 的file.conf中vgroupMapping.my_test_tx_group配置一致,用于关联 TC 集群;- 若使用 Nacos 作为注册中心,需确保微服务与 Seata Server 的
registry.conf配置一致。
3. 拷贝 Seata 配置文件
将 Seata Server 的conf目录下的file.conf和registry.conf拷贝到微服务的src/main/resources目录下:
file.conf:配置事务分组、存储模式等(需与 Server 端保持一致);registry.conf:配置注册中心类型(如 Nacos),确保微服务能发现 Seata Server。
核心配置:数据源代理
Seata 通过代理数据源实现 SQL 拦截与 undo 日志生成(AT 模式核心),需替换默认数据源为 Seata 的DataSourceProxy:
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.Resource;
import javax.sql.DataSource;
import java.io.IOException;
// 导入MyBatis配置
public class DataSourceConfiguration {
// 1. 配置原始数据源(如Druid)
// 绑定yml中的数据源配置
public DataSource dataSource() {
return new DruidDataSource();
}
// 2. 用Seata的DataSourceProxy代理数据源
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
// 3. 配置MyBatis使用代理后的数据源
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSourceProxy dataSourceProxy,
MybatisProperties mybatisProperties) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy); // 设置代理数据源
// 绑定MyBatis配置(mapper路径、config位置等)
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] mapperLocations = resolver.getResources(mybatisProperties.getMapperLocations()[0]);
sqlSessionFactoryBean.setMapperLocations(mapperLocations);
if (mybatisProperties.getConfigLocation() != null) {
Resource[] configResources = resolver.getResources(mybatisProperties.getConfigLocation());
sqlSessionFactoryBean.setConfigLocation(configResources[0]);
}
return sqlSessionFactoryBean;
}
}
为什么需要代理数据源?
- Seata 通过代理数据源拦截 SQL 执行,自动生成 undo 日志(记录数据修改前的状态);
- 全局回滚时,通过 undo 日志恢复数据,实现无侵入式事务回滚。
启动类配置
需排除 Spring Boot 默认的数据源自动配置,避免与 Seata 代理数据源冲突:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
// 排除默认数据源配置
// 启用服务发现
// 启用Feign(用于服务间调用)
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
}
标记全局事务:@GlobalTransactional
在分布式事务的发起方(TM)的核心业务方法上添加@GlobalTransactional注解,标记为全局事务:
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
public class OrderService {
private OrderDao orderDao; // 订单DAO
private StorageClient storageClient; // Feign客户端:调用库存服务
private AccountClient accountClient; // Feign客户端:调用账户服务
/**
* 创建订单(全局事务入口)
*/
public void create(Order order) {
// 1. 创建订单(本地事务)
System.out.println("创建订单");
orderDao.addOrder(order);
// 2. 调用库存服务:扣减库存(远程事务)
System.out.println("扣减库存");
storageClient.decrease(order.getProductId(), order.getCount());
// 3. 调用账户服务:扣减余额(远程事务)
System.out.println("扣减账户余额");
accountClient.decrease(order.getUserId(), order.getMoney());
// 4. 修改订单状态为“完成”(本地事务)
System.out.println("修改订单状态");
orderDao.updateStatus(order.getId(), 1); // 1表示成功
}
}
注解参数说明:
name:全局事务名称(建议唯一,便于追踪);rollbackFor:指定触发回滚的异常类型(默认仅回滚RuntimeException);timeoutMills:全局事务超时时间(默认 60000ms)。
服务间调用:Feign 客户端
远程服务调用需通过 Feign 实现(确保 XID 全局事务 ID 在调用链路中传播):
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
// 库存服务Feign客户端
// 目标服务名
public interface StorageClient {
// 扣减库存
void decrease( Long productId,
Integer count);
}
// 账户服务Feign客户端(类似)
public interface AccountClient {
void decrease( Long userId,
BigDecimal money);
}
XID 传播机制:
Seata 通过ThreadLocal存储 XID(全局事务 ID),Feign 调用时会自动将 XID 放入请求头,远程服务接收后通过拦截器提取 XID 并绑定到当前线程,确保分支事务关联到全局事务。
测试分布式事务
- 正常流程:所有服务调用成功,全局事务提交,
undo_log表记录被删除; - 异常流程:任意远程服务抛出异常(如库存不足),全局事务回滚,所有操作(创建订单、扣减库存、扣减余额)均被撤销,
undo_log表记录用于回滚恢复。
注意事项
- 版本一致性:微服务的
seata-all版本必须与 Seata Server 版本一致,否则可能出现通信异常; - 事务分组匹配:
tx-service-group需与 Seata Server 的vgroupMapping严格一致(大小写敏感); - undo_log 表:所有参与事务的数据库必须创建
undo_log表,否则 AT 模式回滚会失败; - 数据源代理:必须使用
DataSourceProxy代理数据源,否则 Seata 无法拦截 SQL 生成 undo 日志; - 全局事务范围:
@GlobalTransactional仅需添加在事务发起方(TM),参与方(RM)无需额外注解。
总结
Seata 通过@GlobalTransactional注解、数据源代理和 XID 传播机制,实现了分布式事务的无侵入式管理。核心步骤包括:集成依赖、配置事务分组、代理数据源、标记全局事务