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

分布式事务相关

三阶段提交协议(3PC)及其和二阶段提交(2PC)的区别

好的,我来详细解释一下三阶段提交协议(3PC)及其和二阶段提交(2PC)的区别:


🔹背景

2PC(二阶段提交)是分布式事务中最经典的协议,分为准备阶段(Prepare)提交阶段(Commit)

  1. 准备阶段:协调者(Coordinator)让参与者(Participant)预执行事务并“准备提交”
  2. 提交阶段:协调者根据参与者反馈决定提交还是回滚

缺点:

  • 阻塞问题严重:一旦协调者崩溃,参与者可能一直卡住。
  • 单点故障:协调者是单点。
  • 极端网络情况可能导致数据不一致

3PC是对2PC的优化,将“准备阶段”细化为两个阶段,并加入超时机制,试图减少阻塞。


🔹3PC的三个阶段

  1. CanCommit(询问阶段)

    • 协调者向所有参与者发送“能否提交?”的询问。
    • 参与者检查是否可以执行事务(资源是否可用、逻辑是否正确),并返回“可以”或“否”。
    • 超时:若协调者或参与者超时未响应,则直接回滚。
  2. PreCommit(准备阶段)

    • 若所有参与者在询问阶段都答“可以”,协调者发送“准备提交”请求。
    • 参与者执行事务的预提交(如写WAL日志、锁资源),但不真正提交,并返回“OK”。
    • 此时若协调者超时,参与者会根据超时策略选择提交或回滚,减少阻塞。
  3. DoCommit(提交阶段)

    • 协调者收集到所有“OK”后发送“提交”请求。
    • 参与者正式提交事务;如果协调者要求回滚,则回滚事务。

🔹优化点

  • 阶段更多,状态更细:参与者在每个阶段都有明确状态(初始、准备、可提交等),更容易根据超时策略自救。
  • 引入超时机制:超时后参与者可自主选择提交或回滚,降低死锁和阻塞。
  • 减少阻塞时间:比2PC更“非阻塞”,并发性能更高。

🔹不足

  • 不能完全避免数据不一致:在极端网络分区下,参与者可能对“超时”做出不同策略,有的提交有的回滚。
  • 协议复杂度更高:实现成本增加。
  • 适用场景受限:适合并发性能要求高、数据一致性要求相对低的系统。

🔹总结对比

特性2PC3PC
阶段数2阶段(准备+提交)3阶段(询问+准备+提交)
是否阻塞容易阻塞降低阻塞概率
超时机制
数据一致性较强较弱(极端情况下不一致)
复杂度较低较高
适用场景强一致性系统高并发、弱一致性容忍系统

💡一句话概括:
3PC是在2PC的基础上引入一个询问阶段和超时机制,让参与者更智能地“自救”,减少阻塞和单点故障风险,但无法完全保证强一致性。

参与者执行事务的预提交(如写WAL日志、锁资源),但不真正提交,并返回“OK”。—— 这里2PC在准备阶段也是这样做的吗?

是的,2PC 的 准备阶段(Prepare Phase) 和 3PC 的 准备阶段(PreCommit Phase) 在这一点上是类似的:

2PC 的准备阶段:

  • 协调者发“prepare”给参与者

  • 参与者会执行:

    • 写WAL日志(记录事务操作)
    • 锁住相关资源(防止并发修改)
    • 检查是否可以提交
  • 然后返回“我准备好了(OK/Yes)”

  • 不会真正提交事务,只是“预提交”状态,等待协调者最后的“commit”或“abort”。

也就是说:
✅ 2PC 的 Prepare 阶段本质就是“预提交”,只是2PC没有把“询问”拆出来,也没有引入超时机制。


🔍 核心区别:

协议阶段说明
2PCPrepare直接进入预提交,参与者执行WAL和锁资源,然后等待协调者
3PCCanCommit多了个询问阶段,只问“能不能提交”,还没做实际预提交动作
3PCPreCommit真正执行预提交动作,和2PC的Prepare阶段类似

💡可以这么理解:

  • 2PC:问和做预提交一步到位,参与者一旦回复OK就进入阻塞状态,必须等协调者。
  • 3PC:先“纯问”,确认大家都说OK后才做预提交,还加入超时机制,减少长时间阻塞。

TCC 分布式事务模型

好,这里说的 TCC 分布式事务模型 是一种非常灵活的“业务层事务”解决方案,我给你详细拆一下:


🔹TCC 的核心思想

TCC 全称 Try-Confirm-Cancel,是一种基于业务逻辑的“最终一致性”方案:

  • 将一个分布式事务拆分为三步:

    1. Try(预留资源阶段):检查并预留业务资源,但不真正生效。例如:冻结库存、冻结账户余额。
    2. Confirm(确认提交阶段):如果事务成功,确认并真正生效资源。例如:扣减库存、扣款到账。
    3. Cancel(取消回滚阶段):如果事务失败或超时,取消操作、释放资源。例如:解冻库存、退款到账。

🔹执行流程

  1. 事务开始
    调用方发起全局事务,协调器控制整个事务流程。

  2. Try 阶段(资源预留)
    每个服务实现 Try 接口:

    • 校验资源是否充足
    • 锁定或冻结资源
    • 写业务日志
      不真正扣减/提交
  3. Confirm 阶段(提交)
    如果所有服务的 Try 都成功,协调器调用各服务的 Confirm:

    • 执行真正的业务操作(扣款/扣库存)
    • 资源状态变为“已消耗”
  4. Cancel 阶段(回滚)
    如果某个 Try 失败或全局事务失败,调用 Cancel:

    • 释放冻结资源
    • 回滚业务状态

🔹特点

高性能:因为只在 Try 阶段短暂冻结资源,减少了长时间持锁或数据库阻塞。
高灵活性:逻辑在应用层实现,可以针对业务场景做细粒度优化。
⚠️ 开发复杂度高:每个服务要实现 Try/Confirm/Cancel 三个接口,还要考虑网络异常、幂等性、悬挂事务、补偿重试等问题。
⚠️ 一致性保证难度大:是“最终一致性”,并非强一致,需要做好补偿逻辑。


🔹适用场景

  • 电商订单系统:下单时先冻结库存(Try),支付成功后扣减库存(Confirm),支付失败就释放库存(Cancel)。
  • 账户资金转账:先冻结账户余额,确认后扣款,不成功就退回余额。
  • 酒店预订、机票预订:先占座或冻结房间,再确认出票或取消。

🔹和 2PC/3PC 对比

特性2PC/3PC(数据库层事务)TCC(应用层事务)
实现位置依赖数据库事务和协调器业务逻辑层,自行实现
一致性强一致或弱一致最终一致
性能较低(事务锁持久)高(资源冻结时间短)
灵活性高(完全业务定制)
复杂度中等高,需要实现补偿、异常重试等
场景银行账务、金融交易电商、库存、支付、机票预订等高并发场景

💡一句话总结:
TCC 是一种“业务层实现的分布式事务”方案,通过 Try 冻结资源、Confirm 提交、Cancel 回滚,减少资源占用时间,性能更好,但开发成本和复杂度高。

TCC 分布式事务模型中Confirm和Cancel,不就是2PC中的提交阶段吗?2PC中提交阶段失败了也会回滚呀?这里有什么区别

好问题!看起来 TCC 的 Confirm/Cancel2PC 的 Commit/Abort 都是“提交或回滚”,但两者的 设计理念、执行机制和保障的粒度 完全不同。
我们来逐步对比:


🔍1. 定义和角色层面区别

对比点2PC Commit/AbortTCC Confirm/Cancel
实现层数据库层事务协议业务应用层逻辑
谁来执行数据库事务协调器控制提交或回滚每个微服务/业务接口自己实现Confirm/Cancel方法
粒度SQL级、数据库资源锁业务资源级,比如冻结库存、扣减余额
一致性模型尽量保证强一致最终一致

🔍2. 行为逻辑差异

🔸2PC 提交阶段:
  • 协调者在 Prepare 阶段 已经让所有数据库写了 redo log 并锁定资源。
  • Commit 阶段:协调者只需发“commit”指令,各节点提交 redo log 中的操作。
  • 如果 Commit 消息丢失或失败,节点仍会按照日志恢复,数据库层保证事务原子性
  • 关键点:2PC 是完全依赖数据库事务的ACID特性实现的,业务逻辑透明。
🔸TCC 的 Confirm/Cancel:
  • Try 阶段只是业务预留资源,比如冻结库存、预扣余额,但没真正提交。
  • Confirm:业务方自己实现“正式扣减库存、扣款”逻辑。
  • Cancel:业务方自己实现“解冻库存、退回余额”的补偿逻辑。
  • 没有数据库自动的原子性,一切一致性保障靠业务幂等、补偿和重试实现

🔍3. 失败处理方式的本质区别

场景2PC Commit失败TCC Confirm失败
处理方式数据库日志恢复,协调器可重试commit,最终提交成功需要业务层实现Confirm幂等性和重试逻辑,自己保证成功
Rollback数据库rollback操作自动回滚Cancel逻辑需要开发者自己写“解冻/补偿”代码
  • 2PC 的失败恢复是数据库自动做的,程序几乎不用操心。
  • TCC 的失败恢复完全靠业务代码,复杂度高很多。

🔍4. 哲学上的区别

  • 2PC 是“资源层面的事务协议”,通过数据库的日志和锁来实现严格的ACID特性,是“强一致”的分布式事务。
  • TCC 是“业务层面的事务设计模式”,通过业务逻辑上的冻结、确认、补偿来达到最终一致性,性能高,灵活性强,但复杂度高。

可以理解为:

  • 2PC 是“黑盒”数据库协议,开发者只需用事务API。
  • TCC 是“白盒”模式,开发者要显式设计 Try/Confirm/Cancel 三个动作,逻辑可控、性能高,但复杂度也高。

🔑核心对比总结表

特性2PCTCC
实现层数据库/中间件协议应用业务层
资源锁定时间长(锁资源直到commit)短(Try只是冻结资源,不长时间持锁)
一致性强一致最终一致
性能较低(锁+日志)高(细粒度控制资源)
失败恢复数据库日志自动保障业务逻辑自实现补偿/重试
灵活性高(可定制业务补偿策略)
开发成本

💡一句话总结:
TCC 是应用层显式设计的“软事务”,Confirm/Cancel是你写的业务逻辑补偿,而2PC的Commit/Rollback是数据库协议自动保障ACID,两者本质不一样:2PC靠数据库日志恢复,TCC靠业务幂等和补偿重试,最终一致性、性能高、灵活度高。

比如冻结库存、预扣余额,但没真正提交——这里有没有去操作数据库?还是业务层进行操作,那也必然需要扣减缓存等,对资源进行扣减

好问题!TCC 的 Try 阶段确实是要操作数据库或其他资源的,只是它不会“真正提交”业务,而是做“可回滚”的资源预留,这和数据库事务里的锁资源不完全一样。我们细分一下:


🔍Try 阶段做了什么?

在 TCC 中,Try 阶段是业务逻辑级别的资源预留操作,它会去:

  1. 检查业务约束

    • 判断库存是否足够
    • 判断账户余额是否足够
  2. 预留资源(冻结/占用)

    • 库存场景:更新库存表,把库存标记为“已冻结”而不是直接扣减
    • 余额场景:更新账户表,冻结余额字段,而不是直接减掉余额
  3. 记录事务状态

    • 写一条事务记录(log),记录当前事务的 Try 状态,为后续 Confirm/Cancel 做准备。

⚠️注意:这里确实要操作数据库(或者缓存),只是不会真正改变业务的“最终可见状态”,而是一个**“冻结”状态**。


🔍为什么不是简单在缓存里扣?

  • TCC 的一致性要保证“最终一致”,必须有可靠存储(数据库)记录冻结资源,否则宕机或网络问题时,缓存丢失就无法恢复。
  • 缓存通常是用来加速查询或辅助状态管理,但冻结、预扣这种核心逻辑必须落数据库,以便后续 Confirm 或 Cancel 时能准确恢复。

🔍数据库层面 vs 业务逻辑层面区别

特性传统2PC数据库锁TCC的Try冻结
锁方式数据库事务行级锁,持锁直到commit修改状态字段(冻结/预留),不持锁
数据状态事务内不可见状态是可见的,但标记为冻结
持锁时间长,影响并发短,锁立即释放,提升并发
宕机恢复DB日志自动回滚依赖业务记录和补偿逻辑

TCC 通过状态标记代替锁,实现高并发。


🔍一个库存冻结例子

库存表字段:

product_id | total_stock | frozen_stock | available_stock
--------------------------------------------------------
1001       | 100         | 0            | 100

Try 阶段操作:

  1. 检查 available_stock >= 10
  2. 更新 frozen_stock += 10, available_stock -= 10
  3. 写事务日志 status=TRY

此时库存“被占用”,但并未真正减少总库存。

Confirm 阶段:

  • 将冻结的库存扣成正式消耗:total_stock -= 10, frozen_stock -= 10

Cancel 阶段:

  • 将冻结库存释放:frozen_stock -= 10, available_stock += 10

✅ 所以答案是:

  • Try阶段确实要操作数据库或其他资源,只是不会直接提交结果,而是做“冻结”状态的记录;
  • 缓存可以辅助,但核心冻结逻辑必须落库,否则系统崩溃时无法保证一致性。

那么2PC有必要存在吗?TCC性能明显比它要高,难道是因为时效性需求吗?用户要当时看到是否成功与否?比如转账,当时收到账就要用这些钱?

这个问题问得特别有深度!👍
你已经捕捉到一个关键点:为什么还有2PC这种“重”协议存在,既然TCC性能更高、灵活性更好?
答案确实和时效性、数据一致性等级、实现成本、业务复杂度等多个维度相关。
我们从几个角度深入分析:


🔍1. 核心定位不同:2PC是“强一致”,TCC是“最终一致”

特性2PCTCC
一致性保证强一致性(ACID,提交后立即一致)最终一致性(有短暂不一致)
事务透明度开发者几乎无感,交给数据库协调器业务层显式实现 Try/Confirm/Cancel
成本高:性能差、锁资源久、事务日志多高:开发复杂、逻辑重试、补偿机制
使用场景核心金融、清算系统、对一致性要求极高电商、支付、库存等高并发业务
用户体验提交后立刻一致允许短暂延迟一致(最终一致)

关键区别:

  • 2PC下,用户转账A→B,提交事务后A的钱已经扣,B的钱已经到账,即刻可用。
  • TCC下,如果设计不当,可能出现“B的钱显示到账了,但暂时冻结不能用”,直到最终确认才真正可用。

所以2PC是“银行核心账务系统”的那种绝对一致性,TCC是“高并发场景下的最终一致”。


🔍2. 性能与一致性权衡

2PC性能低主要因为:

  1. 长事务锁定资源

    • 事务从开始到提交,数据库行/表锁一直持有。
    • 并发量上不去。
  2. 日志和网络开销大

    • Prepare和Commit阶段要写多次redo/undo日志。
  3. 阻塞问题

    • 协调器宕机会导致参与者阻塞。

TCC通过冻结资源+业务补偿绕过了锁持久化的问题,所以性能高,但它牺牲了立即一致性


🔍3. 成本与复杂度考虑

  • 2PC的优势:

    • 对开发透明,数据库/中间件自动管理一致性,业务方几乎不用改代码。
    • 适合那些必须立刻一致、且流量不高的场景(比如银行账务、证券撮合核心系统)。
  • TCC的劣势:

    • 开发成本高,每个业务都要写Try/Confirm/Cancel。
    • 复杂业务容易出错,要考虑幂等性、空回滚、悬挂事务等边界问题。

换句话说,2PC是“用钱买一致性”,TCC是“用开发复杂度换性能和灵活性”。


🔍4. 场景对比

场景推荐方案理由
银行核心账务2PC / XA资金安全要求极高,流量相对低
证券清算、支付清算2PC或Paxos/Raft严格一致性,不能延迟
电商下单、库存TCC、MQ事务消息高并发,最终一致足够
营销、积分系统异步MQ补偿可容忍较长不一致
大型微服务间交易TCC、Saga性能优先,灵活定制补偿

🔑总结:

  • 2PC存在的意义在于提供一种数据库级、自动化、强一致的分布式事务方案,对开发者友好,适合一致性高于一切的金融级场景。

  • TCC适合高并发业务,但一致性是最终保证,需要业务自己实现补偿逻辑,开发复杂度高。

  • 两者是不同一致性等级下的不同取舍:

    • 2PC:立即一致、性能低、简单易用
    • TCC:最终一致、性能高、开发复杂

💡可以这么记:

2PC是“数据库视角”,TCC是“业务视角”
2PC更像银行柜台的“当场入账”,TCC像“先冻结,再到账”,满足不同业务模型。



RocketMQ是一种最终一致性的分布式事务,就是说它保证的是消息最终一致性,而不是像2PC、3PC、TCC那样强一致分布式事务

我在学习这里的时候,想为什么要先发送一个半消息到消息队列?不如直接在发送方自己执行事务成功之后直接发送消息不就好了吗?就算这条消息发送失败了,但是有确认机制呀,可以重发。

这个问题问得特别好!你其实已经注意到了 “半消息” 的核心设计目的。简单说,RocketMQ事务消息引入“半消息”并不是为了简单的消息可靠投递,而是为了确保业务执行和消息投递的强关联,避免消息丢失或重复带来的不一致问题

我们来仔细拆一下:


🔹你的想法:先执行事务 → 成功后发送消息

表面上看逻辑很简单:

  1. 业务方执行本地事务(如扣库存、扣款等)。
  2. 事务成功后发送一条消息给 MQ,消费者再处理后续逻辑。
  3. 如果消息发送失败,重试发送就好。

看似没问题,但这里有一个关键的分布式一致性问题

  • 如果本地事务提交成功了,但是在发送消息前宕机了呢?
  • 这个时候,消费者永远收不到消息,就会导致 业务状态已经变更但下游系统完全不知情,出现永久性数据不一致

即使有消息发送重试,也没用,因为发送重试逻辑可能根本没机会执行(服务挂了)。

这就是“本地事务”和“消息发送”之间的原子性问题,无法保证两者“要么都成功,要么都失败”。


🔹RocketMQ的解决方案:半消息(Prepare Message)

RocketMQ引入“半消息”来解决这个问题:

  1. 先发送一条半消息(Prepare Message)

    • 半消息发送到 MQ 后,消费者看不见这条消息,它是“待确认”状态。
    • 这一步只是保证 MQ 至少有个“记录”。
  2. 执行本地事务

    • 业务方执行自己的数据库事务(扣款、扣库存等),事务结果是提交或回滚。
  3. 根据事务结果提交/回滚消息

    • 如果事务成功 → 通知 MQ 提交这条半消息,消费者才能真正消费。
    • 如果事务失败 → 通知 MQ 回滚消息,消息被丢弃。
  4. 消息回查机制

    • 如果 MQ 等不到业务方的“提交/回滚”指令,会主动回查业务方事务状态,最终决定提交还是回滚消息。

这样就实现了:

  • 即使发送方在执行本地事务后宕机,MQ 仍然保留着这条半消息,能通过回查确认事务最终状态。
  • 彻底避免了业务执行成功但消息永久丢失的问题。

🔹为什么不能只靠“发送失败重试”?

原因是:

  • “重试”前提是程序还能跑起来,而宕机、网络分区等问题可能导致“发送逻辑”根本没有机会执行。

  • RocketMQ的设计是“先发消息,再执行事务”,消息一旦在 MQ 里有了记录,就算业务方挂了,MQ 也能回查保证最终一致性。

  • 这实际上类似于2PC:

    • 阶段1(半消息) = Prepare
    • 阶段2(事务提交+消息提交) = Commit/Rollback
    • MQ相当于事务协调者。

🔹总结

  • 半消息的作用

    • 确保“本地事务执行”和“消息可靠投递”这两件事的原子性,避免事务成功但消息丢失导致的数据不一致。
  • 直接先执行事务后发消息

    • 无法应对事务提交后服务宕机的极端情况,会造成下游系统永远缺少这条消息,最终一致性无法保证。

RocketMQ这种方案实际上就是牺牲一点复杂度,换取更高的可靠性


💡一句话记:
半消息是为了解决事务和消息投递之间的“原子一致性”问题,而不仅仅是消息可靠投递的问题。

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

相关文章:

  • [软考中级]嵌入式系统设计师—核心知识点速记
  • 分布式相关
  • 【iOS】MVC架构
  • 自制扫地机器人(一)20 元级机械自动避障扫地机器人——东方仙盟
  • 晶晨线刷工具下载及易错点说明:生成工作流程XML失败
  • Trie树(静态数组实现)
  • 人工智能加速漏洞利用,15分钟即可完成概念验证?
  • 神州数码VRRP 原理与配置篇
  • 应用开发使用缓存
  • macos调用chrome后台下载wasm-binaries.tar.xz
  • 对于牛客网—语言学习篇—编程初学者入门训练—复合类型:BC136 KiKi判断上三角矩阵及BC139 矩阵交换题目的解析
  • Nginx四层负载均衡实战指南
  • 基于 YOLOv11n 的无人机航拍小目标检测算法学习
  • 鸿蒙搭配前端开发:应用端与WEB端交互
  • Go学习1:常量、变量的命名
  • 2025.8.31基于UDP的网络聊天室项目
  • LangChain Prompt管理核心:PromptTemplate与ChatPromptTemplate全解析
  • JVM学习总结
  • shell脚本(略)
  • KMP 算法相关练习题
  • 7.0elementplus布局容器详解
  • WebSocket的基本使用方法
  • 让你的App与众不同打造独特品牌展示平台
  • C语言学习笔记(自取)
  • 从新能源汽车看产品逻辑与认知系统
  • QML Chart组件之图例
  • 根据Excel数据表快速创建Word表格(标签)
  • 后向投影合成孔径辐射源定位方法(一)
  • 基于计算机视觉的海底图像增强系统:技术详述与实现
  • VMWare Tools (Linux)安装教程