分布式事务解决方案:最佳实践(适合零基础的新手教程)

周末写代码
2025-06-18 06:03
阅读 702

开篇:什么是分布式事务?它有什么用?

开篇:什么是分布式事务?它有什么用?

在我们开发一个网站或者应用程序时,有时候需要完成一个任务,这个任务会涉及到多个系统或服务。比如你在网上下单买了一件衣服:

  • 你的购物车信息会被更新
  • 商品库存要减1
  • 订单系统要生成订单
  • 用户的积分可能也会增加

这些操作通常是在不同的“服务”中进行的,也就是所谓的“微服务架构”。
如果这些操作中有一个失败了(比如库存减少成功但生成订单失败),就可能会导致数据混乱。比如钱扣了但没有拿到商品。

这时候,“分布式事务”就是用来解决这个问题的技术——确保所有相关系统的操作要么全部成功,要么全部不执行,保证数据的一致性。

一句话总结:分布式事务就是让多个服务一起干活,必须都干完,否则谁也别干。


环境准备:你需要装哪些东西?

数据库设计模型-1

环境准备:你需要装哪些东西?

我们要做一个简单的项目来演示如何实现分布式事务,所以先准备好开发环境。

所需工具(Windows/Mac/Linux通用):

软件 版本要求 安装说明
Java JDK ≥ 8 官网下载
Maven ≥ 3.6 官网下载
MySQL ≥ 5.7 安装指南
Spring Boot 不用手动安装,Maven会自动管理
IDE(推荐) IntelliJ IDEA 或 VS Code IDEA下载

步骤一:安装Java

如果你还不知道有没有安装Java:

java -version

如果没有输出版本号,那就去官网上下载安装一下。

步骤二:安装MySQL数据库

你可以使用命令行安装,或者直接下载图形化安装包(推荐新手用XAMPP一键安装包)。安装完成后设置好用户名和密码(通常是root / root或空密码)。

步骤三:验证Maven安装

打开终端输入:

mvn -v

如果有版本号,就表示安装成功。


核心概念讲解:分布式事务的关键词

在学习具体技术前,我们需要理解几个关键术语。

1. 本地事务 vs 分布式事务

类型 描述 示例
本地事务 在一个数据库内保证一致性 同一个数据库里的两个表操作
分布式事务 多个服务或数据库之间协同工作 库存服务 + 订单服务 + 支付服务

2. CAP定理(简单说)

CAP 是三个词的缩写:

  • C(Consistency)一致性:所有节点看到的数据是一样的。
  • A(Availability)可用性:每个请求都能得到响应。
  • P(Partition Tolerance)分区容忍性:网络不稳定也能继续运行。

⚠️ 这三点不能同时满足,只能选两个重点。大多数分布式系统都选择AP(高可用+分区容忍)。

3. 常见的分布式事务方案(先记住名字,后面详细学)

方案名称 是否强一致性 是否复杂度低 场景
两阶段提交(2PC) 少数系统间协作
三阶段提交(3PC) 用于改进2PC
最终一致性(TCC) 高并发电商场景
Saga模式 操作可逆、失败后补偿
消息队列 异步解耦、延迟容忍

缓存策略对比-2

我们这篇教程会用 TCC 模式 来做实战示例,因为它在实际中应用最广,而且适合初学者理解。


实战项目:用TCC实现一个“下单+扣库存”的例子

我们将构建两个服务:

  1. 订单服务(Order Service)
  2. 库存服务(Inventory Service)

我们要实现的功能是:

当用户下单时:

  • 先检查库存是否足够
  • 如果可以,预占库存(Try)
  • 创建订单(Confirm)
  • 减少库存(Confirm)
  • 如果失败,释放库存(Cancel)

第一步:创建Spring Boot项目

使用Spring Initializr 创建两个独立项目:

Order Service

  • 名称:order-service
  • 添加依赖:Spring Web, MyBatis, MySQL Driver

Inventory Service

  • 名称:inventory-service
  • 同样添加依赖

第二步:配置MySQL数据库

创建两个数据库:

CREATE DATABASE order_db;
CREATE DATABASE inventory_db;

然后分别建立对应的表:

订单表 (order_db)

CREATE TABLE `orders` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `product_id` INT,
  `quantity` INT,
  `status` VARCHAR(20)
);

库存表 (inventory_db)

CREATE TABLE `inventories` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `product_id` INT UNIQUE,
  `stock` INT
);

-- 插入一条初始库存
INSERT INTO inventories(product_id, stock) VALUES (1, 100);

第三步:编写业务逻辑(以TCC为例)

TCC的核心思想是三个步骤:

  1. Try(尝试):资源预留,不做真正更改
  2. Confirm(确认):确认操作,执行真实修改
  3. Cancel(取消):回滚操作,释放预留资源

我们在Spring Boot中使用 Seata 这个开源框架来简化TCC的实现。

🧠 Seata是一个非常流行的开源分布式事务中间件,支持TCC、AT等多种模式,我们这里只讲TCC。


第四步:引入Seata依赖(两个项目的pom.xml都要加)

pom.xml中加入Seata的依赖:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.6.1</version>
</dependency>

第五步:定义TCC接口

在订单服务中:

@TwoPhaseBusinessAction(name = "reduceStock")
public boolean reduceStock(@BusinessActionContextParameter(paramName = "productId") int productId,
                           @BusinessActionContextParameter(paramName = "quantity") int quantity);

在库存服务中:

@TwoPhaseBusinessAction(name = "tryReduceStock")
public boolean tryReduceStock(BusinessActionContext ctx);

@Commit
public boolean confirmReduceStock(BusinessActionContext ctx);

@Rollback
public boolean cancelReduceStock(BusinessActionContext ctx);

第六步:实现Try/Confirm/Cancel逻辑

Try方法(库存服务)

@Override
public boolean tryReduceStock(BusinessActionContext ctx) {
    Integer productId = (Integer) ctx.getActionContext("productId");
    Integer quantity = (Integer) ctx.getActionContext("quantity");
    
    // 更新状态为预扣
    return inventoryMapper.tryLock(productId, quantity);
}

Confirm方法(库存服务)

@Override
public boolean confirmReduceStock(BusinessActionContext ctx) {
    Integer productId = (Integer) ctx.getActionContext("productId");
    Integer quantity = (Integer) ctx.getActionContext("quantity");

    // 真正减库存
    return inventoryMapper.reduceStock(productId, quantity);
}

Cancel方法(库存服务)

@Override
public boolean cancelReduceStock(BusinessActionContext ctx) {
    Integer productId = (Integer) ctx.getActionContext("productId");
    Integer quantity = (Integer) ctx.getActionContext("quantity");

    // 释放锁定
    return inventoryMapper.releaseStock(productId, quantity);
}

注:这些方法中的inventoryMapper是我们对数据库的MyBatis封装类。


第七步:订单服务调用TCC接口

在订单服务中,我们通过Feign调用库存服务的Try接口:

@GlobalTransactional
public void placeOrder(int productId, int quantity) {
    inventoryService.reduceStock(productId, quantity); // Try操作

    // 创建订单
    Order order = new Order();
    order.setProductId(productId);
    order.setQuantity(quantity);
    order.setStatus("CREATED");
    orderMapper.insert(order);

    // 如果执行到这里还没异常,就会自动触发confirm
}

第八步:测试流程

正常流程:

  • 下单成功 → 预扣库存 → 创建订单 → 减库存

异常流程(例如插入订单时报错):

  • 预扣库存 → 抛出异常 → 自动触发Cancel → 释放库存

新手常见问题解答

Q1:为什么不用简单的数据库事务?

因为普通的数据库事务只能作用在一个数据库实例上,而分布式系统是多个服务、多个数据库,无法直接用传统事务控制。

Q2:TCC一定要有Confirm和Cancel吗?

是的。这是TCC的基础,缺少任何一个部分都无法保证最终一致性。

Q3:代码中出现"Branch Rollbacked"是什么意思?

这说明某个分支服务的Cancel操作被触发了,通常是发生了异常导致全局事务回滚。

Q4:Seata必须要部署吗?

是的。Seata Server作为协调者,负责管理事务的生命周期,必须单独启动。


学习建议:下一步该怎么走?

恭喜你完成了第一个分布式事务的实战项目!

接下来你可以沿着这几个方向继续深入:

推荐路线图:

  1. 进阶阅读

    • 学习其他分布式事务模型(如Saga、2PC、AT)
    • 理解CAP与BASE理论的区别
    • 阅读Seata官方文档:Seata Docs
  2. 扩展技能树

    • 学习消息队列(如RocketMQ、Kafka)用于异步通信
    • 学习服务注册发现(Nacos、Eureka)
    • 学习链路追踪(SkyWalking、Zipkin)
  3. 实战演练平台

    • GitHub 上搜索 “distributed transaction example”
    • 使用云厂商(阿里云、腾讯云)提供的分布式事务服务试用

结语:你已经入门了!

虽然分布式事务听起来很深奥,但它其实就是为了确保多个服务之间的操作能“步调一致”。

只要掌握了核心理念,再加上一些框架(比如Seata),你也能写出可靠的分布式系统!

🍀 学编程就像打怪升级,现在你已经击败了“分布式事务BOSS”,继续加油吧!


文章字数统计:约3697字
如果你觉得这篇文章对你有帮助,请点赞或收藏!欢迎留言提问,我会尽力解答。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝