分布式事务解决方案:最佳实践(零基础版)
开篇:什么是分布式事务?它用来做什么?

在我们日常开发中,常常会遇到这样一种情况:一个操作需要跨多个系统、多个数据库才能完成。比如你在网上购物,支付后要减少库存、更新订单状态、发送通知等,这些动作可能分别由不同的服务来处理。
这个时候就遇到了一个关键问题:如果其中某一步出错了怎么办?总不能让钱扣了货没发吧!
这正是分布式事务出现的原因——它要保证多个系统/服务一起完成一项完整的工作,要么全部成功,要么全部失败。
简单理解分布式事务:
想象你在餐厅点菜,服务员、厨师、收银员各司其职。分布式事务就像是确保整个流程顺利执行的“协调员”,一旦发现哪个环节出错(比如厨师做不了这道菜),就能回滚整个过程,让你重新选菜或者取消订单。
环境准备:搭建本地开发环境

为了学习和实操分布式事务,我们需要搭建如下环境:
1. 安装JDK
- 下载地址:https://www.oracle.com/java/technologies/downloads/
- 推荐版本:JDK 11 或 JDK 17
- 验证安装:
java -version
2. 安装IDEA(IntelliJ IDEA)
3. 安装Maven
- 下载地址:https://maven.apache.org/download.cgi
- 配置环境变量
- 验证:
mvn -v
4. 安装MySQL(双数据库实例模拟分布式环境)
- 使用Docker快速启动两个MySQL容器(简化操作):
docker run --name mysql1 -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql:5.7 docker run --name mysql2 -e MYSQL_ROOT_PASSWORD=123456 -p 3307:3306 -d mysql:5.7 - 这样我们就有两个MySQL实例,分别运行在
localhost:3306和localhost:3307
核心概念:通俗易懂地解释专业术语
为了理解分布式事务,先要了解几个关键概念:
1. 本地事务 vs 分布式事务
| 类型 | 特点 | 是否支持ACID |
|---|---|---|
| 本地事务 | 在一个数据库内进行 | 是 |
| 分布式事务 | 涉及多个数据库或微服务 | 否(默认情况下) |
📝 举例:本地事务就像一个人做饭;分布式事务就像几个人协作炒一桌菜。
2. CAP定理(分布式系统三选二)
CAP是分布式系统设计中的一个理论模型,三个要素不可兼得:
| 元素 | 含义 |
|---|---|
| Consistency(一致性) | 所有节点看到的数据都是一样的 |
| Availability(可用性) | 请求总是能返回结果 |
| Partition Tolerance(分区容忍) | 网络中断也能继续工作 |
✅ 实际上大多数系统选择AP(高可用+分区容忍),牺牲强一致性。我们需要找到合适的平衡点。
3. 常见的分布式事务方案(我们重点讲前三种)
| 方案名称 | 是否成熟 | 控制粒度 | 性能 | 复杂度 |
|---|---|---|---|---|
| 两阶段提交(2PC) | 成熟 | 强一致性 | 较差 | 中等 |
| TCC(Try-Confirm-Cancel) | 成熟 | 可控 | 良好 | 高 |
| Saga模式 | 成熟 | 最终一致 | 良好 | 中等 |
| 消息队列最终一致性 | 半自动 | 最终一致 | 优 | 中等 |
我们会在实战项目中重点讲解 TCC 和 Saga 两种最常用也最实用的方式。
实战项目:用TCC实现用户下单与扣款
项目目标:
模拟用户下单时同时进行“余额扣减”和“库存减少”操作,使用TCC模式保障这两个操作的事务一致性。
第一步:创建项目结构
打开IDEA,创建一个Spring Boot Maven项目,添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
第二步:配置两个数据源(连接两个MySQL)
修改 application.yml 文件:
spring:
datasource:
url1: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=UTC
username1: root
password1: 123456
url2: jdbc:mysql://localhost:3307/inventory_db?useSSL=false&serverTimezone=UTC
username2: root
password2: 123456
在Java中为每个数据源创建 DataSourceConfiguration 类,并通过注解区分DAO层调用。
第三步:定义业务逻辑接口(TCC核心)
1. Try 阶段:预检查与资源锁定
public interface OrderService {
// 尝试扣减余额 & 锁定库存
boolean prepare(String userId, String productId);
}
实现类中模拟 Try 动作(注意不真正执行最终操作):
@Override
public boolean prepare(String userId, String productId) {
// 查询余额是否足够
int balance = userMapper.getBalance(userId);
if (balance < 100) return false;
// 查询库存是否充足
int stock = inventoryMapper.getStock(productId);
if (stock <= 0) return false;
// 写入日志或标记为预留状态(不实际修改)
orderMapper.lock(userId, productId);
inventoryMapper.lock(productId);
return true;
}
2. Confirm 阶段:确认执行操作
public interface ConfirmService {
void confirm(String userId, String productId);
}
@Override
public void confirm(String userId, String productId) {
// 扣余额 + 减库存
userMapper.deduct(userId, 100);
inventoryMapper.decrease(productId, 1);
}
3. Cancel 阶段:回滚操作
public interface CancelService {
void cancel(String userId, String productId);
}
@Override
public void cancel(String userId, String productId) {
// 解锁余额和库存
userMapper.unlock(userId);
inventoryMapper.unlock(productId);
}
第四步:整合控制器入口
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private ConfirmService confirmService;
@Autowired
private CancelService cancelService;
@PostMapping("/create")
public String createOrder(@RequestParam String userId,
@RequestParam String productId) {
boolean prepared = orderService.prepare(userId, productId);
if (!prepared) {
cancelService.cancel(userId, productId);
return "下单失败";
}
confirmService.confirm(userId, productId);
return "下单成功!";
}
}
第五步:测试你的应用
你可以用Postman发起请求:
POST http://localhost:8080/order/create?userId=user1&productId=p1
如果你模拟某个步骤失败(比如库存为0),Cancel方法应该被触发,整个操作回退。
常见问题解答(FAQ)
Q1:为什么不能直接用MySQL的事务?
答: 因为MySQL事务只能控制一个数据库内的多个SQL操作,跨库就失效了。分布式环境下,不同服务各自管理自己的数据库,传统事务无法跨库控制。
Q2:TCC太复杂了,能不能更简单点?
答: 可以考虑使用消息队列+本地事务表的方式,或者使用一些现成的框架如Seata来简化开发难度。后续章节会有介绍。
Q3:什么时候用TCC,什么时候用Saga?
- TCC适合: 对事务一致性要求较高、需要精确补偿的操作。
- Saga适合: 长时间任务、可以接受最终一致性的场景(例如航班预订流程)。
学习建议:下一步怎么深入学习?
学完这个入门教程之后,建议继续学习以下几个方向:
1. 深入理解分布式事务框架
- Seata(阿里开源):支持多种模式(AT、TCC、Saga)
- Atomikos:适用于Java EE 应用程序的分布式事务管理器
2. 掌握中间件的使用
- RocketMQ / RabbitMQ:用于事件驱动和异步通信
- Redis + Lua脚本:可用于轻量级分布式锁实现
3. 学习最终一致性方案
- 消息队列 + 事务表
- 异常重试 + 补偿机制
4. 实践真实项目
尝试做一个电商平台或者金融系统的小模块,把所学知识落地实践。
结语
到此为止,你已经掌握了分布式事务的基本原理和一个非常实用的实现方式:TCC。虽然这只是分布式事务的冰山一角,但已经为你打开了通往更高阶技术世界的大门。
🚀 记住:技术的本质是解决问题,而不仅仅是写代码。带着问题去实践,才是成长的捷径。
接下来你可以尝试引入Seata框架进一步优化我们的示例工程,也可以挑战使用Saga模式重构当前逻辑。祝你学习愉快,编程快乐!
✅ 文章总计约:2767字

评论 0