分布式事务解决方案:最佳实践

产品和代码之间
2025-06-15 00:20
阅读 736

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

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

在软件开发的世界里,事务(Transaction)就像是一个“要么全做,要么全不做”的操作包。举个简单的例子:你在网上下单买东西,系统会扣掉你的余额,同时库存减少。这两步操作必须同时成功或同时失败,否则就容易出现数据不一致的问题。

那什么是分布式事务呢?想象这样一个场景:你在淘宝下单,订单服务负责创建订单,支付服务负责扣钱,库存服务负责减库存。这三个服务各自运行在不同的服务器上,甚至用的是不同的数据库。这种情况下,如何保证三个操作的一致性?这就需要使用分布式事务了。

简单来说,分布式事务就是在多个服务或系统之间协调一组操作,确保它们要么全部完成,要么全部撤销。


环境准备:搭建我们的开发环境

环境准备:搭建我们的开发环境

为了更好地学习和实践,我们需要先准备好基础开发环境。下面我们将一步步搭建:

1. 安装 JDK(Java Development Kit)

  • 推荐版本:JDK 17
  • 下载地址:https://adoptium.net/
  • 安装后在终端输入命令确认是否安装成功:
java -version

2. 安装 Spring Boot 开发工具(IntelliJ IDEA 或 VS Code + Spring Boot 插件)

推荐使用 IntelliJ IDEA 社区版下载页面

3. 安装 MySQL 数据库(用于模拟不同服务的数据存储)

4. 安装 Nacos(服务发现与配置中心)

Nacos 是阿里巴巴开源的服务管理平台,我们将用它来注册微服务。

startup.cmd -m standalone   # Windows
sh startup.sh -m standalone # Mac/Linux

访问 http://localhost:8848/nacos 进入控制台,默认账号密码是 nacos/nacos


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

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

为了帮助初学者快速理解,我们用生活中的例子来类比这些技术概念。

技术术语 通俗解释 类比生活
分布式事务 多个服务共同协作完成的一组操作 多个人一起搬沙发,要么一起抬起来,要么一起放下
本地事务 单一服务内的事务 自己写作业,错了可以撤回
全局事务 跨服务的大事务 整个公司一起做一个项目
XA 模式 强一致性事务协议 公司开会,所有人达成共识才执行下一步
TCC 模式 Try - Confirm - Cancel,分阶段处理 订酒店流程:尝试预订 → 确认入住 → 取消房间
Saga 模式 长时间运行的分布式事务,靠补偿机制恢复 退货流程:退款 → 回收货物 → 补偿运费
最终一致性 不求立即同步,但最终状态是对的 微信转账延迟几秒到账

接下来我们会结合具体的代码案例讲解最常用的几种模式。


实战项目:搭建一个简易的电商系统来演示分布式事务

实战项目:搭建一个简易的电商系统来演示分布式事务

第一步:创建两个微服务

我们将创建两个 Spring Boot 微服务:

  1. order-service(订单服务)
  2. inventory-service(库存服务)

使用 Spring Initializr 快速创建项目

访问 https://start.spring.io/

选择如下配置:

  • Project: Maven
  • Language: Java
  • Spring Boot Version: 2.7.x
  • Dependencies:
    • Spring Web
    • Spring Data JPA
    • Lombok
    • Nacos Discovery(可选)
    • Feign Client(可选)

分别下载并解压两个项目,命名为 order-serviceinventory-service


第二步:配置数据库连接

order-service 为例,在 application.yml 中添加如下配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=UTC
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver

同理,为 inventory-service 设置对应的 inventory_db


第三步:编写订单服务代码

1. 实体类 Order.java

@Entity
@Data
public class Order {
    @Id
    private String id;
    private String productId;
    private Integer quantity;
}

2. 控制器 OrderController.java

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

    @Autowired
    private OrderRepository orderRepo;

    @PostMapping
    public ResponseEntity<?> createOrder(@RequestBody Order order) {
        // 假设这里调用库存服务检查库存
        // TODO: 添加 Feign 客户端调用库存服务
        order.setId(UUID.randomUUID().toString());
        orderRepo.save(order);
        return ResponseEntity.ok("订单创建成功");
    }
}

第四步:引入 Feign 实现服务间通信

为了让订单服务能调用库存服务,我们在 order-service 的启动类加上 Feign 注解:

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

然后创建一个 Feign 客户端接口:

@FeignClient(name = "inventory-service")
public interface InventoryClient {
    @PostMapping("/inventory/decrease")
    ResponseEntity<String> decreaseInventory(@RequestParam("productId") String pid, @RequestParam("quantity") int qty);
}

第五步:实现库存服务逻辑

1. 实体类 Inventory.java

@Entity
@Data
public class Inventory {
    @Id
    private String productId;
    private Integer stock;
}

2. 控制器 InventoryController.java

@RestController
@RequestMapping("/inventory")
public class InventoryController {

    @Autowired
    private InventoryRepository inventoryRepo;

    @PostMapping("/decrease")
    public ResponseEntity<String> decreaseStock(@RequestParam String productId, @RequestParam Integer quantity) {
        Inventory inv = inventoryRepo.findById(productId).orElseThrow();
        if (inv.getStock() < quantity) {
            return ResponseEntity.status(400).body("库存不足");
        }
        inv.setStock(inv.getStock() - quantity);
        inventoryRepo.save(inv);
        return ResponseEntity.ok("库存减少成功");
    }
}

引入分布式事务框架:Seata

现在问题来了:如果订单创建成功,但库存更新失败,怎么办?这时候就需要 Seata 来帮忙了!

Seata 是阿里巴巴开源的分布式事务解决方案。

第六步:安装并启动 Seata Server

GitHub 地址:https://github.com/seata/seata/releases

下载并解压后,修改配置文件 conf/file.conf,设置两个数据库的连接信息。

运行命令启动:

sh seata-server.sh -p 8091 -m db

第七步:在项目中集成 Seata 客户端

1. 引入依赖(Spring Boot 项目 pom.xml)

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

2. 在配置文件中启用 Seata

seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_test_tx_group

3. 在订单控制器中使用 @GlobalTransactional

@PostMapping
@GlobalTransactional
public ResponseEntity<?> createOrder(@RequestBody Order order) {
    // 调用库存服务
    String result = inventoryClient.decreaseInventory(order.getProductId(), order.getQuantity());
    
    if (!result.contains("成功")) {
        throw new RuntimeException("库存不足");
    }

    order.setId(UUID.randomUUID().toString());
    orderRepo.save(order);
    return ResponseEntity.ok("订单创建成功");
}

这样,一旦出现异常,整个事务都会回滚,避免脏数据!


常见问题:新手容易遇到的问题和解答

Q1:为什么我调用另一个服务失败后没有回滚?

:请检查是否开启了 @GlobalTransactional 注解,并确保 Seata Server 正常运行。此外,确保所有服务都正确配置了 file.confregistry.conf 文件。


Q2:Seata 支持哪些数据库?

:Seata 原生支持主流的关系型数据库如 MySQL、PostgreSQL、Oracle,也支持部分 NoSQL(需自定义适配器)。


Q3:TCC 和 Saga 有什么区别?

:TCC 是一种预占资源的方式(比如先冻结金额),Saga 是一种事后补偿的方式(比如下单失败后退款)。前者更适用于高并发强一致性场景,后者更适合异步长周期操作。


Q4:我的事务总是卡住不动怎么办?

:可能是数据库死锁或网络延迟导致。建议开启日志跟踪,查看具体哪一步卡住,也可以用 SHOW ENGINE INNODB STATUS; 查看数据库内部状态。


学习建议:下一步你可以学什么?

恭喜你完成了第一个完整的分布式事务实战!接下来你可以继续深入以下方向:

✅ 掌握更多分布式事务模型

模型 特点 适用场景
XA 强一致性 银行系统
TCC 业务补偿 金融交易
Saga 流程补偿 物流系统
AT 自动代理 新零售系统

推荐阅读文档:

✅ 学习更多中间件配合使用

  • RocketMQ(消息队列)
  • Redis(缓存 + 分布式锁)
  • RabbitMQ(异步任务处理)

✅ 实践大型项目

尝试构建一个完整的电商系统,包含:

  • 用户服务
  • 商品服务
  • 库存服务
  • 订单服务
  • 支付服务

你可以部署到 Kubernetes 上,或者尝试 Serverless 架构!


结语

通过本教程,我们从零开始搭建了一个基于 Spring Boot + Seata 的分布式事务系统,学会了怎么保障多个服务之间的数据一致性。虽然分布式事务看起来复杂,但只要掌握了核心思想和常用工具,你就已经迈出了成为高级后端工程师的重要一步!

如果你觉得这篇文章对你有帮助,请记得分享给其他刚入门的朋友哦!


字数统计:约 3571 字

评论 0

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