Spring Cloud Alibaba微服务实战:用Seata搞定订单-库存-账户的分布式事务回滚
Spring Cloud Alibaba微服务实战:Seata实现订单-库存-账户分布式事务一致性
在电商系统的典型下单场景中,用户支付成功后需要同时完成订单创建、库存扣减和账户余额变更三个操作。这三个操作分别属于不同的微服务,部署在不同的服务器上,使用独立的数据库。如何保证这三个操作要么全部成功,要么全部失败?这就是分布式事务要解决的核心问题。
传统单机事务的ACID特性在分布式环境下不再适用,我们需要引入专门的分布式事务解决方案。Seata作为Spring Cloud Alibaba生态中的重要组件,提供了高性能且易用的分布式事务支持。本文将基于一个真实的电商下单案例,深入讲解如何使用Seata的AT模式实现跨服务的事务一致性。
1. Seata核心概念与架构设计
1.1 Seata的三种事务模式
Seata支持三种分布式事务模式:
- AT模式(Auto Transaction):自动补偿型事务,基于两阶段提交实现。业务代码无需手动编写回滚逻辑,通过解析SQL自动生成反向操作。
- TCC模式:需要业务代码实现Try、Confirm、Cancel三个接口,适合对一致性要求极高的场景。
- SAGA模式:长事务解决方案,通过状态机驱动业务流程,适合业务流程长且需要最终一致性的场景。
对于大多数业务场景,AT模式已经能够满足需求,且对代码侵入性最小。本文案例将采用AT模式实现。
1.2 Seata AT模式的核心组件
Seata的架构包含三个核心组件:
| 组件名称 | 作用 |
|---|---|
| Transaction Coordinator (TC) | 事务协调器,维护全局事务的运行状态,负责协调分支事务的提交或回滚 |
| Transaction Manager (TM) | 定义全局事务边界,负责开启、提交或回滚全局事务 |
| Resource Manager (RM) | 管理分支事务处理的资源,负责分支事务注册、状态汇报和本地事务提交/回滚 |
在电商案例中:
- 业务服务作为TM,使用
@GlobalTransactional注解开启全局事务 - 订单、库存、账户服务作为RM,处理各自的分支事务
- Seata Server作为TC,协调所有事务参与者的行为
1.3 undo_log表的工作原理
AT模式的核心机制依赖于undo_log表,其关键字段如下:
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 DEFAULT CHARSET=utf8;
当RM执行业务SQL时,Seata会:
- 前置镜像查询:记录数据修改前的状态
- 执行业务SQL
- 后置镜像查询:记录数据修改后的状态
- 将前后镜像写入
undo_log表
如果事务需要回滚,Seata会根据undo_log中的记录生成反向SQL并执行,将数据恢复到事务开始前的状态。
2. 环境准备与Seata Server部署
2.1 基础环境要求
- JDK 1.8(Seata目前对高版本JDK支持不完善)
- MySQL 5.7+(案例使用MySQL 8.0)
- Nacos 1.4+ 作为配置中心和注册中心
- Spring Boot 2.3.x
- Spring Cloud Hoxton.SR12
- Spring Cloud Alibaba 2.2.6.RELEASE
2.2 Seata Server安装配置
-
从官网下载Seata Server(本文使用1.4.2版本):
wget https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.tar.gz tar -xzvf seata-server-1.4.2.tar.gz cd seata -
初始化数据库表:
- 创建
seata数据库,执行script/server/db/mysql.sql - 在每个业务数据库中添加
undo_log表(执行script/client/at/db/mysql.sql)
- 创建
-
修改
conf/registry.conf,配置使用Nacos作为注册中心:registry { type = "nacos" nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" namespace = "" cluster = "default" } } -
启动Seata Server:
sh bin/seata-server.sh -p 8091 -m file
提示:生产环境建议使用db模式存储事务日志(-m db),并配置集群部署
3. 电商案例实现
3.1 系统架构设计
案例包含四个微服务:
- business-service:业务入口,处理用户下单请求
- order-service:创建订单记录
- storage-service:扣减商品库存
- account-service:扣减用户账户余额
服务调用关系如下:
business-service → order-service → account-service
business-service → storage-service
3.2 关键代码实现
-
在business-service中定义全局事务:
@RestController public class BusinessController { @Autowired private OrderService orderService; @Autowired private StorageService storageService; @GlobalTransactional(timeoutMills = 300000, name = "create-order-tx") @PostMapping("/placeOrder") public String placeOrder(@RequestBody OrderRequest request) { // 扣减库存 storageService.deduct(request.getCommodityCode(), request.getCount()); // 创建订单 orderService.create(request.getUserId(), request.getCommodityCode(), request.getCount()); return "success"; } } -
order-service创建订单后调用account-service:
@Service public class OrderServiceImpl implements OrderService { @Autowired private AccountService accountService; @Transactional public void create(String userId, String commodityCode, int count) { // 创建订单逻辑 Order order = new Order(); order.setUserId(userId); order.setCommodityCode(commodityCode); order.setCount(count); order.setMoney(calculateAmount(commodityCode, count)); orderMapper.insert(order); // 扣减账户余额 accountService.debit(userId, order.getMoney()); } } -
account-service中模拟异常触发回滚:
@Service public class AccountServiceImpl implements AccountService { @Transactional public void debit(String userId, BigDecimal money) { // 模拟用户U002触发异常 if ("U002".equals(userId)) { throw new RuntimeException("模拟异常触发回滚"); } accountMapper.updateBalance(userId, money.negate()); } }
3.3 客户端配置
每个微服务需要添加以下配置:
-
引入依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency> -
配置application.yml:
seata: application-id: ${spring.application.name} tx-service-group: my_test_tx_group service: vgroup-mapping: my_test_tx_group: default registry: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: "" group: SEATA_GROUP config: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: "" group: SEATA_GROUP -
配置数据源代理:
@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. 事务执行过程深度解析
4.1 正常流程事务执行时序
- TM(business-service)向TC申请开启全局事务,TC生成全局事务ID(XID)
- TM将XID通过Feign调用传递到各个RM服务
- 每个RM执行本地事务前,先向TC注册分支事务
- RM执行本地事务,生成undo_log记录
- 所有分支事务执行成功后,TM通知TC提交全局事务
- TC异步通知各RM删除undo_log记录
4.2 异常回滚流程
当account-service抛出异常时:
- 异常通过Feign调用链回传到TM
- TM捕获异常后通知TC回滚全局事务
- TC查询该全局事务下的所有分支事务
- TC通知各RM执行回滚操作
- RM根据undo_log中的记录生成反向SQL并执行
- RM删除undo_log记录,回滚完成
4.3 XID传递机制
Seata通过以下方式传递XID:
-
服务间调用:通过Feign的RequestInterceptor在header中添加
TX_XIDpublic class SeataFeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { String xid = RootContext.getXID(); if (StringUtils.isNotBlank(xid)) { template.header(RootContext.KEY_XID, xid); } } } -
线程间传递:通过RootContext绑定到当前线程
// 设置XID RootContext.bind(xid); // 获取XID String xid = RootContext.getXID(); -
跨线程池传递:需要手动传递XID,或使用Seata的
@GlobalTransactional扩展点
4.4 性能优化建议
-
undo_log表优化:
- 定期清理已完成的undo_log记录
- 对大字段使用压缩存储
- 添加合适的索引
-
Seata Server配置调优:
store { mode = "db" db { datasource = "druid" dbType = "mysql" driverClassName = "com.mysql.cj.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true" user = "seata" password = "seata" minConn = 5 maxConn = 30 maxWait = 5000 } } -
事务分组隔离:
- 不同业务使用不同的事务分组
- 配置不同的Seata集群处理不同分组的事务
5. 生产环境最佳实践
5.1 高可用部署方案
-
Seata Server集群:
- 部署至少3个节点
- 使用Nacos或其它注册中心实现服务发现
- 配置相同的
seata.registry.cluster名称
-
数据库高可用:
- 使用MySQL主从复制
- 配置读写分离
- 定期备份事务日志
-
客户端配置建议:
seata: enable-auto-data-source-proxy: false # 手动配置数据源代理 disable-global-transaction: false client: rm: report-retry-count: 5 table-meta-check-enable: false report-success-enable: false tm: commit-retry-count: 5 rollback-retry-count: 5
5.2 常见问题排查
-
XID未传递:
- 检查Feign拦截器是否配置正确
- 确认服务间调用是否跳过了Feign
- 检查日志中是否有
can not found global transaction xid警告
-
分支事务未注册:
- 确认RM是否配置了
DataSourceProxy - 检查Seata Server日志是否有注册请求
- 确认
tx-service-group配置是否正确
- 确认RM是否配置了
-
undo_log不生效:
- 检查业务库中是否有
undo_log表 - 确认SQL是否被Seata解析(仅支持DML语句)
- 检查
auto-commit是否为false
- 检查业务库中是否有
5.3 监控与告警
-
Seata Server监控指标:
- 全局事务数量
- 分支事务数量
- 事务成功率
- 平均处理时间
-
Prometheus配置示例:
metrics: enabled: true registry-type: compact exporter: prometheus: enabled: true port: 9898 -
关键告警项:
- 事务失败率超过阈值
- 事务处理时间过长
- Seata Server节点不可用
在实际项目中,我们发现当业务高峰期事务量激增时,适当调整seata.server.session.enable-branch-async-remove参数可以显著提升性能。另外,对于非核心业务,可以考虑采用SAGA模式实现最终一致性,减轻系统压力。
更多推荐
所有评论(0)