一、分布式事务基础概念
1.1 本地事务 vs 分布式事务
在传统单数据库应用中,我们通常使用本地事务来保证数据的一致性。本地事务的ACID特性由数据库直接支持:
java
// JDBC本地事务示例 Connection conn = dataSource.getConnection(); // 获取数据库连接 conn.setAutoCommit(false); // 开启事务 try { // 执行增删改查SQL操作 // update account set balance = balance - 100 where id = 1; // update account set balance = balance + 100 where id = 2; conn.commit(); // 提交事务 } catch (Exception e) { conn.rollback(); // 事务回滚 } finally { conn.close(); // 关闭连接 }本地事务特点:
操作单一数据库
ACID特性由数据库保证
性能高,实现简单
1.2 分布式事务的挑战
在微服务架构下,一个业务操作可能跨越多个服务、多个数据库:
text
用户下单 → 订单服务(DB1) → 库存服务(DB2) → 账户服务(DB3)
典型的分布式事务场景:
跨库事务:一个服务需要操作多个不同的数据库
分库分表:数据水平拆分到多个数据库实例
微服务调用链:多个服务协同完成一个业务操作
面临的挑战:
如何保证多个数据库操作的原子性?
如何协调不同服务的提交或回滚?
如何在高并发下保证性能?
二、两阶段提交(2PC)协议
2.1 2PC工作原理
两阶段提交协议是分布式事务的基础理论,分为两个阶段:
第一阶段:准备阶段(Prepare)
text
事务协调者(TC) → 询问 → 所有参与者(RM) 参与者执行事务操作但不提交,锁定资源 参与者回复:准备好(Yes) / 未准备好(No)
第二阶段:提交/回滚阶段(Commit/Rollback)
text
如果所有参与者都回复Yes: TC → 发送Commit → 所有RM RM提交事务,释放资源 如果有参与者回复No或超时: TC → 发送Rollback → 所有RM RM回滚事务,释放资源
2.2 2PC存在的问题
同步阻塞:参与者需要锁定资源直到第二阶段完成
单点故障:协调者故障会导致整个事务阻塞
数据不一致:第二阶段协调者崩溃可能导致部分参与者提交,部分回滚
性能问题:长时间的锁等待影响系统吞吐量
三、Seata分布式事务解决方案
3.1 Seata简介
Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务解决方案,支持多种事务模式:
AT模式(自动补偿):对业务无侵入,推荐使用
TCC模式(手动补偿):高性能,需要业务实现try/confirm/cancel
Saga模式:长事务解决方案
XA模式:基于XA协议的标准实现
3.2 Seata三大核心角色
text
┌─────────────────────────────────────────┐ │ Seata Architecture │ ├─────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ TM │────▶│ TC │ │ │ │ (事务管理器)│ │ (事务协调者)│ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ RM │◀────│ RM │ │ │ │ (资源管理器)│ │ (资源管理器)│ │ │ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────┘
TC(Transaction Coordinator):事务协调者
独立部署的Server端服务
维护全局事务和分支事务的状态
驱动全局事务提交或回滚
TM(Transaction Manager):事务管理器
嵌入在业务应用中
定义全局事务边界(开始、提交、回滚)
通过@GlobalTransactional注解标记事务方法
RM(Resource Manager):资源管理器
嵌入在业务应用中
管理分支事务的资源(数据库连接)
向TC注册分支事务并报告状态
3.3 Seata事务生命周期
text
1. TM → TC: 开启全局事务,生成XID XID: 全局唯一事务ID,在调用链中传递 2. RM → TC: 注册分支事务 将本地事务关联到全局事务XID 3. 业务执行阶段 各服务执行业务SQL,生成undo_log 4. TM → TC: 提交/回滚全局事务 根据业务结果决定事务最终状态 5. TC → RM: 驱动分支事务提交/回滚 异步执行第二阶段操作
四、Seata Server环境搭建
4.1 部署模式选择
Seata Server支持三种存储模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| file | 单机模式,数据存本地文件 | 开发测试环境 |
| db | 高可用模式,数据存数据库 | 生产环境,需要高可用 |
| redis | 高性能模式,数据存Redis | 高性能场景 |
4.2 DB模式 + Nacos部署步骤
步骤1:下载Seata Server
bash
# 从GitHub下载 wget https://github.com/seata/seata/releases/download/v1.7.0/seata-server-1.7.0.zip unzip seata-server-1.7.0.zip cd seata
步骤2:创建数据库表
执行MySQL脚本创建Seata Server所需表结构:
sql
-- 创建seata数据库 CREATE DATABASE IF NOT EXISTS seata; USE seata; -- 执行官方提供的建表脚本 -- 下载地址:https://github.com/seata/seata/blob/v1.7.0/script/server/db/mysql.sql -- 主要包含:global_table、branch_table、lock_table等
步骤3:配置Nacos注册中心
修改conf/application.yml:
yaml
seata: registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 group: SEATA_GROUP namespace: your-namespace-id cluster: default
步骤4:配置Nacos配置中心
修改
conf/application.yml配置中心设置:
yaml
seata: config: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: your-namespace-id group: SEATA_GROUP data-id: seataServer.properties
上传配置到Nacos:
bash
# 修改script/config-center/config.txt store.mode=db store.db.driverClassName=com.mysql.cj.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true store.db.user=root store.db.password=your_password # 事务分组配置 service.vgroupMapping.default_tx_group=default service.default.grouplist=127.0.0.1:8091
步骤5:启动Seata Server
bash
# Linux/Mac ./bin/seata-server.sh # Windows .\bin\seata-server.bat
访问控制台:http://localhost:7091
默认账号密码:seata/seata
五、微服务整合Seata AT模式实战
5.1 业务场景:用户下单
模拟电商下单场景,涉及三个微服务:
订单服务:创建订单记录
库存服务:扣减商品库存
账户服务:扣减用户余额
5.2 环境准备
版本兼容性
| 组件 | 版本 |
|---|---|
| Spring Boot | 3.0.2 |
| Spring Cloud | 2022.0.0 |
| Spring Cloud Alibaba | 2022.0.0.0 |
| Seata | 1.7.0 |
父POM依赖管理
xml
<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2022.0.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
5.3 微服务配置
1. 添加依赖
xml
<!-- Seata分布式事务 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency> <!-- 数据库相关 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>
2. 创建undo_log表(AT模式必需)
在每个微服务的业务数据库中执行:
sql
CREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id', `xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id', `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context, such as serialization', `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info', `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status', `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime', `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime', UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`), INDEX `ix_log_created` (`log_created`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
3. 配置application.yml
yaml
spring: application: name: order-service datasource: url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver seata: application-id: ${spring.application.name} # 事务分组,需要与Server端配置对应 tx-service-group: default_tx_group registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 group: SEATA_GROUP namespace: your-namespace-id config: type: nacos nacos: server-addr: 127.0.0.1:8848 group: SEATA_GROUP namespace: your-namespace-id data-id: seataServer.properties # 开启数据源代理(必须) enable-auto-data-source-proxy: true4. 数据源配置类
java
@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { return new DruidDataSource(); } // Seata会自动代理数据源,无需手动配置 // 注意:不能使用@Primary注解,否则会与Seata的代理冲突 }5.4 业务代码实现
1. 订单服务(事务发起者)
java
@Service @Slf4j public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private StorageFeignService storageFeignService; @Autowired private AccountFeignService accountFeignService; @Override @GlobalTransactional( name = "createOrder", // 全局事务名称 rollbackFor = Exception.class, // 回滚异常类型 timeoutMills = 30000 // 超时时间(毫秒) ) public Order saveOrder(OrderVo orderVo) { log.info("========= 用户下单 ========="); log.info("当前XID: {}", RootContext.getXID()); // 1. 创建订单(本地事务) Order order = new Order(); order.setUserId(orderVo.getUserId()); order.setCommodityCode(orderVo.getCommodityCode()); order.setCount(orderVo.getCount()); order.setMoney(orderVo.getMoney()); order.setStatus(0); // 初始状态 int saveResult = orderMapper.insert(order); log.info("保存订单{}", saveResult > 0 ? "成功" : "失败"); // 2. 扣减库存(远程调用) log.info("开始扣减库存,商品: {}, 数量: {}", orderVo.getCommodityCode(), orderVo.getCount()); ResponseResult<Void> storageResult = storageFeignService.deduct( orderVo.getCommodityCode(), orderVo.getCount() ); if (!storageResult.isSuccess()) { throw new BusinessException("扣减库存失败"); } // 3. 扣减余额(远程调用) log.info("开始扣减余额,用户: {}, 金额: {}", orderVo.getUserId(), orderVo.getMoney()); ResponseResult<Void> accountResult = accountFeignService.debit( orderVo.getUserId(), orderVo.getMoney() ); if (!accountResult.isSuccess()) { throw new BusinessException("扣减余额失败"); } // 4. 更新订单状态 order.setStatus(1); // 成功状态 orderMapper.updateById(order); log.info("订单状态更新成功,订单ID: {}", order.getId()); return order; } }2. 库存服务
java
@Service @Slf4j public class StorageServiceImpl implements StorageService { @Autowired private StorageMapper storageMapper; @Override public void deduct(String commodityCode, Integer count) { log.info("库存服务 - 扣减库存开始,XID: {}", RootContext.getXID()); // 查询当前库存 Storage storage = storageMapper.selectByCommodityCode(commodityCode); if (storage == null) { throw new BusinessException("商品不存在"); } if (storage.getCount() < count) { throw new BusinessException("库存不足"); } // 扣减库存 storage.setCount(storage.getCount() - count); storageMapper.updateById(storage); log.info("库存服务 - 扣减库存成功,商品: {}, 剩余库存: {}", commodityCode, storage.getCount()); } }3. 账户服务
java
@Service @Slf4j public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; @Override public void debit(Long userId, BigDecimal money) { log.info("账户服务 - 扣减余额开始,XID: {}", RootContext.getXID()); // 查询账户 Account account = accountMapper.selectById(userId); if (account == null) { throw new BusinessException("账户不存在"); } if (account.getBalance().compareTo(money) < 0) { throw new BusinessException("余额不足"); } // 扣减余额 account.setBalance(account.getBalance().subtract(money)); accountMapper.updateById(account); log.info("账户服务 - 扣减余额成功,用户: {}, 剩余余额: {}", userId, account.getBalance()); } }5.5 测试验证
测试1:正常下单
bash
# 正常请求 POST http://localhost:8080/order/create Content-Type: application/json { "userId": 1, "commodityCode": "P001", "count": 1, "money": 100.00 } # 预期结果:订单创建成功,库存扣减,余额扣减测试2:模拟异常回滚
java
// 在账户服务中模拟异常 @Service @Slf4j public class AccountServiceImpl implements AccountService { @Override public void debit(Long userId, BigDecimal money) { // ... 正常业务逻辑 // 模拟异常 if (money.compareTo(new BigDecimal("50")) > 0) { throw new RuntimeException("模拟余额扣减异常"); } // ... 剩余逻辑 } }预期结果:
订单服务:事务回滚,订单记录被删除
库存服务:事务回滚,库存数量恢复
账户服务:未执行扣减操作
六、Seata AT模式设计原理
6.1 AT模式核心机制
AT模式是一种无侵入的分布式事务解决方案,其核心原理:
text
第一阶段(执行阶段): 1. 执行业务SQL 2. 解析SQL,生成前镜像(before image)和后镜像(after image) 3. 将前后镜像保存到undo_log表 4. 提交本地事务,释放锁资源 第二阶段(完成阶段): - 如果全局事务提交:异步删除undo_log - 如果全局事务回滚:根据undo_log中的前镜像恢复数据
6.2 执行流程详解
第一阶段:分支事务执行
sql
-- 业务SQL示例 UPDATE product SET stock = stock - 1 WHERE id = 1 AND stock > 0; -- Seata代理后执行的操作: -- 1. 查询前镜像(before image) SELECT id, stock FROM product WHERE id = 1; -- 2. 执行业务SQL UPDATE product SET stock = stock - 1 WHERE id = 1 AND stock > 0; -- 3. 查询后镜像(after image) SELECT id, stock FROM product WHERE id = 1; -- 4. 插入undo_log INSERT INTO undo_log (xid, branch_id, rollback_info, log_status) VALUES ('全局事务XID', '分支事务ID', '前后镜像数据', 0); -- 5. 提交本地事务 COMMIT;第二阶段:全局事务完成
提交场景:
text
TC → 所有RM:提交请求 RM → 删除对应的undo_log记录 完成
回滚场景:
text
TC → 所有RM:回滚请求 RM → 查询undo_log获取前镜像 RM → 根据前镜像生成反向SQL并执行 RM → 删除undo_log记录 完成
6.3 AT模式优势
无侵入性:业务代码无需改造,只需添加注解
高性能:一阶段提交本地事务,释放锁资源
高可用:支持Seata Server集群部署
强一致性:通过undo_log保证最终数据一致性
自动补偿:回滚时自动生成反向SQL
6.4 AT模式限制
仅支持关系型数据库:MySQL、Oracle、PostgreSQL等
SQL解析限制:不支持复杂SQL(如子查询、多表关联更新)
全局锁机制:更新同一行数据时会产生全局锁竞争
undo_log表管理:需要定期清理历史数据
七、生产环境最佳实践
7.1 性能优化配置
yaml
seata: # 客户端配置 client: rm-report-success-enable: false # 是否上报成功状态,生产环境关闭 rm-table-meta-check-enable: false # 自动刷新表元数据,生产环境关闭 lock: retry-interval: 10 # 获取全局锁重试间隔(毫秒) retry-times: 30 # 获取全局锁重试次数 # 服务端配置(seataServer.properties) service: disableGlobalTransaction: false # 是否禁用全局事务 store: db: max-wait: 5000 # 连接池最大等待时间 query-limit: 100 # 查询限制 transport: thread-factory: boss-thread-prefix: NettyBoss # boss线程前缀 worker-thread-prefix: NettyServerNIOWorker server-executor-thread-prefix: NettyServerBizHandler share-boss-worker: false # 是否共享boss线程 client-selector-thread-prefix: NettyClientSelector client-selector-thread-size: 1 client-worker-thread-prefix: NettyClientWorkerThread
7.2 监控与运维
1. Seata控制台监控
访问Seata Server控制台(默认端口7091):
查看全局事务状态
监控分支事务执行情况
分析事务执行时间
2. 集成Prometheus监控
yaml
# application.yml management: endpoints: web: exposure: include: prometheus,health,info metrics: export: prometheus: enabled: true
3. 日志配置
yaml
logging: level: io.seata: DEBUG com.alibaba.cloud.seata: INFO file: name: logs/seata-client.log max-size: 50MB max-history: 30
7.3 故障排查指南
常见问题1:无法获取全局锁
现象:Could not get global lock错误
原因:多个事务同时更新同一行数据
解决方案:
优化业务逻辑,减少热点数据竞争
调整锁等待时间和重试次数
使用TCC模式替代AT模式
常见问题2:undo_log表过大
现象:磁盘空间不足,查询性能下降
解决方案:
定期清理历史undo_log
sql
-- 清理7天前的undo_log DELETE FROM undo_log WHERE log_created < DATE_SUB(NOW(), INTERVAL 7 DAY);
配置自动清理任务
java
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 public void cleanUndoLog() { // 清理逻辑 }常见问题3:XID未传递
现象:分支事务无法关联到全局事务
解决方案:
确保Feign/RestTemplate等HTTP客户端正确传递XID
检查网络拦截器是否过滤了XID头
验证微服务间的网络连通性
八、总结与展望
8.1 Seata AT模式适用场景
推荐使用场景:
业务逻辑相对简单,SQL不复杂
对性能要求较高,希望事务尽快提交
希望业务代码无侵入改造
主要使用MySQL等关系型数据库
不推荐场景:
涉及复杂SQL(多表关联、子查询)
需要处理非关系型数据库
业务对实时一致性要求极高
8.2 与其他模式对比
| 特性 | AT模式 | TCC模式 | Saga模式 |
|---|---|---|---|
| 侵入性 | 无侵入 | 需要编码 | 需要编码 |
| 性能 | 高 | 中 | 高 |
| 一致性 | 最终一致 | 强一致 | 最终一致 |
| 复杂度 | 低 | 高 | 中 |
| 适用场景 | 简单业务 | 复杂业务 | 长事务 |
8.3 学习资源推荐
官方文档:https://seata.io/zh-cn/
GitHub仓库:https://github.com/seata/seata
实战示例:https://github.com/seata/seata-samples
分布式事务是微服务架构中的关键技术点,选择合适的解决方案对系统稳定性至关重要。Seata AT模式以其无侵入性和易用性,成为大多数场景下的首选方案。