Seata 分布式事务使用指南:从集成到实战
Seata 作为分布式事务解决方案,其使用流程简洁高效,核心通过注解标记全局事务、代理数据源实现自动回滚。本文基于 Spring Cloud Alibaba 生态,详细介绍 Seata 的集成步骤与实战要点。
环境准备
在使用 Seata 前,需确保:
Seata Server 已启动:作为事务协调者(TC),需提前配置并启动(参考前文 Seata Server 配置);
数据库准备:所有参与分布式事务的微服务数据库需创建undo_log表(AT 模式用于回滚日志存储),SQL 脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12
| 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 一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.1.0</version> </dependency>
|
2. 配置文件(application.yml)
核心配置事务分组与注册中心,确保微服务能关联到 Seata Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| spring: application: name: springcloudalibaba-seata-order cloud: alibaba: seata: tx-service-group: my_test_tx_group nacos: discovery: server-addr: localhost:8848 datasource: druid: username: root password: 123456 url: jdbc:mysql://localhost:3306/seata_order driver-class-name: com.mysql.jdbc.Driver
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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;
@Configuration @EnableConfigurationProperties(MybatisProperties.class) public class DataSourceConfiguration {
@Bean @ConfigurationProperties(prefix = "spring.datasource.druid") public DataSource dataSource() { return new DruidDataSource(); }
@Bean public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); }
@Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSourceProxy dataSourceProxy, MybatisProperties mybatisProperties) throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy);
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 代理数据源冲突:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients public class OrderApp { public static void main(String[] args) { SpringApplication.run(OrderApp.class, args); } }
|
标记全局事务:@GlobalTransactional
在分布式事务的发起方(TM)的核心业务方法上添加@GlobalTransactional注解,标记为全局事务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import io.seata.spring.annotation.GlobalTransactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
@Service public class OrderService {
@Autowired private OrderDao orderDao;
@Autowired private StorageClient storageClient;
@Autowired private AccountClient accountClient;
@GlobalTransactional( name = "create-order", // 事务名称(唯一标识) rollbackFor = Exception.class // 发生任何异常均回滚 ) public void create(Order order) { System.out.println("创建订单"); orderDao.addOrder(order);
System.out.println("扣减库存"); storageClient.decrease(order.getProductId(), order.getCount());
System.out.println("扣减账户余额"); accountClient.decrease(order.getUserId(), order.getMoney());
System.out.println("修改订单状态"); orderDao.updateStatus(order.getId(), 1); } }
|
注解参数说明:
name:全局事务名称(建议唯一,便于追踪);
rollbackFor:指定触发回滚的异常类型(默认仅回滚RuntimeException);
timeoutMills:全局事务超时时间(默认 60000ms)。
服务间调用:Feign 客户端
远程服务调用需通过 Feign 实现(确保 XID 全局事务 ID 在调用链路中传播):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "springcloudalibaba-seata-storage") public interface StorageClient {
@PostMapping("/storage/decrease") void decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count); }
@FeignClient(name = "springcloudalibaba-seata-account") public interface AccountClient { @PostMapping("/account/decrease") void decrease(@RequestParam("userId") Long userId, @RequestParam("money") 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 传播机制,实现了分布式事务的无侵入式管理。核心步骤包括:集成依赖、配置事务分组、代理数据源、标记全局事务