分布式事务解决方案:最佳实践(面向初学者的教程)
开篇:什么是分布式事务?它用来做什么?

在现代互联网开发中,我们的系统往往不是单一的服务,而是由多个“微服务”组成的。比如,一个电商系统的订单、支付、库存功能可能分别运行在不同的服务器上。
问题来了: 如果用户下单时,需要同时完成“减库存”和“扣款”,但这两个操作在两个服务里,其中一个失败了怎么办?你肯定不希望钱被扣了但没发货,或者发货了却没收钱!
这个时候,我们就需要用到 分布式事务 —— 它的作用就是让多个服务的多个数据库操作“要么一起成功,要么一起失败”。
环境准备:搭建你的第一个分布式项目环境


所需工具:
- JDK 8 或以上
- Maven
- IntelliJ IDEA 或 Eclipse(推荐 IDEA)
- MySQL(至少两个数据库实例)
- Spring Boot + Dubbo + Seata(我们会用到这个开源中间件)
搭建步骤:
Step 1:安装 Java 和 Maven
下载安装好 JDK 并配置好环境变量(JAVA_HOME),然后验证命令:
java -version
mvn -v
Step 2:创建两个数据库
我们模拟两个微服务使用不同的数据库。
执行以下 SQL 创建两个数据库:
CREATE DATABASE order_db;
CREATE DATABASE stock_db;
再分别为它们创建一个表:
-- 订单表
USE order_db;
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
product_id INT,
status VARCHAR(20)
);
-- 库存表
USE stock_db;
CREATE TABLE stocks (
id INT PRIMARY KEY AUTO_INCREMENT,
product_id INT,
count INT
);
Step 3:Spring Boot 初始化
访问 https://start.spring.io,选择如下依赖:
- Spring Web
- MyBatis Framework
- MySQL Driver
- Dubbo(可以搜索
Apache Dubbo) - Lombok(可选)
下载并解压后导入 IDEA。
Step 4:集成 Seata
Seata 是一个开源的分布式事务中间件。我们需要添加它的依赖到 pom.xml 中:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
下载 Seata Server(最新版):
https://github.com/seata/seata/releases
启动 Seata Server:
cd seata-server-1.6.1
sh bin/seata-server.sh -p 8091 -m db
确保配置文件里的数据源已指向自己的数据库。
核心概念:用最简单的语言解释专业术语
我们在讲解前先搞清楚几个关键术语:
✅ 微服务架构(Microservice Architecture)
多个服务各自负责一件事,例如:
- 用户服务:处理用户相关逻辑
- 支付服务:处理付款逻辑
- 库存服务:处理商品库存减少
这些服务相互独立部署,通过网络通信(如 REST API / Dubbo)互相协作。
❗ 分布式事务(Distributed Transaction)
当一个任务要跨多个微服务完成,并且所有服务的操作必须都成功或者都失败,就叫分布式事务。
例如:用户下了一个订单,系统要做两件事:
- 扣款 → 支付服务
- 减库存 → 商品服务
这两个操作要么全部执行成功,要么都不执行,否则会出乱子。
💡 本地事务 vs 分布式事务
| 类型 | 特点 |
|---|---|
| 本地事务 | 同一数据库内部,ACID 完全支持 |
| 分布式事务 | 跨库或服务,需要特殊机制保障一致性 |
📦 Seata 是什么?
Seata 是阿里巴巴开源的一个分布式事务中间件,用于解决跨服务数据的一致性问题。它提供了三种模式(稍后我们会讲到),其中最常用的是 AT 模式(Automatic Transaction)。
实战项目:实现一个简单的分布式下单流程
现在,我们来做一个项目:用户下单时自动扣款和减库存。我们将分三个服务:
- 下单服务(order-service)—— 生成订单
- 库存服务(stock-service)—— 减库存
- 支付服务(payment-service)—— 扣款
但我们这里为了简化演示,只保留两个服务 + Seata 的 AT 模式进行事务控制。
第一步:创建模块
我们用一个 Spring Boot 项目作为主工程,分成以下几个模块:
springboot-dtx-seata/
├── order-service/ ← 下单服务
├── stock-service/ ← 库存服务
└── common-api/ ← 通用接口定义
每个模块都是一个独立的 Spring Boot 项目,并使用 Dubbo 进行远程调用。
第二步:编写 StockService
在 stock-service 中,我们写一个减库存的逻辑。
代码示例:
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
public void decreaseStock(int productId, int quantity) {
// 查看当前库存
Stocks stock = stockMapper.selectById(productId);
if (stock.getCount() < quantity) {
throw new RuntimeException("库存不足");
}
// 减库存
stock.setCount(stock.getCount() - quantity);
stockMapper.updateById(stock);
}
}
对应的 Mapper:
@Mapper
public interface StockMapper extends BaseMapper<Stocks> {
}
实体类:
@Data
public class Stocks {
private Integer id;
private Integer productId;
private Integer count;
}
第三步:编写 OrderService
@RestController
@RequestMapping("/orders")
public class OrderController {
@Reference
private StockService stockService; // Dubbo远程调用
@Autowired
private OrderMapper orderMapper;
@PostMapping
@GlobalTransactional // Seata注解
public String createOrder(@RequestParam int userId, @RequestParam int productId) {
try {
// 1. 减库存
stockService.decreaseStock(productId, 1);

// 2. 创建订单
Orders order = new Orders();
order.setUserId(userId);
order.setProductId(productId);
order.setStatus("created");
orderMapper.insert(order);
return "下单成功!";
} catch (Exception e) {
return "下单失败: " + e.getMessage();
}
}
}
注意:
@GlobalTransactional是 Seata 提供的事务注解。- 当这个方法中抛异常时,Seata 会自动回滚整个事务。
第四步:测试一下是否能回滚
我们可以故意制造一次错误,比如把库存数量改为 0,再次调用下单接口。
POST /orders?userId=1&productId=1
如果返回“库存不足”的错误,你会发现:
- 库存没有变化(因为已经回滚)
- 订单也没插入数据库
✅ 成功!
常见问题:新手最容易遇到的问题及解决方案
❓Q1:调用服务时报错 “Dubbo service not found”
原因:
- Dubbo 服务提供方未启动
- 注册中心(Zookeeper/Nacos)未正常运行
解决办法:
- 检查服务端是否已启动
- 使用命令
telnet localhost 2181看 Nacos/ZK 是否正常运行 - 在配置中检查
dubbo.registry.address=zookeeper://...是否正确
❓Q2:Seata 注解不起作用,没有回滚?
常见情况:
@GlobalTransactional写在普通私有方法或非 controller 上- 数据库表没有 undo_log 表(这是 Seata 必须的)
解决办法:
- 保证
@GlobalTransactional加在外部调用的方法上,比如 Controller 的入口函数。 - 在每个业务数据库中执行如下语句:
-- 创建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,
`ext` VARCHAR(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
❓Q3:事务不生效,数据被修改了一部分?
可能性:
- 没有使用 Seata 的 AT 模式,或未正确拦截 SQL
- 异常被捕获并吞掉,导致事务没有回滚
建议:
- 方法内尽量不要捕获异常,交由全局事务管理器处理
- 使用 try-catch 包裹时重新抛出异常
try {
someMethod();
} catch (Exception e) {
throw new RuntimeException(e);
}
学习建议:下一步你可以学什么?
恭喜你完成了本节内容!接下来可以深入学习以下内容:
🔍 继续了解 Seata 其他模式:
| 模式 | 特点说明 |
|---|---|
| AT模式 | 自动拦截SQL,开发者无感知(推荐) |
| TCC模式 | 需要手动定义 Confirm / Cancel 方法(更灵活) |
| Saga模式 | 适合长流程任务(如物流+支付+通知) |
🧠 深入理解 CAP 理论 和 BASE 思想
- CAP 理论解释了分布式系统中一致性和可用性的矛盾。
- BASE 是一种弱一致性指导原则(基本可用、柔性状态、最终一致)
🚀 学习其他分布式事务框架:
- Atomikos:本地 JTA 实现
- LCN:国内老框架,已被淘汰(仅供参考)
- Nacos + RocketMQ Dledger + Seata 构建完整生态链
结语:坚持练习,你就离大厂不远啦!
分布式事务是微服务开发中最核心的难点之一。这篇文章带你一步步从零开始搭建一个支持分布式事务的小项目。虽然过程有点复杂,但只要跟着步骤一步步走,你一定能掌握!
🎯 小提醒:学习过程中一定要动手写代码,不要光看文档。有问题随时查看官方文档或搜索社区文章,也可以留言提问哦!
如果你觉得这篇文章对你有帮助,请点赞转发让更多小伙伴看到,我们一起进步!🚀

评论 0