1. 项目概述
在微服务架构中,数据一致性一直是开发者面临的最大挑战之一。当业务操作需要跨多个服务进行数据修改时,传统的本地事务机制完全失效。我最近在一个电商项目中就遇到了典型的订单-库存-积分三服务联动的分布式事务问题,最终采用Seata框架成功解决了这个痛点。
Seata是阿里巴巴开源的分布式事务解决方案,提供了AT、TCC、SAGA和XA四种模式。其中AT模式对业务代码侵入最小,适合大多数需要强一致性的场景。本文将基于SpringCloud Alibaba生态,手把手带你实现一个完整的分布式事务案例。
2. 核心需求解析
2.1 典型业务场景
假设我们有一个简化的电商系统,包含三个微服务:
- 订单服务:创建订单记录
- 库存服务:扣减商品库存
- 积分服务:增加用户积分
当用户下单时,这三个操作必须作为一个原子单元执行:要么全部成功,要么全部回滚。这就是典型的分布式事务需求。
2.2 技术选型对比
常见的分布式事务方案包括:
- 本地消息表:实现简单但需要额外维护消息表
- MQ事务消息:依赖消息中间件,有学习成本
- TCC模式:需要编写try/confirm/cancel代码
- Seata AT模式:自动补偿,对代码侵入小
经过对比,我们选择Seata AT模式,因为:
- 与SpringCloud Alibaba天然集成
- 支持大部分SQL语句的自动回滚
- 社区活跃,企业应用广泛
- 提供全局锁机制防止脏写
3. 环境准备与配置
3.1 组件版本说明
确保各组件版本兼容:
- SpringBoot 2.6.11
- SpringCloud 2021.0.4
- SpringCloud Alibaba 2021.0.4.0
- Seata 1.6.1
- MySQL 8.0
- Nacos 2.1.0(作为配置中心和注册中心)
3.2 Seata Server部署
- 下载Seata Server:
bash复制wget https://github.com/seata/seata/releases/download/v1.6.1/seata-server-1.6.1.tar.gz
tar -zxvf seata-server-1.6.1.tar.gz
- 修改conf/registry.conf配置:
conf复制registry {
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
}
- 启动Seata Server:
bash复制sh bin/seata-server.sh -p 8091 -h 127.0.0.1
3.3 数据库准备
每个业务数据库都需要添加undo_log表:
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;
4. 代码实现详解
4.1 添加依赖
在所有微服务的pom.xml中添加:
xml复制<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2021.0.4.0</version>
</dependency>
4.2 配置数据源代理
必须使用Seata的数据源代理:
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);
}
}
4.3 全局事务注解
在业务入口方法添加@GlobalTransactional:
java复制@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
@PostMapping("/create")
public Result createOrder(@RequestBody OrderDTO orderDTO) {
// 1. 创建订单
orderService.create(orderDTO);
// 2. 扣减库存
stockFeignService.deduct(orderDTO.getProductId(), orderDTO.getCount());
// 3. 增加积分
pointsFeignService.increase(orderDTO.getUserId(), orderDTO.getAmount());
return Result.success();
}
}
4.4 Feign客户端配置
确保Feign客户端开启Seata支持:
yaml复制feign:
client:
config:
default:
loggerLevel: full
hystrix:
enabled: false
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
5. 核心原理剖析
5.1 AT模式工作流程
-
一阶段:
- 解析SQL生成前后镜像
- 执行业务SQL
- 生成undo_log记录
-
二阶段提交:
- 删除undo_log
- 释放全局锁
-
二阶段回滚:
- 根据undo_log生成反向SQL
- 执行补偿操作
- 删除undo_log
5.2 全局锁机制
Seata通过全局锁防止脏写:
- 在UPDATE/DELETE前获取行锁
- 只有全局事务能修改被锁定的行
- 本地事务提交后释放锁
6. 常见问题与解决方案
6.1 事务不生效排查
-
检查点:
- 是否使用了DataSourceProxy
- @GlobalTransactional是否加在入口方法
- Feign调用是否在同一个事务上下文中
- Seata Server日志是否有异常
-
典型错误:
java复制// 错误示例:自调用导致事务失效 public void createOrder() { this.deductStock(); // 内部调用不走代理 }
6.2 性能优化建议
-
减少全局锁持有时间:
- 将全局事务拆分为多个小事务
- 避免在事务中进行远程调用
-
配置调优:
yaml复制seata: client: rm: report-retry-count: 5 table-meta-check-enable: false service: disable-global-transaction: false
7. 生产环境注意事项
-
高可用部署:
- Seata Server集群部署
- 数据库主从配置
- 定期备份undo_log表
-
监控配置:
- 集成Prometheus监控
- 设置事务超时告警
- 日志集中收集分析
-
异常处理策略:
java复制@GlobalTransactional(name = "create-order", rollbackFor = Exception.class, timeoutMills = 60000, retryTimes = 3)
8. 进阶实践技巧
8.1 混合事务模式
对于特殊场景可以组合使用:
java复制// AT模式用于订单创建
@GlobalTransactional
public void process() {
orderService.create();
// TCC模式用于积分处理
pointsFeignService.tccTry();
}
8.2 批量操作优化
处理批量插入时:
java复制// 分批处理避免锁超时
List<List<Item>> batches = Lists.partition(items, 100);
batches.forEach(batch -> {
itemMapper.batchInsert(batch);
});
8.3 跨语言支持
通过HTTP API接入非Java服务:
java复制@GlobalTransactional
public void multiLanguage() {
// Java服务
orderService.create();
// 调用Python服务
restTemplate.postForEntity("http://python-service/api", body, String.class);
}
经过多个项目的实战验证,Seata在解决分布式事务问题上的表现非常稳定。关键是要理解其工作原理,合理设计事务边界,并做好异常情况的监控处理。对于刚开始使用的团队,建议从简单的AT模式入手,逐步扩展到更复杂的场景。
