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

高冷猫
2025-06-15 06:45
阅读 218

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

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

在传统的单体应用中,我们的程序运行在一个服务器上,数据库也在本地。这种情况下,处理事务(比如转账、下单等操作)非常简单,可以用一个 BEGIN TRANSACTIONCOMMIT 就搞定了。

但现代互联网系统越来越复杂了。为了提高性能和可扩展性,人们通常把系统拆分成多个服务,每个服务各自管理自己的数据库。比如:

  • 用户服务:负责管理用户数据
  • 支付服务:负责处理支付逻辑
  • 订单服务:负责创建订单信息

当我们需要跨多个服务进行一次完整的操作时(比如下单并支付),就会遇到一个很头疼的问题:怎么保证这些操作要么全成功,要么全失败?

这就是“分布式事务”要解决的问题。

通俗理解: 如果你去商场买东西,收银员告诉你先拿货再付款。那你肯定不干对吧?我们希望整个过程是可靠的 —— 要么买到东西也完成了付款,要么什么都没发生。

同样的,在多个微服务之间,我们也希望确保数据的一致性。


环境准备:开发环境搭建指南(适合初学者)

环境准备:开发环境搭建指南(适合初学者)

在开始动手之前,我们需要准备好一些工具。

所需软件列表:

工具 用途说明
Java 17 或更高版本 编写后端服务
Maven 项目依赖管理
IntelliJ IDEA / VS Code IDE
MySQL 8+ 数据库
Nacos(服务注册中心) 微服务之间的协调
Seata(分布式事务框架) 管理事务一致性
Docker(非必须) 部署中间件

安装步骤(简化版):

第一步:安装 JDK

第二步:安装 MySQL

  • 推荐使用 XAMPP、WAMP 或直接下载 MySQL 官方安装包。
  • 启动服务后,用 Navicat 新建两个数据库:
    • order_db
    • payment_db

第三步:安装 Nacos

第四步:安装 Seata

  • 下载地址:https://github.com/seata/seata/releases
  • 解压后修改配置文件:
    • registry.conf: 设置 mode 为 nacos,并填写对应的 IP
    • 各个服务的配置文件添加 seata-client 配置
  • 运行:
    ./seata-server.sh
    

如果你嫌麻烦,也可以用 Docker 一键启动所有环境(这里就不展开讲了)。


核心概念:快速掌握关键术语(通俗解释)

以下是几个你在学习分布式事务过程中会频繁接触到的概念。

1. 分布式事务

拆分后的多个服务之间,执行一组有因果关系的操作,保证它们整体成功或失败。

2. 事务 ACID 特性

  • A(原子性):要么全做,要么都不做
  • C(一致性):事务前后数据状态保持一致
  • I(隔离性):并发执行互不影响
  • D(持久性):一旦提交就不会丢失

但在分布式系统里,ACID 是难以完全满足的。

3. CAP 原则

Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容忍性) 在一个分布式系统中,最多只能同时满足两项。

我们要根据实际业务选择侧重哪一个。

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

一种经典的分布式事务解决方案,分为两个阶段:

  • 准备阶段:协调者问所有参与者是否可以提交
  • 提交阶段:如果都同意就提交,否则回滚

缺点:同步等待时间长,协调者故障会影响全局

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

一种柔性事务模型,流程如下:

  • Try:资源预留(冻结库存、预扣款)
  • Confirm:确认执行(真正减库存、扣款)
  • Cancel:取消操作(释放库存、退回款项)

优点:性能好、支持高并发,适合电商类场景


实战项目:从头开始实现一个简单的分布式事务系统

我们来做一个小项目:用户下订单并完成支付。涉及两个独立的服务:

  • OrderService(订单服务)
  • PaymentService(支付服务)

我们将用 Seata + Spring Boot + TCC 模式 来实现。

第一步:新建 Spring Boot 项目

创建两个项目,分别命名为 order-servicepayment-service

使用 Spring Initializr 创建项目:

  • 选 Spring Web、Spring Data JPA、MySQL Driver、Nacos Discovery、Seata Starter

第二步:编写实体类 & Repository

以订单表为例:

@Entity
public class Order {
    @Id
    private String id;
    private String userId;
    private BigDecimal amount;
    private String status; // CREATED, PAID
}

Payment 类似,不做赘述。

第三步:配置 Seata 并启用服务注册发现

在 application.yml 中添加 Seata 配置:

seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_group
  service:
    vgroup-mapping:
      my_group: default

并在主类添加注解:

@EnableDiscoveryClient
@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

第四步:TCC 模式核心代码示例

假设用户下单时先检查余额是否足够,再冻结资金,接着创建订单,最后确认支付。

PaymentService 的 Try 方法:

@TwoPhaseBusinessAction(name = "deductMoney")
public boolean deductMoney(BusinessActionContext ctx, @BusinessActionContextParameter(paramName = "userId") String userId,
                           @BusinessActionContextParameter(paramName = "amount") BigDecimal amount) {
    // 冻结金额,不实际扣除
    User user = userRepository.findById(userId).get();
    if (user.getBalance().compareTo(amount) < 0) {
        return false;
    }
    user.setFrozenAmount(user.getFrozenAmount().add(amount));
    userRepository.save(user);
    return true;
}

Confirm 阶段:

@Commit
public boolean confirmDeductMoney(BusinessActionContext ctx) {
    String userId = (String) ctx.getActionContext("userId");
    BigDecimal amount = (BigDecimal) ctx.getActionContext("amount");

    User user = userRepository.findById(userId).get();
    user.setBalance(user.getBalance().subtract(amount));
    user.setFrozenAmount(user.getFrozenAmount().subtract(amount));
    userRepository.save(user);
    return true;
}

Cancel 阶段:

@Rollback
public boolean cancelDeductMoney(BusinessActionContext ctx) {
    String userId = (String) ctx.getActionContext("userId");
    BigDecimal amount = (BigDecimal) ctx.getActionContext("amount");

    User user = userRepository.findById(userId).get();
    user.setFrozenAmount(user.getFrozenAmount().subtract(amount)); // 解冻
    userRepository.save(user);
    return true;
}

第五步:调用方式加注解开启事务

在 Controller 层:

@GetMapping("/createOrder")
@GlobalTransactional
public String createOrder(@RequestParam String userId, @RequestParam BigDecimal amount) {
    String orderId = UUID.randomUUID().toString();

    // Step 1: 减少用户余额(调用 payment 服务)
    paymentFeignClient.deductMoney(userId, amount);

    // Step 2: 创建订单
    Order order = new Order();
    order.setId(orderId);
    order.setUserId(userId);
    order.setAmount(amount);
    order.setStatus("CREATED");
    orderRepository.save(order);

    return "下单成功:" + orderId;
}

当任何一个服务出错,整个流程都会回退(Cancel 触发),保证数据一致性!


常见问题与解答

Q1:为什么我的分布式事务没生效?

可能原因:

  • Seata 没有正确配置,没有连接到 TC(事务协调器)
  • 没有使用正确的注解 @GlobalTransactional
  • 数据库未使用支持的 JDBC 数据源(建议使用 Druid、Hikari)

Q2:Seata 报错说找不到 transaction group 怎么办?

答:检查 file.confapplication.yml 中的 tx-service-group 是否一致,以及注册中心是否正常运行。

Q3:TCC 的 Confirm 和 Cancel 方法没有触发?

答:确保业务方法抛出异常才会触发 Cancel,正常返回不会自动 rollback。


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

服务器部署方案-1

恭喜你完成了第一个分布式事务实战项目!接下来你可以沿着以下路径继续深入学习:

1. 学习其他分布式事务方案

  • Saga 模式:适用于长周期操作,如退款、物流调度
  • 最终一致性 + 补偿机制:结合定时任务校验数据
  • RocketMQ 事务消息:适用于异步场景下的事务处理

2. 学习 Seata 的 AT 模式

  • 不改动业务代码即可实现分布式事务
  • 通过代理数据库实现 SQL 拦截与回滚日志记录

3. 结合真实项目练手

尝试集成到你的毕业设计或者开源项目中,例如:

  • 电商系统(下单、支付、库存同步)
  • 在线教育平台(报名、课程分配、积分更新)

4. 学习分布式系统基础理论

  • Paxos、Raft 协议
  • CAP 与 BASE 理论
  • 服务降级与熔断(Hystrix、Sentinel)

结语

数据库设计模型-2

分布式事务是一个入门门槛稍高的技术点,但它是构建高可用、高性能分布式系统的基石。

本文带你一步步从零搭建了一个基于 TCC 的分布式事务系统,希望你能动手实践,加深理解。后续可以根据自身兴趣,继续探索更高级的分布式架构模式。

坚持练习,你就一定能在后端开发这条路上越走越稳 🚀


💡 温馨提示:完整代码示例可在 GitHub 搜索相关 Seata 教程项目获取。你也可以试着 fork 一份 starter-template 来加速开发。

如有任何问题,欢迎在下方留言提问 😊

评论 0

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