一个
引导阅读
对于分布式事务的概念,可能有很多同学不理解或者理解不深。在本文中,作者打算着重介绍与分布式事务相关的基本概念,如2PC、3PC、TCC等。
2
数据库事务的概念
在描述分布式事务的概念之前,让我们回顾一下与事务相关的一些概念。
交易的基本概念:
它是一个程序执行单元,在这个单元中,所有的操作都成功执行,或者所有的操作都失败。不允许出现只有一半操作成功,另一半操作失败的情况。例如,如果一段事务代码完成了两个数据库更新操作,这两个数据库操作要么成功执行,要么回滚。
交易的基本特征:
我们知道事务有四个非常重要的特征,我们通常称之为(ACID)。
原子性:表示一个事务是一个不可分割的整体,所有操作要么完成,要么根本不做;只要事务中的一个操作出错并回滚到事务开始前的状态,所有以前执行的操作都是无效的,应该回滚到开始前的状态。
一致性:数据必须在事务执行前后从一种状态到另一种状态保持一致。比如A转账给B时(A和B的总金额是一致的状态),A是不可能扣钱的,但是B没有收到。
隔离:多个并发事务相互隔离,不能相互干扰。事务的隔离可能不是特别容易理解。这里的并发事务是指两个事务操作同一数据的情况;对于并发事务中相同数据的隔离问题,要求不能出现脏读和幻读,即事务A不能读取事务B尚未提交的数据,或者当事务A读取数据进行更新操作时,不允许事务B先更新该数据。为了解决这个问题,常用的手段是锁定,由数据库的相关锁定机制来保证。
持久性:事务完成后,对数据库的更改将被永久保存,并且不能回滚。
你可以在网上搜索数据库交易的基本概念。这只是回顾一下事务的基本概念和特征,比如事务并发性、事务隔离级别等。如果忘记了,可以复习一下(小技巧:面试中常问的问题)。
三
什么是分布式事务
我们已经回顾了下一个事务的基本概念,那么分布式事务的概念是什么呢?it和数据库事务有什么区别?
事实上,分布式事务和数据库事务的概念本质上是一样的。既然是事务,就需要满足事务的基本特征(ACID),但是分布式事务的表达方式和本地事务的表达方式有很大的不同。比如在一个JVM进程中,如果数据库的多个记录需要同时操作,并且这些操作需要在一个事务中,那么我们可以通过数据库提供的事务机制(通常是数据库锁)来实现。
通过将JVM进程(应用)拆分成微服务架构,将一个本地逻辑执行单元拆分成几个独立的微服务,分别操作不同的数据库和表,通过网络调用服务。
例如,服务A收到采购订单请求后,需要致电服务B进行支付。如果付款成功,则采购订单将被处理为待发货;否则,采购订单将被处理为失败。(如图所示)
上面的例子中,服务B是否会支付成功,但是服务A因为网络通话问题没有得到通知,导致用户支付,但是购物订单却无法显示支付成功的状态?
答案是这种情况比较常见,因为服务B需要在处理成功后向服务A发送一个网络请求,这个过程很有可能失败。那么如何保证“服务a->:服务b”这个流程可以形成一个事务,要么全部成功,要么全部失败?而这是一个典型的需要通过分布式事务来解决的问题。
分布式事务是为了解决微服务架构(都是分布式系统的形式)中不同节点之间的数据一致性问题。这种一致性问题本质上解决了传统事务需要解决的问题,即当一个请求在多个微服务调用链中时,所有服务的数据处理要么成功,要么回滚。当然,分布式事务问题的形式可能与传统事务有很大不同,但问题的本质是一样的,需要解决数据一致性的问题。
分布式事务的实现方式有很多种,最具代表性的是Oracle Tuxedo系统提出的XA分布式事务协议。XA协议包括两种实现:两阶段提交(2PC)和三阶段提交(3PC)。接下来,我们将分别介绍这两种实现的原理。
四
两阶段提交(2PC)
两阶段提交也称为2pc(两阶段提交协议),是一个非常经典的强一致性集中式原子提交协议。这里的集中化意味着协议中有两种类型的节点:一种是集中式协调器节点,另一种是n个参与节点。
让我们以一个尽可能贴近实际业务场景的操作为例:“假设在一个分布式架构系统中,事务的发起者通过分布式事务协调器向应用服务A和应用服务B发起处理请求(比如rockemq,早期的rockemq版本不提供事务消息特性时,有些公司会开发一个可靠的基于MQ的消息服务来实现某些分布式事务特性)。在处理过程中,他们会分别操作自己的服务数据库,现在要求应用服务A和应用服务B的数据处理操作应该在一个事务中”?
在上面的例子中,如果采用两阶段提交来实现分布式事务,其操作原理应该是什么?(例如):
第一阶段:请求/投票阶段(点击放大)
既然叫两阶段提交,说明这个过程大致有两个阶段的处理流程。第一个阶段如图,称为请求/投票阶段。什么意思?
即分布式事务的发起者向分布式事务的协调者发送请求时,协调者会先向参与节点A和参与节点B发送事务预处理请求,称为准备,有些资料也称为“投票请求”。
说白了,问这些参与节点“这件事你能处理成功吗?”此时,这些参与节点一般会打开本地数据库事务,开始执行本地数据库事务,但不会在执行后立即提交本地数据库事务,而是先向协调者报告,说“这里我能处理/这里我不能处理。”。
如果所有参与节点都向协调器提供“投票提交”反馈,则流程将进入第二阶段。
第二阶段:提交/执行阶段(正常流程)
如果所有参与节点都向协调器报告“我可以在这里处理”,那么协调器将向所有参与节点发送一个“全局_提交”,也就是说,您可以提交本地事务。此时,参与节点将完成自己本地数据库事务的提交,最后用“ack”消息回复协调器提交结果,然后协调器将分布式事务完成结果返回给调用者。
第二阶段:提交/执行阶段(异常流程)
相反,在第二阶段,除了所有参与节点反馈“我这里可以处理”的情况外,还会出现部分节点反馈“我这里不能处理”的情况,然后参与节点会向协调节点反馈消息“投票_中止”。此时,分布式事务协调器节点将向所有参与节点发送事务回滚消息(“global_rollback”)。此时,每个参与节点将回滚本地事务,释放资源,并向协调节点发送“ack”确认消息,协调节点将向调用者返回分布式事务失败的结果。
事实上,分布式事务是一件非常复杂的事情。两阶段提交通过添加事务协调器的角色,只解决了分布式系统中一个事务需要跨多个服务节点的数据一致性问题。但是考虑到异常情况,这个过程并不是那么无懈可击。
假设在第二阶段,如果协调器收到participant的“Vote_Request”后挂机或者网络出现异常,那么participant节点就会一直处于本地事务挂起状态,从而长时间占用资源。当然,这种情况只会发生在极端情况下。但是作为一个健壮的软件系统,异常情况的处理才是真正检验方案正确性的地方。
以下几点是XA-两阶段提交协议中遇到的一些问题:
性能问题。从流程上我们可以看得出,其最大缺点就在于它的执行过程中间,节点都处于阻塞状态。各个操作数据库的节点此时都占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知进行全局提交,参与者进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。协调者单点故障问题。事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而导致参与者节点始终处于事务无法完成的中间状态。丢失消息导致的数据不一致问题。在第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题。既然两阶段提交有上述问题,还有其他解决方法吗?
五
三阶段提交(3PC)
三阶段提交,也称为3PC,在两阶段提交的基础上增加了CanCommit阶段,并引入了超时机制。一旦事务参与者长时间没有收到协调者的提交请求,就会自动进行局部提交,有效解决了协调者单点失败的问题。
然而,性能问题和不一致性并没有得到根本解决。我们来看看接下来的三个阶段是什么样的。
第一阶段:CanCommit阶段
这个阶段类似于2PC第二阶段的Ready阶段,是一个事务查询操作。交易的协调人问所有的参与者:“你能完成这笔交易吗?”,如果参与者NOde认为可以完成交易,则返回“YES”,否则返回“no”。在实际场景中,参与者节点将尝试自己的事务逻辑。其实说白了就是检查自己的状态健康,看自己有没有执行事务操作的能力。
第二阶段:预调试阶段
在第一阶段,如果所有参与者都返回是,他们将进入事务预提交的预提交阶段。此时分布式事务协调器会向所有参与节点发送预提交请求,参与节点收到后开始执行事务操作,并在事务日志中记录撤销和重做信息。参与者完成事务操作(此时处于未提交事务状态)后,会向协调者反馈“ack”,表示我准备提交,等待协调者下一条指令。
否则,如果第一阶段的任何参与者节点返回的结果是没有响应,或者协调器在等待参与者节点的反馈时超时(只有协调器可以在2PC超时,并且参与者没有超时机制)。整个分布式事务将被中断,协调器将向所有参与者发送“中止”请求。
第三阶段:DoCommit阶段
第二阶段,如果所有参与节点都可以进行预提交,协调者将从“预提交状态”->“提交状态”变为“提交状态”。然后向所有参与节点发送“doCommit”请求。参与节点收到提交请求后,将分别执行事务提交操作,并向协调节点反馈一条“ack”消息,协调节点收到所有参与节点的Ack消息后完成事务。
相反,如果一个参与节点未能完成预提交的反馈或反馈超时,协调器将向所有参与节点发送中止请求,从而中断事务。
看到这里,你会想“3PC相比2PC的优化在哪里?”
与2PC相比,3PC为协调器和参与器设置超时时间,而2PC只有协调器超时机制。这解决了什么问题?这个优化点主要避免了参与者在长时间无法与协调器节点通信(协调器挂机)时无法释放资源的问题,因为参与者本身有超时机制,超时后会自动进行本地提交释放资源。这种机制也减少了整个事务的阻塞时间和范围。
此外,通过CanCommit、PreCommit和DoCommit的设计,与2PC相比,多设置了一个缓冲阶段,保证最终提交阶段之前所有参与节点的状态一致。
六
薪酬交易
当谈到分布式事务的概念时,许多人感到困惑。好像分布式事务是TCC。其实TCC和2PC、3PC一样,只是分布式事务的一种实现方案。
TCC(Try-Confirm-Cancel)也叫补偿交易。其核心思想是:“对于每个操作,都要注册一个对应的确认和补偿(取消操作)”。它分为三个操作:
Try阶段:主要是对业务系统做检测及资源预留。Confirm阶段:确认执行业务操作。Cancel阶段:取消执行业务操作。TCC事务的处理流程类似于2PC两阶段提交,但是2PC通常是跨库的DB级,而TCC本质上是应用层的2PC,需要通过业务逻辑来实现。这种分布式事务实现的优点是,应用程序可以自己定义数据库操作的粒度,这使得减少锁冲突和提高吞吐量成为可能。
但缺点是对应用的侵入性很大,业务逻辑的每个分支都需要实现三个操作:尝试、确认、取消。此外,实施难度大,需要根据网络状态、系统故障等不同的故障原因实施不同的回滚策略。为了满足一致性的要求,确认和取消接口也必须实现幂等性。
变矩器离合器的具体示意图如下:
七
消息队列MQ事务
在介绍2PC和3PC的时候,我们说性能问题不能从根本上解决,但是通过MQ事务消息异步解耦,实现系统数据的最终一致性,会不会好很多?其实这就是我们在下一篇文章“如何基于RocketMQ的事务消息特性实现分布式系统的最终一致性”中要继续讲的内容。《请向前看!
—————结束————
程序员格雷
1.《2PC 分布式事务:深入理解什么是2PC、3PC及TCC协议》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《2PC 分布式事务:深入理解什么是2PC、3PC及TCC协议》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/jiaoyu/1320302.html