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

微服务迷航
2025-06-29 00:54
阅读 223

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

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

在你开始接触后端开发时,可能已经了解过什么是“数据库事务”。简单来说,事务是为了确保多个操作要么都成功,要么都失败。比如转账操作——从A账户扣钱和向B账户加钱这两个动作如果其中一个失败,整个交易都应该取消。

但是,随着系统越来越复杂,数据可能分布在不同的服务器、不同的数据库中。这个时候我们就遇到了一个新问题:如何在一个服务调用另一个服务的时候,还能保证事务的一致性?

这,就是我们今天要讲的内容:分布式事务

举个生活中的例子:

想象你在点外卖:

  1. 下单 → 商家接单
  2. 扣减用户余额
  3. 增加商家收入
  4. 修改库存数量

这些操作可能分别属于不同的服务,有的可能在支付系统中完成,有的在订单中心,有的在库存系统。如果其中某一步失败了该怎么办?你肯定不希望扣了你的钱但订单没生成,或者库存减少了但订单也没创建。

所以,我们需要一种机制来确保所有操作整体的成功或失败 —— 这就是 分布式事务 的价值所在。


环境准备:搭建我们的实验环境

环境准备:搭建我们的实验环境

🔧 目标:准备好一个支持微服务通信 + 多数据库的 Spring Boot 开发环境

使用技术栈(推荐):

  • Java JDK 17+
  • Spring Boot 2.7 或以上
  • 数据库:
    • 用户账户表(MySQL)
    • 店铺收入表(PostgreSQL)
  • 消息中间件(RocketMQ or RabbitMQ)
  • 微服务框架(Spring Cloud Alibaba)

步骤一:安装JDK & IntelliJ IDEA(或IDEA社区版)

  1. 下载并安装 OpenJDK(Java 17+)
  2. 安装IntelliJ IDEA社区版
  3. 测试是否安装成功,在终端输入:
    java -version
    

步骤二:创建父项目(Maven)

<!-- pom.xml -->
<modules>
    <module>order-service</module>
    <module>payment-service</module>
    <module>inventory-service</module>
</modules>

每个模块对应一个微服务。

步骤三:配置数据库

用户服务(MySQL)

CREATE DATABASE user_db;
USE user_db;

CREATE TABLE `user` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(50),
  `balance` DECIMAL(10,2) DEFAULT 0
);

收入服务(PostgreSQL)

CREATE DATABASE shop_db;

CREATE TABLE income(
  id SERIAL PRIMARY KEY,
  order_id BIGINT NOT NULL,
  amount NUMERIC(10,2)
);

步骤四:集成消息队列(选型RocketMQ)

下载 RocketMQ 并启动 Broker:

unzip rocketmq-all-*.zip
cd rocketmq-*
nohup bin/mqnamesrv &
sleep 3
nohup bin/mqbroker -n localhost:9876 &

核心概念:通俗易懂解释关键术语

核心概念:通俗易懂解释关键术语

在学习分布式事务之前,我们要先理解几个关键词:

关键词 中文含义 解释说明
XA 协议 两阶段提交协议的一种实现 强一致性方案,但性能差
TCC Try-Confirm-Cancel 最流行的柔性事务模型之一
SAGA 长事务补偿机制 适用于业务逻辑复杂的场景
本地事务消息表 本地记录消息状态 确保本地操作和消息发送一致
事件驱动 发送消息驱动其他服务 常用于最终一致性设计

下面我们重点介绍最常用也最容易上手的:TCC模式


实战项目:使用 TCC 实现分布式转账

服务器部署方案-1

实战项目:使用 TCC 实现分布式转账

💡 项目目标:

我们模拟两个服务之间的资金转移:

  • 用户服务(User Service)负责扣款
  • 店铺服务(Shop Service)负责收款
  • 如果其中一个失败,要回退全部操作(例如:退款、恢复库存)

第一步:添加依赖项(以 User 服务为例)

<!-- TCC 事务管理器(Seata 可选) -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.6.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

第二步:定义 TCC 接口(ShopService)

public interface ShopTccAction {
    @TwoPhaseBusinessAction(name = "transferToShop")
    boolean prepare(BusinessActionContext ctx);

    @Commit
    boolean commit(BusinessActionContext ctx);

    @Rollback
    boolean rollback(BusinessActionContext ctx);
}

第三步:实现接口逻辑(简化示例)

@Component
public class ShopTccActionImpl implements ShopTccAction {

    @Override
    public boolean prepare(BusinessActionContext ctx) {
        // 准备接收金额
        Long orderId = (Long) ctx.getActionContext("orderId");
        Double amount = (Double) ctx.getActionContext("amount");

        // 调用 DB 操作插入预收记录,但不确认到账
        return true; // 表示 prepare 成功
    }

    @Override
    public boolean commit(BusinessActionContext ctx) {
        // 真正执行收入增加
        Long orderId = (Long) ctx.getActionContext("orderId");
        Double amount = (Double) ctx.getActionContext("amount");

        // update income set status='confirmed' where orderId=...
        return true;
    }

    @Override
    public boolean rollback(BusinessActionContext ctx) {
        // 回滚收入操作,可能是删除预收记录
        return true;
    }
}

第四步:编写主流程(UserService)

@RestController
public class UserController {

    @Autowired
    private UserService userService;
    
    @Autowired
    private ShopTccAction shopAction;

    @GetMapping("/transfer")
    public String transferMoney(@RequestParam Long userId, 
                                @RequestParam Double amount) {
        try {
            BusinessActionContext ctx = new BusinessActionContext();
            ctx.setActionContext("userId", userId);
            ctx.setActionContext("amount", amount);

            // 开始全局事务
            GlobalTransactionContext.getCurrentOrCreate().begin(60000);

            // 先扣款
            userService.deductBalance(userId, amount); // 本地事务
            
            // 再尝试增加商店收入
            if (!shopAction.prepare(ctx)) {
                throw new RuntimeException("Prepare failed");
            }

            // 提交所有事务
            GlobalTransactionContext.getCurrent().commit();

            return "转账成功";
        } catch (Exception e) {
            GlobalTransactionContext.getCurrent().rollback();
            return "转账失败:" + e.getMessage();
        }
    }
}

🧪 测试一下:

访问以下地址模拟一次转账:

http://localhost:8080/transfer?userId=1&amount=100

打开数据库查看 user 表 balance 是否减少,income 表是否有记录新增。


常见问题与解答(FAQ)

✅ Q1:什么是强一致性 vs 最终一致性?

  • 强一致性:所有操作必须同步完成,如数据库本地事务,保证实时一致。
  • 最终一致性:允许暂时不一致,但最终会达成一致状态。适合高并发、分布式系统。

✅ Q2:TCC 和 Saga 的区别?

  • TCC 是预先锁定资源,再逐步 Confirm 或 Rollback;
  • Saga 是按步骤顺序执行操作,出错时反向执行补偿动作。

✅ Q3:TCC 为什么比 XA 更好?

XA 对数据库压力大、性能差,TCC 更轻量、更灵活,可以结合业务逻辑自定义提交/回滚规则。

✅ Q4:我的微服务没有 Seata 怎么做 TCC?

你可以手动实现 TCC 的 Try、Confirm、Rollback 三个方法,不需要框架也可以。


学习建议:下一步该学什么?

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

接下来你可以继续深入的方向有:

📌 1. 进阶学习内容

  • 学习分布式事务框架:Seata / Lcn
  • 学习基于消息队列的事务处理(如 RocketMQ 本地事务消息机制)
  • 学习 Event Sourcing + CQRS 架构下的事务处理策略

📌 2. 项目推荐练习

  • 实现一个下单+支付+库存更新的完整闭环系统
  • 在 Kafka 中实现事务消息投递

📌 3. 工具推荐

  • Postman:调试 REST API 接口
  • Redis Desktop Manager:查看缓存
  • Zipkin:跟踪分布式请求链路
  • Docker + K8s:部署多个服务观察其交互过程

结语:学完之后你能做什么?

学完这篇入门教程后,你应该已经可以:

✅ 理解分布式事务的基本原理
✅ 使用 TCC 模式解决跨服务的数据一致性问题
✅ 编写带有事务保障的多服务代码
✅ 针对实际业务场景选择合适的事务解决方案

如果你是后端工程师、架构师方向的学习者,那么掌握分布式事务将是职业生涯的一个重要里程碑!


📌 附录:源码 GitHub 示例(模拟)

git clone https://github.com/example/distributed-tcc-demo.git

有问题可以在评论区留言提问哦 😊

评论 0

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