当前位置: 首页 > web >正文

1 分布式事务在 Java Web 项目中的实践

好的,分布式事务在 Java Web 项目中的实践是一个非常核心且常见的问题。我会从理论到实践,为你系统地讲解常见的解决方案和具体技术实现。

1. 理解分布式事务的核心问题

在分布式系统中,一个业务操作往往需要调用多个独立的服务(例如:订单服务、库存服务、账户服务),每个服务都有自己的数据库(即资源管理器)。要保证所有服务的数据要么全部成功,要么全部失败,这就是分布式事务要解决的问题,其核心是满足 ACID 特性,尤其是原子性(Atomicity)

传统的单机数据库事务(通过 JDBC 的 commit/rollback)无法直接跨越多个数据库。


2. 主流实践方案

Java Web 项目中处理分布式事务主要有以下几种主流方案,各有其适用场景。

方案一:两阶段提交 (2PC - Two-Phase Commit) - 强一致性

这是一种重量级的解决方案,追求强一致性。

  • 角色:包含一个协调者 (Coordinator)(通常是事务管理器)和多个参与者 (Participants)(即各个微服务/资源管理器)。
  • 阶段一:准备阶段 (Prepare Phase)
    • 协调者向所有参与者发送事务内容,询问是否可以提交。
    • 每个参与者执行事务,但不提交,将 UndoRedo 信息记入事务日志。
    • 参与者回复协调者:“可以提交” (Yes) 或“不可以提交” (No)。
  • 阶段二:提交/回滚阶段 (Commit/Rollback Phase)
    • 如果所有参与者都回复 “Yes”:协调者向所有参与者发送 Commit 命令,参与者正式提交事务。
    • 如果任何一个参与者回复 “No” 或超时:协调者向所有参与者发送 Rollback 命令,参与者回滚事务。
  • Java 实现
    • JTA (Java Transaction API):Java EE 的标准 API。
    • 实现框架:如 Atomikos, Narayana
    • 使用方式:通常与 Spring 的 JtaTransactionManager 集成,使用 @Transactional 注解即可管理分布式事务。
  • 优点:强一致性,ACID 保障严格。
  • 缺点
    • 同步阻塞:在准备阶段后,所有参与者都在等待协调者的指令,资源会被锁定。
    • 性能差:通信次数多,延迟高,不适合高并发场景。
    • 协调者单点问题:协调者宕机会导致参与者一直处于不确定状态。
方案二:TCC (Try-Confirm-Cancel) - 最终一致性

这是一种补偿型的解决方案,通过业务逻辑来实现最终一致性。它将一个业务操作分成三个步骤:

  1. Try 阶段:尝试执行,完成所有业务的检查,并预留必要的业务资源。
    • 订单服务:创建一个状态为 “待确认” 的订单。
    • 库存服务:检查库存,并预扣库存(将库存锁定,而不是实际减少)。
    • 账户服务:检查账户余额,并冻结要支付的金额。
  1. Confirm 阶段:确认执行。如果所有服务的 Try 都成功了,则进入 Confirm 阶段。Confirm 使用 Try 阶段预留的资源,真正执行业务操作。
    • 订单服务:将订单状态更新为 “已确认”。
    • 库存服务:扣减预扣的库存。
    • 账户服务:扣减冻结的金额。
  1. Cancel 阶段:取消执行。如果任何一个服务的 Try 失败,则进入 Cancel 阶段,释放 Try 阶段预留的资源。
    • 订单服务:将订单状态更新为 “已取消”。
    • 库存服务:释放预扣的库存。
    • 账户服务:释放冻结的金额。
  • Java 实现
    • 框架Seata (TCC 模式), Hmily, ByteTCC
    • 使用方式:你需要为每个服务编写 try, confirm, cancel 三个接口方法,并通过框架注解(如 @TwoPhaseBusinessAction)将其关联。框架会负责调用这些方法。
  • 优点:性能比 2PC 好,保证了最终一致性。
  • 缺点
    • 代码侵入性强:需要为每个业务逻辑编写 Try/Confirm/Cancel 三个方法,开发量大。
    • 业务模型复杂:需要考虑如何预留和释放资源。
方案三:基于消息队列的最终一致性 - 最常用

这是目前互联网公司最常用的方案,利用消息队列的可靠性来实现最终一致性。其核心思想是:将分布式事务拆分成本地事务+异步消息

经典场景:下单扣库存

  1. 本地事务(订单服务)
    • 在订单服务的数据库中创建订单,状态为 “待支付”。
    • 同一个本地事务中,向消息表(与订单表在同一个数据库)插入一条记录,内容是“要发送扣减库存的消息”。或者,直接向 MQ 发送一条 “半消息”/“预备消息”(RocketMQ 支持)。
  1. 消息投递
    • 有一个定时任务扫描消息表,将消息发送给 MQ。或者,MQ 回调确认后正式投递消息(RocketMQ 事务消息机制)。
  1. 消费者(库存服务)
    • 库存服务监听 MQ,收到扣减库存的消息。
    • 执行本地事务:扣减数据库库存。
    • 如果执行成功,向 MQ 返回 ACK,消息被消费;如果失败,MQ 会重投消息(需要保证接口幂等性)。
  • Java 实现
    • MQRocketMQ(原生支持事务消息),KafkaRabbitMQ 需要结合数据库消息表自己实现。
    • 框架Spring Cloud Stream, RocketMQ-Spring-Boot-Starter
  • 优点
    • 性能高,吞吐量好。
    • 实现了业务的解耦。
    • 通用性强,适用于很多最终一致性场景。
  • 缺点
    • 只保证最终一致性,存在短暂的数据不一致窗口。
    • 需要处理消息重试和幂等性问题。
方案四:Seata 的 AT 模式 (Automatic Transaction) - 无侵入

Seata 是阿里开源的分布式事务解决方案,其 AT 模式对代码侵入性低,类似于“增强版的 2PC”。

  • 原理
    1. 一阶段
      • Seata 的 JDBC 数据源代理会拦截业务 SQL。
      • 解析 SQL,生成查询快照(before image)。
      • 执行 SQL,更新数据库。
      • 生成更新后的快照(after image)。
      • 将行锁和快照信息保存到 Seata 的全局锁表(global_table, lock_table)中。
    1. 二阶段提交
      • 如果所有分支事务成功,TM 通知 TC,TC 通知所有 RM 删除快照和锁信息,完成提交。
    1. 二阶段回滚
      • 如果有分支事务失败,TM 通知 TC,TC 通知 RM 回滚。
      • RM 比较当前数据与 after image,如果一致,用 before image 回滚数据;如果不一致,说明有脏写,需要人工介入。
  • Java 实现
    • 框架Seata
    • 使用方式
      1. 部署 Seata Server (TC)。
      2. 在每个微服务中引入 Seata Client 依赖。
      3. 配置数据源代理。
      4. 在全局事务的入口方法上添加 @GlobalTransactional 注解。
  • 优点:使用简单,代码侵入性低,性能不错。
  • 缺点
    • 需要部署和维护 Seata Server。
    • AT 模式默认读未提交隔离级别,可能存在脏读(但通过全局锁一定程度上避免)。

实践总结与选型建议

方案

一致性

性能

侵入性

复杂度

适用场景

2PC (JTA)

强一致

传统企业级应用,内部系统

TCC

最终一致

对一致性要求高,资金、交易核心场景

本地消息表

最终一致

异步场景,如订单、库存、积分

Seata AT

最终一致

希望快速上手,对一致性要求非强一致的业务

如何选择?

  1. 追求强一致性:银行、支付等金融核心系统,可考虑 TCC2PC(但后者现在较少)。
  2. 追求高可用和最终一致性:绝大多数互联网业务(如电商、社交),首选 基于消息队列的最终一致性 方案。
  3. 希望快速开发且无侵入:可以选择 Seata AT 模式,它能解决大部分分布式事务问题,且对代码影响最小。

示例代码 (Seata AT 模式)

以 Spring Cloud Alibaba + Seata 为例:

  1. 订单服务入口方法
@Service
public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate AccountFeignClient accountFeignClient; // 通过Feign调用账户服务@Override@GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class) // 开启全局事务public void createOrder(Order order) {// 1. 本地操作:创建订单orderMapper.insert(order);// 2. 远程调用:扣减账户余额ResponseEntity<String> result = accountFeignClient.decrease(order.getUserId(), order.getMoney());if (!result.getStatusCode().is2xxSuccessful()) {throw new RuntimeException("调用账户服务失败");}// 如果发生异常,全局事务会自动回滚:订单记录会被删除,账户服务的扣款也会回滚}
}
  1. 账户服务方法
@Service
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountMapper accountMapper;@Override@Transactional // 本地事务注解public void decrease(Long userId, BigDecimal money) {// 本地操作:扣减账户余额accountMapper.decrease(userId, money);// 这里如果抛出异常,会先回滚本地事务,然后通知Seata TC,TC会通知订单服务回滚}
}

只需要一个 @GlobalTransactional 注解,Seata 就会自动帮你协调订单服务和账户服务的事务,使其成为一个分布式事务。

希望这个详细的解答能帮助你在 Java Web 项目中更好地实践分布式事务!

http://www.xdnf.cn/news/20373.html

相关文章:

  • 分库分表方案中出现数据倾斜问题怎么解决
  • MySQL知识回顾总结----数据类型
  • 143. 重排链表
  • 2025.09.05 用队列实现栈 有效的括号 删除字符串中的所有相邻重复项
  • 2025高教社数学建模国赛C题 - NIPT的时点选择与胎儿的异常判定(完整参考论文)
  • MySQL数据库——事务、索引和视图
  • Linux基础指令(入门必备2.0)
  • B.50.10.06-NoSQL数据库与电商应用
  • 深度学习:CNN 模型训练中的学习率调整(基于 PyTorch)
  • nVisual从入门到精通—应用实例
  • 【51单片机8*8点阵显示箭头动画详细注释】2022-12-1
  • vim 常用快捷键汇总
  • 学习 Android (二十) 学习 OpenCV (五)
  • FastVLM-0.5B 模型解析
  • React Hooks UseCallback
  • Docker Registry 实现原理、适用场景、常用操作及搭建详解
  • CRYPT32!CryptMsgUpdate函数分析两次CRYPT32!PkiAsn1Decode的作用
  • Linux之Docker虚拟化技术(四)
  • 解决Vue Canvas组件在高DPR屏幕上的绘制偏移和区域缩放问题
  • Process Explorer 学习笔记(第三章3.2.1):主窗口与进程列表详解
  • 9.5C++作业
  • Ruoyi-vue-plus-5.x第五篇Spring框架核心技术:5.2 Spring Security集成
  • 使用PyTorch构建卷积神经网络(CNN)实现CIFAR-10图像分类
  • 1688 商品详情抓取 API 接口接入秘籍:轻松实现数据获取
  • LeetCode Hot 100 第11天
  • 微前端架构:解构前端巨石应用的艺术
  • 【Android】制造一个ANR并进行简单分析
  • Kotlin中抽象类和开放类
  • 《从报错到运行:STM32G4 工程在 Keil 中的头文件配置与调试实战》
  • CRYPT32!ASN1Dec_SignedDataWithBlobs函数分析之CRYPT32!ASN1Dec_AttributesNC的作用是得到三个证书