一文搞懂分布式事务:从理论到实践方案

一文搞懂分布式事务:从理论到实践方案

在分布式系统中,一个核心操作常拆分为多个小操作,分散在不同服务器、不同应用中 ------ 比如电商下单,要同时修改订单库、库存库、支付库的数据。这就需要分布式事务来保障:这些小操作要么全部成功,要么全部失败,本质是保证不同数据库的数据一致性。

本文将从理论基础(CAP、BASE)到规范(XA、JTA),再到实践方案(2PC、3PC、TCC、Seata),逐步拆解分布式事务的核心逻辑。

一、理论基础:CAP 定理与 BASE 理论

分布式事务的设计,首先要围绕 "一致性" 和 "可用性" 的平衡展开,这就离不开 CAP 和 BASE 两大理论。

1. CAP 定理:三选二的必然选择

CAP 分别代表一致性(Consistency)可用性(Availability)分区容错性(Partition Tolerance)

  • 一致性:数据分布在多个节点,某个节点更新后,其他节点能立即读到最新数据(强一致性);若读不到,则为不一致。
  • 可用性:非故障节点需在合理时间内返回正确响应,不能无响应或报错。
  • 分区容错性:网络分区(如部分节点断连)时,系统仍能正常工作。

核心结论:分布式系统必须保证 P(分区容错性是分布式的基础),因此只能在 "CP" 和 "AP" 中选其一:

  • 选 CP(强一致性) :如 ZooKeeper、MongoDB。网络分区时,为避免数据不一致,会牺牲可用性 ------ 比如节点 A 更新后无法同步到节点 B,系统会暂停服务,不让用户读到不一致数据。
  • 选 AP(高可用性) :如 12306、电商平台。网络分区时,优先保证服务可用,允许短暂数据不一致 ------ 比如用户看到的库存是旧的,但实际下单时会再次校验,避免超卖。

2. BASE 理论:AP 的 "柔性妥协"

BASE 是对 AP 的扩展,追求基本可用(Basic Availability)软状态(Soft State)最终一致性(Eventual Consistency) ,是 CAP 中 "放弃强一致性、保可用性" 的落地指导:

  • 基本可用:允许损失部分非核心服务,保证核心功能可用 ------ 比如电商大促时,关闭 "商品评价" 功能,优先保障 "下单付款"。
  • 软状态:允许系统存在中间状态(如 "订单待支付"),不影响整体可用性。
  • 最终一致性:短期内数据可能不一致,但经过一段时间(如同步延迟),所有节点数据会趋于一致。

BASE 与 ACID(数据库事务的原子性、一致性、隔离性、持久性)相反:ACID 追求强一致性,BASE 则用 "最终一致" 换可用性,解决分布式系统的网络延迟问题。

二、核心规范:XA 与 JTA

有了理论基础,分布式事务需要具体规范来定义角色和交互逻辑,最经典的是 XA 规范和 JTA API。

1. XA 规范:定义分布式事务角色与接口

XA 是 X/Open 组织制定的分布式事务模型,核心是明确 4 个角色和交互接口:

  • Application(应用) :发起事务的服务实例(如订单服务)。
  • TM(事务管理器) :协调全局事务的组件,负责通知各节点提交 / 回滚。
  • RM(资源管理器) :数据库,负责执行本地事务(如订单库、库存库)。
  • CRM(通信资源管理器) :可选,如消息中间件,用于跨服务通信。

全局事务:XA 定义的核心概念 ------ 横跨多个 RM 的事务,需保证 "一个 RM 失败,所有 RM 都回滚"。XA 的关键是:定义 TM 与 RM 的交互接口,让 TM 能统一控制所有 RM 的提交 / 回滚,具体实现由数据库厂商提供(如 MySQL 支持 XA)。

2. JTA:Java 中的分布式事务 API

JTA(Java Transaction API)是 Java 领域对 XA 规范的实现,属于 J2EE 体系:

  • 单库事务用 JDBC 事务(如Connection.commit());
  • 跨库事务用 JTA API,通过 TM 协调多个 RM,需结合 JNDI(Java 命名服务)使用。

现状:JTA 规范虽完善,但近年在分布式微服务场景中很少使用 ------ 过于依赖 J2EE 环境,灵活性不足。

三、经典方案:从 2PC 到 3PC

基于 XA 规范,最经典的分布式事务实现是 "两阶段提交(2PC)" 和 "三阶段提交(3PC)",核心是通过 "分阶段协调" 保证一致性。

1. 两阶段提交(2PC):简单但有缺陷

2PC 是 XA 的典型实现,分 "准备阶段" 和 "提交阶段":

  • 阶段 1:准备(Prepare)

TM 向所有 RM 发送 "准备请求",RM 执行本地事务(如扣减库存),写入 Undo Log(回滚日志)和 Redo Log(重做日志),但不提交;执行成功后,RM 向 TM 返回 "就绪",否则返回 "失败"。

  • 阶段 2:提交 / 回滚(Commit/Abort)

若所有 RM 都返回 "就绪",TM 发送 "提交请求",RM 提交事务并释放锁;若有 RM 返回 "失败",TM 发送 "回滚请求",RM 通过 Undo Log 回滚。

缺陷

  • 同步阻塞:RM 等待 TM 指令时,会锁定资源,无法处理其他请求,不支持高并发;
  • 单点故障:TM 宕机后,RM 会一直卡在 "就绪" 状态,无法释放资源;
  • 脑裂问题:阶段 2 中,部分 RM 收到 "提交" 指令,部分没收到,导致数据不一致;
  • 状态丢失:TM 热备时,若某 RM 已提交但 TM 宕机,新 TM 无法获知该 RM 状态。

2. 三阶段提交(3PC):优化 2PC 的缺陷

3PC 在 2PC 基础上增加 "预准备阶段" 和 "超时机制",解决部分问题:

  • 阶段 1:预准备(CanCommit)

TM 发送 "是否可提交" 请求,RM 仅检查自身状态(如网络、资源是否可用),不执行实际事务,返回 "就绪" 或 "失败"------ 此阶段无资源锁定。

  • 阶段 2:准备(PreCommit)

若所有 RM "就绪",TM 发送 "预提交请求",RM 执行事务(写日志,不提交);若有 RM "失败",TM 发送 "中止请求",事务结束。

  • 阶段 3:提交(DoCommit)

若所有 RM "预提交成功",TM 发送 "提交请求";若有 RM "失败" 或超时,TM 发送 "回滚请求"。

优化点

  • 超时机制:RM 若超时未收到 TM 指令,会自动提交(基于 CanCommit 阶段确认的 "自身可用"),避免资源长期锁定;
  • 减少阻塞:CanCommit 阶段无资源锁定,降低同步阻塞影响。

缺陷:仍未解决脑裂问题 ------ 若阶段 3 中,部分 RM 没收到 "回滚" 指令,会自动提交,导致数据不一致。

四、实战方案:TCC、Seata 与更多

2PC/3PC 依赖数据库层,灵活性不足,实际业务中更常用 TCC、可靠消息、Seata 等方案。

1. TCC:应用层的 "两阶段提交"

TCC(Try-Confirm-Cancel)是在应用层实现分布式事务,核心是为每个业务操作定义 "准备、确认、补偿" 三个接口,不依赖数据库 XA 接口。

核心逻辑(三阶段):
  • Try:业务检查 + 资源预留 ------ 如下单时,检查库存是否足够,锁定库存(不扣减)。
  • Confirm:确认业务操作 ------ 如扣减已锁定的库存,此阶段需保证 "一定成功"(失败则重试)。
  • Cancel:补偿回滚 ------ 如释放 Try 阶段锁定的库存,恢复资源。
执行流程:
  1. TM 发起所有 RM 的 Try 操作;
  1. 若所有 Try 成功,TM 发起所有 RM 的 Confirm 操作;
  1. 若任一 Try 失败,TM 发起所有 RM 的 Cancel 操作。
关键问题与解决:
  • 幂等性:用 "事务状态表" 记录状态(INIT/CONFIRMED/ROLLBACKED),重复调用时先查状态,避免重复执行;
  • 空回滚:Cancel 时查状态表,若 Try 未执行(无 INIT 记录),则不处理;
  • 资源悬挂:Cancel 执行后,若 Try 请求延迟到达,通过状态表拦截(已存在 Cancel 记录则拒绝 Try)。
适用场景:

强隔离性、短耗时业务(如支付扣款、积分兑换),但对应用侵入性强 ------ 每个业务需写 Try/Confirm/Cancel 接口。

2. 其他常见方案

方案 核心逻辑 适用场景
可靠消息最终一致 用消息中间件异步同步数据,失败则重试 耗时业务(如订单创建后发通知)
最大努力通知 异步通知 + 有限次重试,不保证 100% 成功 非核心业务(如短信提醒)
ByteTCC 框架 封装 TCC 逻辑,自动重试 Confirm,用数据库记录事务状态 简化 TCC 开发,需建专用日志表

3. Seata:开源分布式事务框架

Seata 是阿里开源的分布式事务解决方案,支持 AT、TCC、SAGA、XA 四种模式,开箱即用,是当前微服务场景的主流选择。

核心角色:
  • TC(Transaction Coordinator) :Seata 服务端,协调全局事务;
  • TM(Transaction Manager) :应用端,发起 / 结束全局事务;
  • RM(Resource Manager) :数据库端,执行本地事务,向 TC 注册分支事务。
主流模式:AT 模式(基于 2PC 的优化)

AT 模式依赖支持 ACID 的关系型数据库,无需改业务代码,核心是 "自动生成回滚日志":

  • 一阶段:RM 执行本地事务(如扣库存),同时写入 "回滚日志"(记录数据修改前的状态),一并提交;
  • 二阶段提交:TM 通知 TC 提交,TC 异步清理回滚日志,快速结束;
  • 二阶段回滚:TC 通知 RM,RM 通过回滚日志生成 "补偿操作"(如恢复库存),完成回滚。

优势:对业务无侵入,性能优于传统 2PC,适合微服务场景。

总结

分布式事务的核心是 "平衡一致性与可用性":

  • 理论上,CAP 定调 "P 必选,CP/AP 二选一",BASE 用 "最终一致" 落地 AP;
  • 规范上,XA 定义角色接口,JTA 是 Java 实现,但灵活性不足;
  • 实践上,2PC/3PC 是基础方案,TCC 适合强一致场景,Seata(AT 模式)是微服务首选,可靠消息适合异步场景。

实际开发中,需根据业务优先级(如 "支付需强一致,通知可最终一致")选择方案,避免过度追求 "强一致" 导致可用性下降。

相关推荐
小蜗牛编程实录3 小时前
深入理解网络 IO:从基础模型到多路复用技术
后端
Ekreke3 小时前
一次Nginx 403 的问题排查
后端
鸿蒙小白龙3 小时前
openharmony之分布式相机开发:预览\拍照\编辑\同步\删除\分享教程
分布式·harmonyos·鸿蒙·鸿蒙系统·open harmony
没有bug.的程序员3 小时前
电商系统分布式架构实战:从单体到微服务的演进之路
java·分布式·微服务·云原生·架构·监控体系·指标采集
绝无仅有3 小时前
面试真实经历某商银行大厂数据库MYSQL问题和答案总结(二)
后端·面试·github
绝无仅有3 小时前
通过编写修复脚本修复 Docker 启动失败(二)
后端·面试·github
老K的Java兵器库3 小时前
并发集合踩坑现场:ConcurrentHashMap size() 阻塞、HashSet 并发 add 丢数据、Queue 伪共享
java·后端·spring
冷冷的菜哥4 小时前
go邮件发送——附件与图片显示
开发语言·后端·golang·邮件发送·smtp发送邮件
向葭奔赴♡4 小时前
Spring Boot 分模块:从数据库到前端接口
数据库·spring boot·后端