大白话八股文-分布式事务

一.分布式事务介绍

        分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

       可以归结为以下几种场景:        

  • 单体系统访问多个数据库
  • 多个微服务访问同一个数据库
  • 多个微服务访问多个数据库

二.分布式理论(CAP,BASE)

1.CAP

        CAP 指的是在分布式环境下,一个系统需要满足一致性/可用性/分区容错性这三个特性,而经过无数系统的实践,最终我们发现一个分布式系统,无法同时满足这三点。

C: 一致性 => 一 个分布式系统,在任意情况下,所有实例都应该返回最新一致的数据。

A: 可用性 => 一 个分布式系统,在任意情况下,所有实例都应该能够随时响应成功数据给客户端,哪怕数据是旧数据。

P: 分区容错性 => 一 个分布式系统,要保证在任意情况下,至少有一个网络分区是可用的,从另一方面来说,就是存在多个网络分区时,能够容忍有部分分区不可用。

分区更多意义上,指的是网络分区(一个一个机房)

        由于每个系统都基本要满足分区容错性,而分区容错也就意味着服务实例可能存在多个不同的网络分区中,而一旦在多个不同的网络分区时,A/C之间就产生了互斥性,也就证明了确实一个系统无法实现 CAP 三点同时满足。由于分区容错性是绝大部分系统都应该满足的特性,因此真正的系统设计更多考虑的特性只有 CP/AP 两种类型的系统。

2.BASE理论

        BASE (Basically Available Soft state Eventually consistent)基本可用软状态(中间状态)的最终一致

  • BA: 基本可用 =>在一个分布式系统中,只要满足基本可用即可,无需所有节点都可用。
  • S: 中间状态 =>在一个分布式系统中,不需要满足强一致性,允许其存在中间状态。
  • E:最终一致性 =>在一个分布式系统中,允许存在中间状态,但是最终(时效性)一定要达成一致。

        BASE 理论更多是针对 CAP 的一个优化版本,核心是最终一致以及中间状态,保证服务实例基本可用就行,目前绝大部分系统的设计其实都是按照 BASE 理论去进行设计的。

三.分布式事务的解决方案

1.全局事务XA

        全局事务基于DTP模型实现。DTP是由X/Open组织提出的一种分布式事务模型——X/Open

Distributed Transaction Processing Reference Model。它规定了要实现分布式事务,需要三种角色:

  • AP: Application 应用系统 (微服务)
  • TM: Transaction Manager 事务管理器 (全局事务管理)
  • RM: Resource Manager 资源管理器 (数据库)

1.二阶段提交

整个事务分成两个阶段(2PC):

阶段一:准备阶段(Prepare Phase)

  • 协调者(Coordinator)向所有参与者(Participants)发送 prepare 请求,询问它们是否准备好提交事务。
  • 每个参与者检查本地事务的状态,确定是否可以提交事务。
  • 如果参与者能够提交事务(例如,没有冲突、资源可用等),它会锁定相关资源,并返回 prepared 响应给协调者。
  • 如果某个参与者无法提交事务(例如,资源不足或发生错误),它会返回 abort 响应给协调者。
  • 协调者等待所有参与者的响应。如果所有参与者都返回 prepared,则进入第二阶段;如果有任何一个参与者返回 abort,则整个事务将被回滚。

阶段二: 提交阶段(Commit Phase)

  • 如果所有参与者都返回 prepared,协调者会向所有参与者发送 commit 请求,指示它们提交事务。
  • 如果有任何一个参与者返回 abort,或者协调者在第一阶段超时未收到所有参与者的响应,协调者会向所有参与者发送 abort 请求,指示它们回滚事务。
  • 如果收到 commit 请求,参与者会解锁之前锁定的资源,并提交事务。
  • 如果收到 abort 请求,参与者会释放之前锁定的资源,并回滚事务。
  • 参与者执行完提交或回滚操作后,会向协调者发送确认消息,通知其操作结果。

优点

  • 强一致性:2PC 能够确保所有参与者要么全部提交事务,要么全部回滚事务,从而保证了系统的强一致性。
  • 简单明了:2PC 的逻辑相对简单,容易理解和实现。

缺点

  • 阻塞问题:如果协调者在第一阶段发出 prepare 请求后崩溃,所有参与者都会处于等待状态,导致系统出现阻塞。这种情况被称为“阻塞”或“悬挂”状态。
  • 性能开销大:由于需要两次全局通信(准备阶段和提交阶段),2PC 的性能开销较大,尤其是在高并发场景下,可能会成为瓶颈。
  • 单点故障:协调者是整个事务的核心,如果协调者发生故障,整个事务可能无法继续进行,直到协调者恢复。协调者发出commit请求之后,部分参与者发生故障,这会导致只有部分参与者接收到commit请求,其他故障参与者未接收到请求,于是整个分布式便出现数据不一致的现象。
2.三阶段提交

        三阶段提交协议(3PC)是对两阶段提交协议的一种改进,旨在解决2PC中的阻塞问题。3PC通过引入第三个阶段——预提交阶段(Pre-commit Phase),进一步细化了事务的执行过程,减少了阻塞的可能性。

第一阶段:CanCommit Phase

  • 协调者向所有参与者发送 canCommit 请求,询问它们是否有能力提交事务。
  • 每个参与者检查本地资源的状态,确定是否可以提交事务。
  • 如果参与者能够提交事务(例如,没有冲突、资源可用等),它会返回 yes 响应给协调者。
  • 如果某个参与者无法提交事务(例如,资源不足或发生错误),它会返回 no 响应给协调者。
  • 协调者等待所有参与者的响应。如果所有参与者都返回 yes,则进入第二阶段;如果有任何一个参与者返回 no,则整个事务将被回滚。

第二阶段:Pre-commit Phase(预提交阶段)

  • 如果所有参与者都返回 yes,协调者会向所有参与者发送 preCommit 请求,指示它们进入预提交状态。
  • 参与者接收到 preCommit 请求后,会锁定相关资源,并做好提交的准备工作。此时,参与者处于预提交状态,但尚未真正提交事务。
  • 参与者向协调者发送确认消息,表明它们已经准备好提交事务。
  • 协调者等待所有参与者的确认消息。如果所有参与者都返回确认消息,则进入第三阶段;如果有任何一个参与者未能返回确认消息,协调者会进入第三阶段并发送 abort 请求

第三阶段:Do-commit/Abort Phase(提交/回滚阶段)

  • 如果所有参与者都返回确认消息,协调者会向所有参与者发送 doCommit 请求,指示它们提交事务。
  • 如果有任何一个参与者未能返回确认消息,或者协调者在第二阶段超时未收到所有参与者的确认消息,协调者会向所有参与者发送 abort 请求,指示它们回滚事务。
  • 如果收到 doCommit 请求,参与者会解锁之前锁定的资源,并提交事务。
  • 如果收到 abort 请求,参与者会释放之前锁定的资源,并回滚事务。
  • 参与者执行完提交或回滚操作后,会向协调者发送确认消息,通知其操作结果。

预提交状态的具体作用
        在第二阶段(Pre-commit Phase),参与者进入预提交状态,这意味着:

  • 锁定资源:参与者已经锁定了相关的资源,并做好了准备工作的状态。
  • 减少阻塞:即使协调者在此阶段崩溃,参与者可以根据自己的状态做出合理的决策(例如,超时后自动提交事务)。这减少了系统陷入长期阻塞的可能性。

优点

  • 减少阻塞:相比于2PC,3PC 引入了预提交阶段,减少了协调者崩溃后系统陷入阻塞的可能性。即使协调者在某个阶段崩溃,参与者仍然可以根据自己的状态做出合理的决策(例如,超时后自动提交或回滚)。
  • 更高的容错性:3PC 在一定程度上提高了系统的容错性,减少了对协调者的依赖。

缺点

  • 复杂度增加:相比2PC,3PC 的流程更加复杂,增加了实现和维护的难度。
  • 性能开销更大:由于引入了额外的预提交阶段,3PC 需要三次全局通信,因此性能开销比2PC更大。
  • 依然存在阻塞风险:虽然3PC 减少了阻塞的可能性,但在某些极端情况下(例如,协调者在预提交阶段崩溃),系统仍然可能出现阻塞。

2.本地消息表+消息队列

        本地消息表与业务数据表处于同一个数据库中,这样就能 利用本地事务来保证在对这两个表的操作实现分布式事务,并且使用了消息队列来保证最终一致性。

  1. 在数据库中存放一张本地消息的表, 在分布式事务操作的一方执行写业务数据的操作之后向本地消息表发送一个消息, 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的 本地事务能保证这个消息一定会被写入本地消息表中
  2. 之后将本地消息表中的消息转发到 Kafka /RabbitMQ等消息队列中,如果转发成功,消息状态可以直接改成已成功,并将消息从本地消息表中置为已发送状态,否则继续重新转发,重新转发就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理
  3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作,可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。

3.MQ事务消息

        有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。以阿里的 RocketMQ 中间件为例,其思路大致为:

  • 第一阶段Prepared消息,会拿到消息的地址。
  • 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

        也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

优点: 实现了最终一致性,不需要依赖本地数据库事务

缺点: 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源

4.TCC(补偿事务)

        2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了。TCC 是 Try、Confirm、Cancel 三个词语的缩写,TCC 要求每个分支事务实现三个操作: 预处理 Try、 确认 Confirm、 撤销 Cancel

  • Try 指的是预留,即资源的预留和锁定, 注意是预留
  • Confirm 指的是业务确认操作,这一步其实就是真正的执行了
  • Cancel 指的是撤销回滚操作,可以理解为把预留阶段的动作撤销了

三个阶段

  1. Try 阶段是做完业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操作,它和后续的 Confirm 一起才能真正构成一个完整的业务逻辑
  2. Confirm 阶段是做确认提交,Try 阶段所有分支事务执行成功后开始执行 Confirm。通常情况下,采用 TCC 则认为 Confirm 阶段是不会出错的。即:只要 Try 成功,Confirm 一定成功。若 Confirm 阶段真的出错了,需引入重试机制或人工处理
  3. Cancel 阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,采用 TCC 则认为 Cancel 阶段也是一定成功的。若 Cancel 阶段真的出错了,需引入重试机制或人工处理

TM 事务管理器
        TM事务管理器可以实现为独立的服务,也可以让全局事务发起方充当 TM 的角色,TM 独立出来是为了成为公用组件,是为了考虑系统结构和软件复用。TM 在发起全局事务时生成全局事务记录,全局事务 ID 贯穿整个分布式事务调用链条,用来记录事务上下文, 追踪和记录状态,由于 Confirm 和 Cancel 失败需进行重试,因此需要实现为幂等,幂等性是指同一个操作无论请求多少次,其结果都相同

        可以看到流程还是很简单的,难点在于业务上的定义,对于每一个操作你都需要定义三个动作分别对应Try - Confirm - Cancel因此 TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作。还有一点要注意,撤销和确认操作的执行可能需要重试,因此还需要保证操作的幂等。相对于 2PC、3PC ,TCC 适用的范围更大,但是开发量也更大,毕竟都在业务上实现,不过也因为是在业务上实现的,所以TCC可以跨数据库、跨不同的业务系统来实现事务。

5.最大努力通知

发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。

  • 有一定的消息重复通知机制。因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。
  • 消息校对机制。

        如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。

最大努力通知与可靠消息一致性有什么不同?

    1.解决方案思想不同

        可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证。

        最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。

    2.两者的业务应用场景不同

        可靠消息一致性关注的是交易过程的事务一致,以异步的方式完成交易。最大努力通知关注的是交易后的通知事务,即将交易结果可靠的通知出去。

    3.技术解决方向不同

        可靠消息一致性要解决消息从发出到接收的一致性,即消息发出并且被接收到。最大努力通知无法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠机制是,最大努力的将消息通知给接收方,当消息无法被接收方接收时,由接收方主动查询消息(业务处理结果)。 

通过对最大努力通知的理解,采用MQ的ack机制就可以实现最大努力通知。

方案1

本方案是利用MQ的ack机制由MQ向接收通知方发送通知,流程如下

    1、发起通知方将通知发给MQ。使用普通消息机制将通知发给MQ。

    注意:如果消息没有发出去可由接收通知方主动请求发起通知方查询业务执行结果。(后边会讲)

    2、接收通知方监听 MQ。

    3、接收通知方接收消息,业务处理完成回应ack。

    4、接收通知方若没有回应ack则MQ会重复通知。

    MQ会按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 (如果MQ采用rocketMq,在broker中可进行配置),直到达到通知要求的时间窗口上限。

    5、接收通知方可通过消息校对接口来校对消息的一致性。

方案2

        本方案也是利用MQ的ack机制,与方案1不同的是应用程序向接收通知方发送通知

交互流程如下:

    1、发起通知方将通知发给MQ。

    使用可靠消息一致方案中的事务消息保证本地事务与消息的原子性,最终将通知先发给MQ。

    2、通知程序监听 MQ,接收MQ的消息。

    方案1中接收通知方直接监听MQ,方案2中由通知程序监听MQ。通知程序若没有回应ack则MQ会重复通知。

    3、通知程序通过互联网接口协议(如http、webservice)调用接收通知方案接口,完成通知。通知程序调用接收通知方案接口成功就表示通知成功,即消费MQ消息成功,MQ将不再向通知程序投递通知消息。

    4、接收通知方可通过消息校对接口来校对消息的一致性。

方案1和方案2的不同点

    1、方案1中由接收通知方案监听 MQ,此方案主要应用与内部应用之间的通知。

    2、方案2中由通知程序监听MQ,收到MQ的消息后由通知程序通过互联网接口协议调用接收通知方。此方案主要应用于外部应用之间的通知,例如支付宝、微信的支付结果通知。

6.Seata

        Seata是由阿里中间件团队发起的开源项目 Fescar,后更名为Seata,它是一个是开源的分布式事务框架。传统2PC的问题在Seata中得到了解决,它通过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是性能较好,且不长时间占用连接资源,它以高效并且对业务0侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(即2PC)及TCC模式的分布式事务解决方案。

与 传统2PC 的模型类似,Seata定义了3个组件来协议分布式事务的处理过程

  • Transaction Coordinator (TC): 事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚。
  • Transaction Manager (TM): 事务管理器,TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令。
  • Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚。

以新用户注册送积分来举例Seata的分布式事务过程

  1. 用户服务的 TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID

  2. 用户服务的 RM 向 TC 注册 分支事务,该分支事务在用户服务执行新增用户逻辑,并将其纳入 XID 对应全局事务的管辖

  3. 用户服务执行分支事务,向用户表插入一条记录

  4. 逻辑执行到远程调用积分服务时(XID 在微服务调用链路的上下文中传播),积分服务的RM 向 TC 注册分支事务,该分支事务执行增加积分的逻辑,并将其纳入 XID 对应全局事务的管辖

  5. 积分服务执行分支事务,向积分记录表插入一条记录,执行完毕后,返回用户服务

  6. 用户服务分支事务执行完毕

  7. TM 向 TC 发起针对 XID 的全局提交或回滚决议

  8. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求

Seata实现2PC与传统2PC的差别

  • 架构层次方面,传统2PC方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身,通过 XA 协议实现,而Seata的 RM 是以jar包的形式作为中间件层部署在应用程序这一侧的
  • 两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback,事务性资源的锁都要保持到Phase2完成才释放。而Seata的做法是在Phase1 就将本地事务提交,这样就可以省去Phase2持锁的时间,整体提高效率

参考文章:

分布式事务_分布式事务的解决方案-优快云博客

两阶段提交协议和三阶段提交协议详细讲讲_两段提交协议-优快云博客

分布式事务处理:2PC、TCC、可靠消息与最大努力通知-优快云博客

Java分布式事务是指在分布式系统中,多个独立的服务或应用之间进行数据操作时,保证数据的一致性和可靠性的一种机制。简单来说,就是多个服务或应用在进行数据操作时,要么全部成功,要么全部失败,不会出现部分成功部分失败的情况。 在分布式系统中,每个服务或应用都有自己的数据库,它们之间需要进行数据的读取和写入。当多个服务或应用同时进行数据操作时,可能会出现以下问题: 1. 数据不一致:由于网络延迟或其他原因,某个服务或应用的数据操作成功了,但其他服务或应用的数据操作失败了,导致数据不一致。 2. 并发冲突:多个服务或应用同时对同一份数据进行读写操作,可能会导致数据冲突和错误。 为了解决这些问题,Java分布式事务引入了一些机制和技术,例如: 1. 两阶段提交(Two-Phase Commit):在分布式事务中,引入一个协调者(Coordinator)来协调各个参与者(Participant)的数据操作。在第一阶段,协调者询问各个参与者是否可以提交事务;在第二阶段,如果所有参与者都同意提交,则协调者通知各个参与者提交事务;如果有任何一个参与者不同意提交,则协调者通知各个参与者回滚事务。 2. 分布式事务消息:使用消息队列来实现分布式事务,将数据操作和消息发送放在同一个事务中,保证数据和消息的一致性。 3. 分布式锁:通过分布式锁来控制对共享资源的访问,保证在同一时间只有一个服务或应用可以对资源进行操作,避免并发冲突。 以上是对Java分布式事务的简单介绍,希望能帮到你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值