分布式事务怎么搞?Spring Boot实战入门指南

Vue快乐水
2026-01-03 19:31
阅读 619

大家好,我是掘金上常写教程的全栈工程师。最近在带实习生时,发现很多同学一听到“分布式事务”就发怵,觉得是高不可攀的“大厂专属技术”。其实不然!我当初学的时候也是一头雾水,但只要拆解清楚、动手实践,你会发现它没那么神秘。今天这篇教程,就是专门写给零基础的同学——哪怕你刚学完 Spring Boot 的 Hello World,也能跟着一步步搞懂分布式事务的核心思路和最佳实践。


一、什么是分布式事务?为什么需要它?

想象一下:你在网上买了一本书,系统要同时做两件事:

  1. 扣减库存(商品服务)
  2. 生成订单(订单服务)

这两个操作分别在两个不同的微服务中完成。如果第一步成功了,第二步失败了,就会出现“库存少了但没下单”的尴尬局面——数据不一致!

分布式事务,就是要保证跨多个服务/数据库的操作,要么全部成功,要么全部回滚,就像一个原子操作。

💡 简单说:本地事务管一个数据库,分布式事务管多个数据库/服务。


二、开发环境准备(5分钟搞定)

我们用最主流的组合:Spring Boot + MySQL。虽然标题提到了 Python,但分布式事务在 Java 生态(尤其是 Spring Cloud)中更成熟,Python 多用于辅助测试或脚本。我会在文末说明 Python 的角色。

所需工具清单:

工具 版本建议 用途
JDK 17 Java 运行环境
Maven 3.8+ 项目依赖管理
MySQL 8.0 数据库
IntelliJ IDEA 最新版 开发 IDE
Python 3.8+(可选) 写测试脚本

创建两个 Spring Boot 项目

# 订单服务
spring init --dependencies=web,data-jpa,mysql order-service

# 库存服务
spring init --dependencies=web,data-jpa,mysql inventory-service

每个项目的 application.yml 配置如下(记得改数据库名):

# order-service/src/main/resources/application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=UTC
    username: root
    password: your_password
  jpa:
    hibernate:
      ddl-auto: update
server:
  port: 8081

库存服务同理,端口改为 8082,数据库名为 inventory_db


三、核心概念:三大主流方案对比

分布式事务没有银弹,但有几种经典模式。新手先掌握这三种就够了:

方案 原理 优点 缺点 适用场景
2PC(两阶段提交) 协调者统一指挥 强一致性 性能差、阻塞 传统金融系统
TCC(Try-Confirm-Cancel) 业务层面补偿 灵活、高性能 代码复杂 电商、支付
Saga 模式 事件驱动 + 补偿 易理解、适合长事务 最终一致性 订单履约、物流

🚫 避坑提醒:初学者别一上来就啃 2PC!它对数据库有强依赖,且性能瓶颈明显。TCC 和 Saga 更适合现代微服务架构


四、实战:用 TCC 模式实现下单+扣库存

我们用 TCC 模式来实现“创建订单 + 扣减库存”。关键思想是:先预留资源,再确认或取消

步骤 1:定义库存服务的 TCC 接口

inventory-service 中:

// InventoryController.java
@RestController
public class InventoryController {

    @PostMapping("/inventory/reserve")
    public ResponseEntity<String> reserve(@RequestParam Long productId, @RequestParam Integer quantity) {
        // Try 阶段:冻结库存(比如 total=100, frozen=10)
        inventoryService.freezeStock(productId, quantity);
        return ResponseEntity.ok("reserved");
    }

    @PostMapping("/inventory/confirm")
    public ResponseEntity<String> confirm(@RequestParam Long productId, @RequestParam Integer quantity) {
        // Confirm 阶段:真正扣减库存
        inventoryAssistant.confirmStock(productId, quantity);
        return ResponseEntity.ok("confirmed");
    }

    @PostMapping("/inventory/cancel")
    public ResponseEntity<String> cancel(@RequestParam Long productId, @RequestParam Integer quantity) {
        // Cancel 阶段:释放冻结库存
        inventoryService.releaseFrozenStock(productId, quantity);
        return ResponseEntity.ok("cancelled");
    }
}

步骤 2:在订单服务中编排 TCC 流程

order-service 中,我们模拟一个下单流程:

@Service
public class OrderService {

    private final RestTemplate restTemplate = new RestTemplate();

    public String createOrder(Long productId, Integer quantity) {
        try {
            // 1. Try: 预留库存
            restTemplate.postForObject(
                "http://localhost:8082/inventory/reserve?productId=" + productId + "&quantity=" + quantity,
                null, String.class
            );

            // 2. 本地创建订单(假设这里可能抛异常)
            Order order = new Order(productId, quantity);
            orderRepository.save(order);

            // 3. Confirm: 确认扣库存
            restTemplate.postForObject(
                "http://localhost:8082/inventory/confirm?productId=" + productId + "&quantity=" + quantity,
                null, String.class
            );

            return "Order created successfully";
        } catch (Exception e) {
            // 4. 出错?Cancel!
            restTemplate.postForObject(
                "http://localhost:8082/inventory/cancel?productId=" + productId + "&quantity=" + quantity,
                null, String.class
            );
            throw new RuntimeException("Order failed, rolled back", e);
        }
    }
}

这就是最简版 TCC!虽然没有用 Seata 等框架,但逻辑清晰,适合理解原理。


五、新手常见问题解答

Q1:为什么不用数据库事务直接解决?

A:因为订单和库存是两个数据库,本地事务无法跨库。MySQL 的 XA 事务(2PC)性能太差,不适合高并发场景。

Q2:TCC 写起来好麻烦,有没有简化方案?

A:有!推荐使用 Seata 框架。它通过注解自动处理 TCC 流程,比如:

@GlobalTransactional
public void createOrder() { ... }

但建议先手写一遍,再学框架,否则容易“知其然不知其所以然”。

Q3:Python 能用来做什么?

A:虽然核心服务用 Java,但你可以用 Python 写自动化测试脚本,模拟高并发下单,验证事务是否可靠:

# test_distributed_tx.py
import requests
import threading

def place_order():
    try:
        resp = requests.post("http://localhost:8081/order/create?productId=1&quantity=1")
        print(f"Result: {resp.text}")
    except Exception as e:
        print(f"Error: {e}")

# 模拟10个并发用户
threads = [threading.Thread(target=place_order) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()

六、面试题高频考点

面试官最爱问这些:

  1. “你们系统如何保证分布式事务一致性?”
    → 回答模板:“我们采用 TCC 模式,通过 Try 预留资源、Confirm 提交、Cancel 补偿,配合 Seata 框架实现。”

  2. “TCC 和 Saga 有什么区别?”
    → 关键点:TCC 是同步的(强隔离),Saga 是异步事件链(最终一致,需处理空补偿、幂等)。

  3. “如何保证 Cancel 操作一定能执行?”
    → 引入本地消息表定时对账任务,确保补偿不丢失。


七、下一步学习建议

  1. 深入框架:学习 Seata 官方文档,尝试用 @GlobalTransactional 替代手写 TCC。
  2. 理解 CAP:分布式事务本质是 CAP 理论的实践——通常选择 AP(可用性+分区容错),牺牲强一致性。
  3. 动手扩展:给你的 demo 加上 Redis 缓存、RabbitMQ 异步解耦,体验更复杂的场景。
  4. 读源码:Seata 的 AT 模式源码很清晰,适合进阶。

结语

分布式事务不是魔法,而是一套设计思维:当系统拆分后,如何用工程手段兜底数据一致性。我当初也是从手写 TCC 开始,慢慢才理解 Seata、RocketMQ 事务消息等高级方案。记住:先跑通,再优化;先理解,再封装

如果你跟着这篇教程跑通了代码,恭喜你——已经超过了 80% 只看理论不动手的人!接下来,去 GitHub 找个开源项目贡献吧,实战才是成长最快的方式。

🌟 最后彩蛋:关注我,下期教你《用 Python + FastAPI 快速搭建分布式事务的 Mock 服务》,让 Java 后端调试更轻松!

评论 0

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