分布式事务解决方案:最佳实践(新手友好版)

代码收容所
2025-06-30 09:09
阅读 665

开篇:什么是分布式事务?为什么要学它?

开篇:什么是分布式事务?为什么要学它?

在开发大型系统时,我们经常需要操作多个数据库或服务。例如:

  • 用户下单购买商品时,可能需要同时修改订单数据库和库存数据库。
  • 银行转账时,需要从一个账户扣钱,再给另一个账户加钱。

这些操作如果其中一步失败,整个流程都应该失败,否则数据就会出错。这,就是“事务”要解决的问题

传统的单数据库应用中,我们使用 本地事务(Local Transaction),比如 SQL 中的 BEGIN TRANSACTIONCOMMIT/ROLLBACK

但在微服务架构中,各个模块独立运行、访问各自的数据源,这就需要处理跨服务的事务一致性。这种情况下,我们称之为 分布式事务(Distributed Transaction)。

分布式事务的关键点是:

  • 多个服务参与
  • 数据变更必须全部成功或者全部失败(具备 ACID 属性中的原子性和一致性)
  • 要保证数据最终一致

本教程将带你一步步了解分布式事务的核心概念,并通过代码示例实现一个简单的场景。


环境准备:搭建我们的开发环境

API接口文档-1

环境准备:搭建我们的开发环境

你需要安装以下工具(以 Java 技术栈为例):

1. JDK 17(或其他 LTS 版本)

你可以去官网下载:https://adoptium.net

验证命令:

java -version

2. Maven 构建工具

Maven 是 Java 的标准项目构建工具。

下载地址:https://maven.apache.org/download.cgi

验证命令:

mvn -v

3. Spring Boot(推荐版本 3.x)

我们将基于 Spring Boot 构建微服务。

Spring Initializr 地址:
https://start.spring.io

选择如下选项:

  • Project: Maven
  • Language: Java
  • Spring Boot Version: 最新稳定版
  • Dependencies: Spring Web, Spring Data JPA, Lombok(可选)

生成后下载项目解压即可。

4. MySQL 或 PostgreSQL 数据库

我们用两个数据库模拟两个不同的服务。

MySQL 安装指南:
https://dev.mysql.com/doc/refman/8.0/en/installing.html

创建两个数据库示例:

CREATE DATABASE order_service;
CREATE DATABASE inventory_service;

5. IDE(建议 IntelliJ IDEA 或 VS Code)


核心概念:通俗解释分布式事务相关术语

核心概念:通俗解释分布式事务相关术语

这一部分我们将用最简单易懂的语言,介绍几个核心概念。

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

类型 描述
本地事务 在单一数据库或服务内执行
分布式事务 涉及多个服务之间的数据操作协调

📝 举个例子:你在家洗衣服是本地事务;但如果你还要一边洗衣服一边煮饭,这就是分布式任务了。


2. CAP 定理(理解基本思想)

CAP 是分布式系统的经典理论之一,指的是:

  • C:Consistency(一致性)
  • A:Availability(可用性)
  • P:Partition tolerance(分区容忍)

这三个只能同时满足两个。

在设计分布式系统时,通常会牺牲一致性来换取高可用和容错能力。


3. 常见的分布式事务解决方案

以下是几种主流方案:

方案名称 适用场景 优缺点
两阶段提交(2PC) 强一致性要求高的场景 可靠但性能差,依赖中心节点(协调者)
TCC(Try-Confirm-Cancel) 对业务逻辑有一定控制力 实现复杂,适合金融类系统
Saga 模式 长周期、异步处理任务 易实现但需处理回滚逻辑
事件驱动 + 最终一致性 松耦合、高并发场景 异步处理,延迟小,但不能保证强一致
Seata(阿里开源框架) 快速上手 支持多种模式,社区活跃

我们在实战项目中将重点演示 Seata 的使用,因为它是目前企业级较为流行的开源解决方案之一。


实战项目:用 Seata 实现订单 + 库存减扣的分布式事务

我们模拟这样一个场景:

  • 用户下订单,需要操作两个服务:
    • OrderService:下单并记录订单信息
    • InventoryService:减少商品库存
  • 如果其中一个步骤失败,整个操作都要回滚

第一步:创建两个 Spring Boot 项目

1. 创建 OrderService 项目

使用 Start.Spring.IO 添加如下依赖:

  • Spring Web
  • Spring Data JPA
  • Lombok(可选)

配置 application.yml 文件连接到 order_service 数据库

2. 创建 InventoryService 项目

类似地创建第二个项目,连接到 inventory_service 数据库

第二步:引入 Seata 客户端

在两个项目的 pom.xml 中添加 Seata 依赖:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>最新版如 1.6.1</version>
</dependency>

配置 application.yml 加入 Seata 客户端配置:

seata:
  enabled: true
  application-id: order-service # 不同的服务名
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  config:
    type: file
  registry:
    type: file

第三步:启动 Seata Server(TC 组件)

Seata 的架构包括三个组件:

  • TC(Transaction Coordinator):负责协调全局事务
  • TM(Transaction Manager):发起方
  • RM(Resource Manager):参与者(每个数据库)

你可以在官方 GitHub 下载并启动 TC Server: https://github.com/seata/seata/releases

进入目录执行启动脚本:

sh seata-server.sh -p 8091 -m file

第四步:编写核心业务逻辑

1. OrderController.java(OrderService 中)

@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/order")
    public String createOrder(@RequestParam("productId") Long productId) {
        try {
            orderService.createOrder(productId);
            return "下单成功";
        } catch (Exception e) {
            return "下单失败:" + e.getMessage();
        }
    }
}

2. OrderService.java

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private InventoryClient inventoryClient;

    @GlobalTransactional // Seata 标记开启分布式事务
    public void createOrder(Long productId) {
        // 1. 创建订单
        Order order = new Order();
        order.setProductId(productId);
        order.setStatus("CREATED");
        orderRepository.save(order);

        // 2. 扣除库存(调用远程接口)
        String result = inventoryClient.decreaseInventory(productId, 1L);
        if (!"SUCCESS".equals(result)) {
            throw new RuntimeException("库存扣除失败");
        }
    }
}

3. InventoryService 提供 REST API

@RestController
@RequestMapping("/inventory")
public class InventoryController {

    @PostMapping("/decrease")
    public String decreaseInventory(@RequestParam("productId") Long productId,
                                    @RequestParam("count") Long count) {
        // 这里简化模拟实际操作
        boolean success = inventoryService.decreaseStock(productId, count);
        return success ? "SUCCESS" : "FAILURE";
    }
}

第五步:测试一下!

启动 Seata Server → 启动两个服务 → 发起请求:

POST http://localhost:8080/order?productId=1

这时如果任何一方失败(比如库存不足),整个事务都会回滚,订单不会保存。


常见问题解答

问题1:为什么我看到本地事务还在,但数据没回滚?

答:确保:

  • Seata Server 正常运行
  • @GlobalTransactional 注解已正确添加
  • 项目中引入了正确的依赖(特别是数据库代理)

问题2:我的服务没有使用数据库怎么办?

答:如果你只是远程调用外部系统,TCC 或 Saga 模式更适合。Seata 也支持此类集成。

问题3:可以不用 Seata 吗?

答:当然可以!如果你希望更轻量级的方式,可以选择:

  • 基于消息队列的事件驱动
  • 使用 RocketMQ、RabbitMQ 等结合补偿机制实现最终一致

学习建议:接下来怎么深入学习?

初学者进阶路线图:

✅ 第一阶段(基础掌握)

  • 完成本文实战项目
  • 学习 Seata 的其他模式(AT、TCC、SAGA)
  • 理解注册中心与配置中心的作用(Nacos、Eureka)

✅ 第二阶段(深度理解)

  • 学习 CAP、BASE 理论
  • 掌握 TCC 模式下的 Try、Confirm、Cancel 三种方法
  • 尝试自定义分布式事务管理器

✅ 第三阶段(生产实践)

  • 结合 Kafka/RocketMQ 实现事件驱动型事务
  • 探索分布式事务与微服务治理的融合
  • 研究 DTM、Apache ServiceCombSaga 等替代方案

总结

系统架构设计图-2

分布式事务虽然听起来很复杂,但实际上它是在帮助我们在微服务架构下安全地操作数据。本文通过通俗讲解 + 实战案例的形式,引导你初步掌握了分布式事务的基本概念和使用方式。

只要你愿意多练习,理解每种模型背后的原理,很快就能掌握这项重要技能。记住一句话:

“事务不是用来保证成功的,而是为了在失败时能够优雅回退。”

继续加油,未来你也可以轻松应对高并发、大规模系统的挑战!


🔍 延伸资源推荐:

评论 0

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