分布式事务解决方案:最佳实践(零基础版)

Prompt造梦师
2025-06-22 07:16
阅读 302

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

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

在我们日常开发中,常常会遇到这样一种情况:一个操作需要跨多个系统、多个数据库才能完成。比如你在网上购物,支付后要减少库存、更新订单状态、发送通知等,这些动作可能分别由不同的服务来处理。

这个时候就遇到了一个关键问题:如果其中某一步出错了怎么办?总不能让钱扣了货没发吧!

这正是分布式事务出现的原因——它要保证多个系统/服务一起完成一项完整的工作,要么全部成功,要么全部失败。

简单理解分布式事务:

想象你在餐厅点菜,服务员、厨师、收银员各司其职。分布式事务就像是确保整个流程顺利执行的“协调员”,一旦发现哪个环节出错(比如厨师做不了这道菜),就能回滚整个过程,让你重新选菜或者取消订单。


环境准备:搭建本地开发环境

环境准备:搭建本地开发环境

为了学习和实操分布式事务,我们需要搭建如下环境:

1. 安装JDK

2. 安装IDEA(IntelliJ IDEA)

3. 安装Maven

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:3306localhost: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

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