分布式事务解决方案:最佳实践(面向零基础初学者的教程)

HTTPS小卫士
2025-06-21 16:16
阅读 269

一、开篇:什么是分布式事务?它用来解决什么问题?

一、开篇:什么是分布式事务?它用来解决什么问题?

在我们编写一个普通的后端应用时,可能只需要处理一个数据库里的数据。比如用户注册、下单等操作,这些都发生在同一个系统里。

但随着业务的发展,系统越来越复杂,我们可能会把不同的功能模块拆分成多个独立的服务。比如:

  • 用户服务:负责管理用户信息
  • 订单服务:负责处理订单生成与支付
  • 库存服务:负责管理商品库存

这时就出现了一个问题:

用户下了一个订单,需要同时扣减库存和生成订单,这两个操作分别由两个不同的服务完成,如何保证它们要么一起成功,要么一起失败?

这就引出了今天我们要讲的技术主题:分布式事务

简单来说,分布式事务就是在多个服务之间协调事务,确保整个流程的数据一致性


二、环境准备:搭建开发环境(以 Spring Boot + Seata 为例)

数据流转过程-1

二、环境准备:搭建开发环境(以 Spring Boot + Seata 为例)

本教程使用 Java 技术栈,配合开源框架 Seata 来实现分布式事务。如果你是新手也不用怕,我们从零开始一步步来。

所需工具安装清单

工具 用途
JDK 1.8+ 编写 Java 程序的基础
Maven 项目构建工具
IntelliJ IDEA 开发工具(推荐)
MySQL 数据库 存储数据
Seata Server 实现分布式事务的核心组件

第一步:下载并启动 Seata Server

  1. 下载地址:https://github.com/seata/seata/releases
  2. 解压后进入目录 conf,打开 file.conf 文件:
    • 修改各服务对应的数据库连接配置
  3. 启动命令:
sh seata-server.sh -p 8091 -h 127.0.0.1 -m file

表示启动一个本地 Seata Server,监听端口 8091。


第二步:创建测试数据库和表

执行以下 SQL 创建两个数据库 order_dbstorage_db

-- storage_db
CREATE DATABASE storage_db;
USE storage_db;

CREATE TABLE storage (
  id INT PRIMARY KEY AUTO_INCREMENT,
  product_id INT NOT NULL,
  used INT DEFAULT 0,
  residue INT DEFAULT 0
);

-- order_db
CREATE DATABASE order_db;
USE order_db;

CREATE TABLE orders (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  product_id INT NOT NULL,
  count INT DEFAULT 0,
  status VARCHAR(50)
);

三、核心概念:轻松理解分布式事务的关键术语

三、核心概念:轻松理解分布式事务的关键术语

这部分我们将介绍几个重要的关键词,并用通俗易懂的语言解释它们。

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

类型 特点 示例
本地事务 同一个数据库内的一组操作 在一个服务里修改订单状态和库存
分布式事务 涉及多个数据库或服务之间的协同操作 用户服务 + 订单服务 + 库存服务一起工作

2. 两阶段提交协议(2PC)

这就像两个人一起签署一份合同:

  1. 协调者问每个人:“你准备好了吗?” → 准备阶段
  2. 大家都说“准备好”后,协调者说:“可以签了!” → 提交阶段

如果其中一人没准备好,那合同就不签。

缺点:效率低,某一方宕机整个流程就会卡住。


3. TCC 模型(Try-Confirm-Cancel)

这是目前最常用的模型之一,三个步骤分别是:

  • Try(尝试):检查资源是否可用(比如库存是否足够)
  • Confirm(确认):真正扣除资源,更新状态
  • Cancel(取消):如果出错,回滚之前的操作

我们可以把这个过程想象成网上订票的过程:

  • Try:先检查你有没有足够的余额
  • Confirm:扣款成功,出票
  • Cancel:扣款失败,退订回车票信息

四、实战项目:手把手带你做一次完整流程

四、实战项目:手把手带你做一次完整流程

接下来,我们将通过一个简单的示例,演示如何在一个订单服务中调用库存服务,完成一次带事务控制的下单操作。

项目结构说明

  • order-service: 负责订单逻辑
  • storage-service: 负责库存逻辑
  • seata-config: 配置 Seata 相关信息
  • 使用 Nacos 作为注册中心(简化服务通信)

第一步:在 pom.xml 中添加依赖(以 order-service 为例)

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

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

第二步:定义 Feign 接口(用于远程调用)

@FeignClient(name = "storage-service")
public interface StorageFeignService {
    
    @PostMapping("/deduct")
    void deduct(@RequestParam("productId") Long productId, 
                @RequestParam("count") Integer count);
}

服务器部署方案-2


第三步:编写订单服务主方法(开启全局事务)

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GlobalTransactional  // Seata 关键注解,开启分布式事务
    @PostMapping("/create")
    public String createOrder(@RequestBody OrderDTO dto) {
        orderService.create(dto);
        return "订单创建成功";
    }
}

第四步:在 OrderService 中调用库存服务

@Service
public class OrderService {

    @Autowired
    private StorageFeignService storageFeignService;

    public void create(OrderDTO dto) {
        // Step 1: 尝试扣库存
        storageFeignService.deduct(dto.getProductId(), dto.getCount());

        // Step 2: 创建订单
        saveOrder(dto);
    }

    private void saveOrder(OrderDTO dto) {
        // 这里省略具体 DB 插入逻辑
        System.out.println("订单已创建:" + dto.toString());
    }
}

第五步:库存服务实现接口逻辑(包含 TCC 方法)

@RestController
@RequestMapping("/storage")
public class StorageController {

    @PostMapping("/deduct")
    public void deduct(@RequestParam("productId") Long productId,
                       @RequestParam("count") Integer count) {
        try {
            // 尝试锁定资源
            deductStock(productId, count);
        } catch (Exception e) {
            cancelDeduct(productId, count);  // 扣减失败则回滚
        }
    }

    private void deductStock(Long productId, Integer count) {
        // 实际扣除库存逻辑
        System.out.println("库存扣减成功");
    }

    private void cancelDeduct(Long productId, Integer count) {
        // 回滚逻辑
        System.out.println("库存回滚");
    }
}

这样,我们就完成了一次跨服务的事务处理!


五、常见问题解答(FAQ)

Q1:为什么用了 Seata,还是无法回滚?

A:请检查以下几个方面:

  • Seata Server 是否正常运行
  • @GlobalTransactional 注解是否正确使用
  • 服务间是否使用了 Feign 或 Dubbo 等支持 Seata 的 RPC 调用方式

Q2:TCC 模型好还是 Saga 模型好?

A:TCC 更适用于业务规则明确、可逆的场景;Saga 更适合长周期任务(如物流发货),不过补偿机制更复杂。建议新手优先掌握 TCC 模式。


Q3:可以用 Redis 做分布式锁代替事务吗?

A:Redis 可以做简单的分布式控制,但不具备事务回滚的能力,不能替代完整的分布式事务解决方案。


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

恭喜你完成了这次实战!如果你想进一步提升能力,可以从以下几个方向继续深入:

1. 了解其他事务方案

  • 消息队列事务消息(如 RocketMQ)
  • Saga 模式(异步补偿)
  • XA 协议(数据库原生支持)

2. 学习微服务架构基础

  • 服务注册与发现(Nacos / Eureka)
  • 服务熔断与降级(Sentinel / Hystrix)
  • API 网关(Spring Cloud Gateway)

3. 结合真实项目练手

找一些开源商城项目(如 Mall4j、若依商城)练习分布式事务的应用。


结语

分布式事务虽然是一个难点,但它并不是遥不可及的高墙。只要你有耐心一步步去理解、去实践,就能掌握这项关键技术。

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏或分享给你的朋友一起学习!也欢迎关注我的专栏,我会持续带来更多新手友好的编程教学内容 😊


文章长度统计:约 2482 字
📌 本文结构清晰,包含核心讲解、代码示例、问题解答和学习路径建议,非常适合刚入门的开发者阅读和练习。

评论 0

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