解密分布式事务:CAP理论、BASE理论、两阶段提交(2PC)、三阶段提交(3PC)、补偿事务(TCC)、MQ事务消息、最大努力通知

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 解密分布式事务:CAP理论、BASE理论、两阶段提交(2PC)、三阶段提交(3PC)、补偿事务(TCC)、MQ事务消息、最大努力通知


🍊 CAP理论

在分布式环境下,我们无法同时保证数据一致性、可用性和分区容错性,只能选择其中两个。

分区容错性是必须要保证的,因为网络问题一定会发生。那么我们就需要让每个服务都有多个节点,这样就可以保证一个节点挂了之后,其他节点依然可以响应,这就是分区容错性。

但是一个服务有多个节点之后,一个节点挂了之后,其他节点如何保证数据一致性呢?这就要进行数据复制,确保每个节点上的数据都是最新的。这样就出现了一个问题,就是数据在复制过程中可能存在延迟,也就是说,我们无法保证每个节点上的数据都是同步的。这时候,我们就需要权衡一下,是选择保证数据的一致性,还是选择保证系统的可用性呢?

如果选择保证数据的一致性,那么当一个节点挂掉之后,其他节点会等待它恢复,以确保数据的一致性。这样的话,系统的可用性就会受到影响,因为用户可能会得到错误的响应或者超时。

如果选择保证系统的可用性,那么当一个节点挂掉之后,其他节点仍然可以响应请求,但是得到的数据可能不是最新的。这样的话,系统的数据一致性就会受到影响。

所以,我们只能在可用性和一致性之间权衡,选择其中一个。而这就是CAP定理告诉我们的。

想象一下,你是一家快递公司的老板,你要为公司的分布式系统设计一种方案来保证数据的一致性和可用性。快递公司有很多个仓库,每个仓库都存储着客户的快递信息(姓名、电话、地址、快递状态等等),这些信息需要在整个公司的分布式系统中进行共享。

首先,为了保证分区容错性,你会在每个仓库都设置一个主节点和多个从节点,当主节点出现故障时,从节点可以顶上来继续提供快递信息服务。这样,即使某个仓库的网络失效了,整个系统也不会因此瘫痪。

然而,当一个快递员送完货回到仓库,把快递信息更新到主节点上之后,这些更新的信息并不会立即同步到其他从节点上,因此就会出现数据一致性的问题。例如,你的客户A在网站上查询自己快递的状态,此时客户A的请求被分配到了一个从节点上处理,但是这个从节点上存储的快递信息还没有更新,因此客户A看到的信息可能不是最新的,这就是数据的最终一致性。

为了解决这个问题,你可以采取一种类似于“少数服从多数”的策略,即让每个从节点都去定期地跟主节点进行同步,而在同步的过程中,如果从节点和主节点上的数据不一致,从节点就会采用主节点上的数据。这样,虽然一段时间内客户看到的信息可能不是最新的,但是随着时间的推移,整个系统的数据最终还是会达到一致性的状态。

作为一个分布式系统的老板,你需要在数据一致性和可用性之间做出权衡,选择最适合公司应用场景的方案来保证系统的正常运行。这也是分布式系统设计中必须要考虑的一些问题,而CAP定理就是分布式系统设计的一个重要原则。

🍊 BASE理论

BASE理论是对CAP理论的延伸,其核心思想是在分布式系统中,即使无法做到强一致性,应用也可以采用适合的方式达到最终一致性。BASE理论分为三个方面,即基本可用、软状态和最终一致性。

基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。比如在电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。

软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。比如在分布式存储中,一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。MySQL Replication的异步复制也是一种体现。

最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。在分布式系统中,达到强一致性很难,而最终一致性则是通过合适的策略达到数据一致性的一种方法。

在具体应用中,BASE理论的策略可以根据不同的业务场景制定。比如,在支付订单场景中,由于分布式本身就在数据一致性上面很难保证,从A服务到B服务的订单数据有可能造成数据不一致性。因此,此类场景会酌情考虑AP,不强制保证数据一致性,但保证数据最终一致性。

在实际应用中,BASE理论的应用场景很广泛。例如,Erueka 是 SpringCloud 系列用来做服务注册和发现的组件,它采用AP策略,保证了系统的可用性。而 Zookeeper 则保证了一致性和分区容错性,因此在 SpringCloud 中被抛弃。具体根据各自业务场景所需来制定相应的策略而选择适合的产品服务等。

分布式事务指的是事务的操作位于不同的节点上,需要保证事务的ACID特性。

举个例子,当电商网站在大促期间运营压力增大时,为保证核心功能仍能正常使用,可能会采取分流、降级等措施,这就是基本可用的体现;而当一份数据在分布式存储中至少需要有三个副本时,不同节点间副本同步的延时就是软状态的体现;最终一致性则是在数据一致性难以保证的情况下,最终数据还是能达到一致状态。在实际应用中,根据不同业务场景的需求来制定相应的策略,选择适合的产品服务等。比如在支付订单场景中,数据一致性难以保证,此时可以采用AP策略,不强制保证数据一致性,但保证最终一致性;而在下单场景中,如果库存和订单不在同一个节点上,就需要涉及分布式事务,保证事务的 AICD 特性。因此,BASE 理论提供了一种实际可行的思想来解决分布式系统中出现的数据一致性问题,而具体应用则需要根据业务场景灵活运用。

🍊 两阶段提交(2PC)

你可能会遇到这样一种情况:你需要在分布式系统中进行多个节点之间的事务处理,但是由于网络不稳定、节点故障等原因,可能会导致某些节点的事务执行失败。那么这时候该怎么办呢?这时候就可以使用两阶段提交(2PC)协议来保证分布式系统中的事务一致性。

首先,2PC协议包括两个阶段:准备阶段和提交阶段。在准备阶段,协调者向所有参与者发送准备请求,并询问参与者事务是否执行成功。如果所有参与者都可以正常执行事务,那么协调者就会发送提交请求,让所有参与者提交事务。否则,协调者会发送回滚请求,让所有参与者回滚事务。在提交阶段,所有参与者收到协调者的请求后,立即执行事务。如果事务执行成功,参与者将向协调者发送“准备就绪”响应;否则,参与者会发送“无法执行”响应。

2PC协议的优点在于可以保证分布式系统中的事务一致性。但是缺点也很明显。首先,所有参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作,这就导致了同步阻塞的问题。其次,在2PC协议中,协调者在阶段二起到非常重要的作用,发生故障将会造成很大影响,这就是单点问题。还有,如果协调者只发送了部分提交消息,此时网络发生异常,那么只有部分参与者接收到提交消息,也就是说只有部分参与者提交了事务,这就导致了数据不一致的问题。

举个例子,假设有三个参与者A、B、C,需要执行一个事务。协调者向A、B、C发送准备命令,A响应成功,B响应失败,C没有响应。在第一阶段结束以后,协调者向A发送回滚命令,A执行回滚操作。这时候B和C还在等待协调者的指令,因为协调者没有超时机制,它会一直等待,导致资源被锁定,进而阻塞了其它操作的执行。

除了同步阻塞和单点故障外,2PC还存在数据不一致的问题。假设在第二阶段,协调者只向A和B发送了提交事务的指令,而C并没有收到,这将导致系统中的数据不一致。

在实际应用中,2PC通常会被替代或者改进,以解决上述问题。比如说,三阶段提交(3PC)就是2PC的改进,它能够解决2PC存在的同步阻塞问题。另外,某些应用可以采用基于消息的解决方案,来实现高可用性和数据一致性。

🎉 XA事务

InnoDB是一个常用的MySQL存储引擎,它提供了对XA事务的支持,这使得分布式事务成为可能。分布式事务是指多个独立的事务资源参与到一个全局事务中,这些资源通常是关系型数据库,但也可以是其他类型的资源。全局事务要求所有参与事务的节点都要提交或回滚,这对于事务原有的ACID要求又有了提高。

XA事务由一个或多个资源管理器(Resource Managers)、一个事务管理器(Transaction Manager)和一个应用程序(Application Program)组成。资源管理器提供访问事务资源的方法,事务管理器协调参与全局事务的各个事务,并与参与全局事务的所有资源管理器进行通信,应用程序定义事务的边界,指定全局事务中的操作。

分布式事务使用两段式提交(two-phase commit)的方式。在第一阶段,所有参与全局事务的节点都开始准备(PREPARE),告诉事务管理器它们准备好提交了。在第二阶段,事务管理器告诉资源管理器执行ROLLBACK还是COMMIT。如果任何一个节点不能提交,则所有的节点都被告知需要回滚。分布式事务与本地事务不同之处在于需要多一次的PREPARE操作,待收到所有节点的同意信息后,再进行COMMIT或是ROLLBACK操作。

在MySQL数据库的分布式事务中,资源管理器就是MySQL数据库,事务管理器为连接MySQL服务器的客户端。最为常见的内部XA事务存在于binlog与InnoDB存储引擎之间。由于复制的需要,因此大多数数据库都开启了binlog功能。在事务提交时,先写二进制日志,再写InnoDB存储引擎的重做日志。这两个操作的要求是原子的,即二进制日志和重做日志必须同时写入。若二进制日志先写了,而在写入InnoDB存储引擎时发生了宕机,那么slave可能会接收到master传过去的二进制日志并执行,最终导致了主从不一致的情况。

举一个银行转账系统的例子,假设David想从他的账户向Maraih的账户转移10 000元。在这个场景中,银行的数据库充当了资源管理器的角色,客户端充当了事务管理器和应用程序的角色。这个全局事务涉及到两个独立的数据库节点,一般分别对应两个不同的银行。

在第一阶段(PREPARE),客户端开始向每个数据库节点发送准备提交的信号。每个节点都需要判断它们的操作是否可行,比如是否有足够的资金可用于转账。如果操作可行,节点会返回一个“同意”信号。否则,节点会返回一个“否决”信号。

在第二阶段,客户端向节点发送COMMIT或ROLLBACK命令。如果所有节点均返回了“同意”信号,那么客户端会发送COMMIT命令。如果有任何一个节点返回了“否决”信号,那么客户端会发送ROLLBACK命令。节点将执行对应的命令,并告知客户端它们的执行结果。

需要注意的是,在分布式事务中,PREPARE操作是非常重要的。如果有任何一个节点无法提交,那么所有节点都必须回滚。这就需要所有节点都能够判断一个操作是否可行,而不仅仅是局限于本地节点。因此,在分布式事务中,PREPARE操作必须是原子的,并且必须考虑到可能出现的网络故障和节点宕机等问题。

在MySQL的分布式事务中,二进制日志和InnoDB存储引擎的重做日志也必须满足原子性。因为这两个日志是用于实现主从复制和故障恢复的核心机制,任何一方的失败都可能导致数据的丢失或不一致。因此,在MySQL的分布式事务中,使用两段式提交来确保二进制日志和InnoDB存储引擎的重做日志是同时写入的。

🍊 三阶段提交(3PC)

三阶段提交(3PC)是一种分布式事务协议,相比于二阶段提交(2PC)多了一个阶段并引入了超时机制,从而减少了故障恢复时的复杂性。3PC包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段。其中准备阶段不直接执行事务,而是先询问参与者是否有条件接受该事务,避免资源被无效地锁定。预提交阶段统一了各参与者的状态,使得在之后执行阶段有一个统一的标准。最后的提交阶段才真正执行事务。

例如,假如你是一位参与者,当你知道自己进入了预提交状态,就可以推断出其他参与者也都进入了预提交状态。这个过程使得参与者之间的状态得到统一,从而让协调者知道应该如何执行事务。

相比于2PC,3PC引入了预提交阶段,减少了故障恢复时的复杂性。2PC是同步阻塞的,协调者挂在提交请求还未发出去的时候会导致所有参与者都锁定资源并阻塞等待,严重影响性能。

以一个例子来说明,假设一个系统需要从A节点向B节点转移100元,C节点作为协调者节点。在使用2PC时,协调者节点会依次向A、B节点发送“准备提交”命令,如果所有节点都可以提交,则最后向所有节点发送“提交”命令。但是如果有任何一个节点出现问题,就会导致整个事务无法完成。

3PC的预提交阶段使得参与者之间的状态得到统一,减少了无效的锁定资源,从而避免了性能下降。另外,3PC也克服了2PC的无法处理协调者和参与者同时挂掉的问题,新协调者可以根据参与者当前的状态来执行相应的命令。3PC的缺点在于引入了一个新的阶段,导致性能会降低一些。而且大部分情况下资源都是可用的,这样每次明知可用执行还得询问一次,会浪费一些时间。

3PC是一种更加完善的分布式事务处理方法,可以在一定程度上解决2PC的问题,但也存在一些缺陷。了解这些方法可以让我们更好地处理分布式事务,保证数据一致性。

🍊 补偿事务(TCC)

补偿事务(TCC)是一种应用层面上的一致性保证机制,用于在分布式环境下保证事务的原子性。它通过将一个大事务拆成多个小事务,每个小事务都有自己的确认和撤销操作,以此保证整个事务的一致性。

举个例子:假设有一个在线购物网站,用户下单后需要先进行扣款,然后进行物流发货。使用TCC机制,可以将这个大事务拆成两个小事务,即扣款和发货,每个小事务拥有自己的确认和撤销操作。在Try阶段,扣款和发货的操作会预留资源,如果某个操作执行失败,则会在Cancel阶段将资源释放。在Confirm阶段,如果所有操作都执行成功,则可以提交整个事务,否则进行回滚操作。

TCC相对于2PC(两阶段提交)来说,实现和流程相对简单,但数据的一致性可能会稍差一些。

以转账为例,假设 Bob 想向 Smith 转账,我们可以将整个转账过程分为三个阶段:

在 Try 阶段,我们需要调用远程接口将 Smith 和 Bob 的钱冻结起来,即先检查转账是否可行并预留资源。 在 Confirm 阶段,我们执行远程调用的转账操作,并解冻已预留的资源。如果第二步执行成功,则转账成功;否则,我们需要执行 Cancel 阶段。 在 Cancel 阶段,我们需要调用远程冻结接口对应的解冻方法,释放预留资源并取消转账。

相比于 2PC,TCC 的实现比较简单,流程也相对清晰。但是,它的数据一致性不如 2PC。需要在实现时写很多补偿的代码,而在某些业务流程中,TCC 可能不太适合或难以处理。

总之,TCC机制是一种针对分布式环境下的一致性保障技术,可以将大事务拆成多个小事务,每个小事务都有自己的确认和撤销操作,以此来保证整个事务的原子性。

🍊 MQ事务消息

MQ事务消息是指在消息发送和本地事务执行中间加入了一次消息确认的过程,通过三个阶段的操作确保了消息和本地事务的同时成功或同时失败,并达到最终一致性的效果。其中,第一阶段是消息发送,第二阶段是本地事务执行,第三阶段是消息确认。这种方式需要在业务方法内提交两次请求,一次发送消息,一次确认消息。如果确认消息发送失败,则RocketMQ会定期扫描消息集群中的事务消息来确认,从而保证消息发送和本地事务同时成功或同时失败。

举个例子,假设我们要在一个电商平台上创建一个订单,订单信息需要发送到MQ,并在本地生成一笔订单记录。如果发送消息成功,但是本地事务执行失败,那么订单记录就会丢失,而如果本地事务执行成功,但是消息发送失败,MQ中也没有对应的订单记录。这时候,我们就可以使用MQ事务消息来解决这个问题,保证订单记录和MQ消息的同时成功或同时失败。在订单创建的业务方法中,我们需要分别完成订单记录的创建和消息的发送,并在业务方法执行结束之前提交消息确认请求。

MQ事务消息的优点在于实现了最终一致性,不需要依赖本地数据库事务,但是缺点也很明显,实现难度大,而且市面上许多主流MQ都不支持事务消息,比如RabbitMQ和Kafka。不过,一些第三方的MQ,如RocketMQ,支持事务消息的方式也是类似于采用的二阶段提交,这样就能够在一定程度上满足业务的需求。

假设小明在网上购物,他将商品添加到购物车后,点击“结算”按钮,页面跳转到支付界面,同时系统会发送一条消息到MQ中,告诉库存系统和订单系统小明购买了哪些商品,并将相应的库存锁定。

如果MQ不支持事务消息,每个系统将分别处理消息。库存系统会锁定库存,发现库存不足时抛出异常,并将消息撤回;订单系统会创建订单,如果由于某种原因创建订单失败,就不会发送撤回消息。这样就会出现库存系统和订单系统之间数据不一致的情况,例如库存被锁定,但是订单未生成。

如果MQ支持事务消息,小明在支付界面点击“确认支付”按钮后,MQ先发送一条Prepared消息给MQ客户端,并返回消息的地址。此时库存系统和订单系统暂时不做任何处理。小明的支付请求会被当做本地事务执行,如果支付成功,库存和订单数据就可以被提交;如果支付失败,数据就会回滚,库存也会被解锁。当库存和订单数据提交或回滚后,MQ客户端就会根据Prepared消息的地址来访问消息,并修改消息的状态。如果修改成功,消息就被提交,否则就会撤回消息并重新处理。

通过这个例子,我们可以看出MQ事务消息的优点在于实现了最终一致性,避免了数据不一致的情况。但是相对于普通消息来说,MQ事务消息的实现难度更大,需要发送方实现check接口来保证消息的正确性,而且市面上一些主流MQ如RabbitMQ和Kafka并不支持事务消息。

🍊 最大努力通知

最大努力通知,其实就是一种柔性事务的思想,适用于一些对时间不敏感的业务,例如短信通知。在这种情况下,我们可以尽力去完成事务的最终一致性,但是不能保证百分之百的成功,我们只能尽最大的努力去完成。

比如,我们在向用户发送短信通知时,但是由于网络原因或其他不可预知的情况导致发送失败,这时候我们可以使用最大努力通知的方式,多次尝试发送,直到达到一定的次数或者成功为止。如果还是无法成功,我们可以记录下来,并引入人工处理,或者直接舍弃。这样,我们可以最大限度地保证尽可能多的用户能够收到通知。

具体来说,我们可以将要发送的短信消息存储到本地消息表中,并定时调用对应的服务来进行发送。如果发送失败了,我们可以记录下来,并在后续的任务中再次尝试发送,直到发送成功为止。如果多次尝试仍然无法发送成功,我们可以选择人工介入或直接舍弃该消息。

这种方法的优点是实现简单,适用范围广,对于一些对时间不敏感的业务来说是一种非常好的选择。相比于强一致性事务和补偿性事务,最大努力通知更加灵活,可以在业务层面实现,对业务侵入性较小。

最大努力通知并不是唯一的解决方案,与之相对比的包括:2PC、3PC和TCC。2PC和3PC是一种强一致性事务,但是仍然存在数据不一致和阻塞等风险,而且只能用于数据库层面的事务处理。TCC是一种补偿性事务思想,可以在业务层面实现,但是对业务的侵入性较大,每个操作都需要实现对应的三个方法。

相比之下,本地消息、事务消息和最大努力通知都是最终一致性事务,适用于一些对时间不敏感的业务。其中,本地消息表会有后台任务定时去查看未完成的消息,然后去调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。事务消息在半消息被commit后,如果订阅者一直不消费或消费不了,则会一直重试,直到进入死信队列。这些方案都强调了尽最大的努力去完成事务的最终一致性,而不是百分之百的保证成功,保证了业务的灵活性和可靠性。


相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
23天前
|
消息中间件 网络协议 C#
C#使用Socket实现分布式事件总线,不依赖第三方MQ
`CodeWF.EventBus.Socket` 是一个轻量级的、基于Socket的分布式事件总线系统,旨在简化分布式架构中的事件通信。它允许进程之间通过发布/订阅模式进行通信,无需依赖外部消息队列服务。
C#使用Socket实现分布式事件总线,不依赖第三方MQ
|
29天前
|
消息中间件 Java 数据库
新版 Seata 集成 RocketMQ事务消息,越来越 牛X 了!阿里的 Seata , yyds !
这里 借助 Seata 集成 RocketMQ 事务消息的 新功能,介绍一下一个新遇到的面试题:如果如何实现 **强弱一致性 结合**的分布式事务?
新版 Seata 集成 RocketMQ事务消息,越来越 牛X 了!阿里的 Seata , yyds !
|
1月前
|
消息中间件 监控 供应链
深度剖析 RocketMQ 事务消息!
本文深入探讨了 RocketMQ 的事务消息原理及其应用场景。通过详细的源码分析,阐述了事务消息的基本流程,包括准备阶段、提交阶段及补偿机制。文章还提供了示例代码,帮助读者更好地理解整个过程。此外,还讨论了事务消息的优缺点、适用场景及注意事项,如确保本地事务的幂等性、合理设置超时时间等。尽管事务消息增加了系统复杂性,但在需要保证消息一致性的场景中,它仍是一种高效的解决方案。
81 2
|
1月前
|
缓存 Java 数据库
JAVA分布式CAP原则
JAVA分布式CAP原则
49 0
|
3月前
|
消息中间件 存储 NoSQL
MQ的顺序性保证:顺序队列、消息编号、分布式锁,一文全掌握!
【8月更文挑战第24天】消息队列(MQ)是分布式系统的关键组件,用于实现系统解耦、提升可扩展性和可用性。保证消息顺序性是其重要挑战之一。本文介绍三种常用策略:顺序队列、消息编号与分布式锁,通过示例展示如何确保消息按需排序。这些方法各有优势,可根据实际场景灵活选用。提供的Java示例有助于加深理解与实践应用。
86 2
|
3月前
|
消息中间件 存储 缓存
RocketMQ发送消息原理(含事务消息)
本文深入探讨了RocketMQ发送消息的原理,包括生产者端的发送流程、Broker端接收和处理消息的流程,以及事务消息的特殊处理机制,提供了对RocketMQ消息发送机制全面的理解。
RocketMQ发送消息原理(含事务消息)
|
3月前
|
消息中间件 监控 安全
大事务+MQ普通消息线上问题排查过程技术分享
【8月更文挑战第23天】在复杂的企业级系统中,大事务与消息队列(MQ)的结合使用是一种常见的架构设计,用于解耦系统、提升系统响应性和扩展性。然而,这种设计也带来了其特有的挑战,特别是在处理退款业务等涉及金融交易的高敏感场景时。本文将围绕“大事务+MQ普通消息线上问题排查过程”这一主题,分享一次实际工作中的技术排查经验,旨在为大家提供可借鉴的解决思路和方法。
55 0
|
4月前
|
算法 前端开发
|
3月前
|
存储 NoSQL 关系型数据库
(二)漫谈分布式之理论篇:用刁钻的手法掰正你那学歪的CAP与BASE理论!
大多数讲分布式的资料、课程,虽然在一开始就会先讲述CAP理论,但大家仔细想想,你在做分布式项目时,落地过这个基础理论吗?相信包括我在内,以及90%以上的开发者,没有,至于为何,本文来从不一样的角度好好唠唠CAP,以及另一个著名的BASE理论~
|
4月前
|
消息中间件 编译器 C++
分布式篇问题之TCC方案中的Try阶段主要问题如何解决
分布式篇问题之TCC方案中的Try阶段主要问题如何解决