当前位置: 首页 > 科技观察

面试官问我分布式事务,感觉他有给我40k的冲动

时间:2023-03-20 22:37:57 科技观察

本文转载自微信公众号《11:30的小黑》,作者是楼下小黑哥。转载本文请联系小黑11:30公众号。在这次使用分布式事务框架的过程中,学习了一些分布式事务的知识,所以在这篇文章中我们就来谈谈分布式事务。首先,让我们回顾一下什么是交易。什么是交易?作为后端开发,在日常开发中只要和数据库有交互,肯定会用到事务。现在摘录一段wiki解释来解释什么是交易。它是数据库管理系统执行过程中的一个逻辑单元。它由有限的数据库操作序列组成。数据库系统具有事务特性,这是区别于文件系统的一个重要特性。在传统的文件系统中,如果正在写入文件,操作系统突然崩溃,此时文件可能已经损坏。数据库系统引入了事务特性,可以保证数据库从一种状态转换到另一种状态。提交作业时,您可以确保保存所有更改或不保存任何更改。通常一个事务会包含多个读写操作。事务有四个基本属性,通常称为ACID。A(Atomicity):原子性。事务会被视为一个整体,要么所有语句都成功,要么全部失败,不能有语句成功有语句失败。C(一致性):一致性。数据库的状态从一种状态变为另一种状态,在事务开始前和事务结束后,数据库的完整性约束保持不变。什么是数据库完整性约束不变?例如,如果表的name字段是唯一约束,如果在事务提交或回滚后name字段变为非唯一,这将破坏数据库的完整性约束。I(Isolation):隔离。多个并发事务在互不影响的情况下执行。D(耐久性):持久性。事务提交后,其对数据库的修改可以永久保存在数据库中。因此,这个特性要求数据库系统在崩溃时即使需要恢复也不能丢失提交的数据。所以早期我们的系统只有一个数据源。这时候,我们就可以依靠数据库系统事务来保证业务的正确性。但是随着业务的不断扩大,我们业务的单表可能有千万级的数据,使用另外的数据库实例时可能会出现性能相关的性能问题。这时候我们就要考虑分库分表了。但这可能会导致单个应用程序连接到多个数据源的情况。下图显示了一个示例。在上图中的购买流程中,商户余额表和用户余额表在两个独立的数据库实例中,这样分开的事务可以保证商户余额或用户余额的扣减成功或不成功。但是我们不能保证两个事务同时成功或失败。还有一种情况,随着系统越来越大,我们会选择将系统应用拆分成多个微服务,让单个应用只操作一个数据源。这时候我们会遇到这样的情况,一个业务调用会调用多个应用,每个应用独立操作数据源,如下图所示。在这种情况下,我们无法保证所有调用都会成功。从上面的例子我们可以看出,随着业务的发展,传统的单机事务已经不能满足我们的业务需求。这时候就需要分布式事务来保证。分布式事务摘自wiki解释。分布式事务是涉及两个或多个网络主机的数据库事务。先说说实现分布式事务的一些理论基础。分布式事务技术理论CAP定理。在分布式系统中(指相互连接并共享数据的节点集合),当涉及到读写操作时,只能保证一致性、可用性和分区容忍度这两者,必须牺牲另一个。极客时间学习架构摘自0第22章解释虽然CAP的理论定义是三个元素中只能选择两个,但是当我们在分布式环境中思考时,我们会发现我们必须选择P(分区tolerance)元素,因为网络本身不可能100%可靠,有可能发生故障,所以分区是必然现象。如果我们选择CA而放弃P,那么当发生分区时,为了保证C,系统需要禁止写入。当有写请求时,系统返回错误(比如当前系统不允许写),这又与A冲突,因为A要求不返回错误,不超时。所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构BASE理论,它们是后面三个词的缩写。基本可用:当分布式系统发生故障时,允许丢失一些可用的功能,以保证核心功能可用。软状态(softstate):允许系统中存在中间状态,这种状态不影响系统的可用性,这里指的是CAP中的不一致性。最终一致:最终一致是指经过一段时间后,所有节点的数据都会达到一致性。BASE是CAP中AP方案的补充。在BASE中使用软状态和最终一致性确保延迟后的一致性。BASE和ACID是相反的。ACID是强一致性模型,而BASE牺牲了这种强一致性,让数据在短时间内不一致,最终一致。接下来我们看一下分布式事务的实现。分布式事务实现方案是基于数据库资源层的2PC两阶段提交协议3PC三阶段提交协议是基于业务层的TCC是基于数据库资源层的实现方案。由于有多个事务,我们需要一个角色来管理每个事务的状态。我们称这个角色为协调者,交易参与者为参与者。参与者和协调者一般都是基于特定的协议,目前比较著名的是XA接口协议。基于协调者和参与者的思想设定,分别提出了2PC和3PC来实现XA分布式事务。2PC两阶段提交协议顾名思义,这个过程主要分为两步。在第一阶段,协调器(事务管理器)会涉及到事务的预提交,此时数据库资源会开始被锁定。参与者将撤消和重做写入事务日志。第二阶段,参与者(资源管理者)提交事务,或者使用undolog回滚事务,释放资源。整个过程如下图所示。分布式事务提交成功场景:分布式事务回滚场景:该方案的优点是:实现比较简单,主流数据库支持,一致性强。MySQL5.5之后,基于XA协议实现。相应的,这个方案也有缺点:协调器的单点问题。如果提交阶段协调器宕机,参与者一直在等待,资源已经被锁定阻塞。虽然协调者可以连任,但是这个问题无法解决。同步阻塞时间过长,事务在整个执行过程中都处于阻塞状态,直到commit完成,资源释放。如果在提交过程/回滚过程中,由于网络延迟,参与者没有收到指令,则该参与者已被阻止。数据不一致。第二阶段,当协调器发出第一个提交信号后宕机,第一个参与者提交事务,第二个参与者因为没有收到协调器信号而不能提交事务。因此,针对2PC的不足,提出了改进方案3PC。3PC三阶段提交协议是三阶段提交,在两阶段提交的基础上,对两阶段进行了改进。三阶段步骤如下。1..CanCommit,协调者询问参与者是否可以提交事务。2.PreCommit,如果所有参与者都可以提交事务,协调者发出PreCommit命令,参与者锁定资源等待最终命令。所有参与者返回确认信息,协调器向每个事务发送事务执行通知,锁定资源,并返回执行状态。一些参与者返回负面信息或协调者等待超时。这种情况下,协调器认为事务不能正常执行,发出中断命令,各参与者退出准备状态3.DoCommit,如果第二阶段都响应ack,则发出DoCommit,最后提交事务,否则发出中断事务命令,所有参与者执行事务回滚。所有参与者正常执行事务,协调者发出最终提交命令释放锁定的资源。部分参与者执行事务失败,协调器等待超时,协调器发出回滚命令释放锁定的资源。详情见下图。三阶段提交对比两个阶段,引入超时机制,减少事务阻塞,解决单点故障。第三阶段,一旦参与者没有收到协调者的信号,等待超时后,参与者默认执行commit释放资源。三阶段仍然不能解决数据一致性问题。如果协调者发送回滚命令,但由于网络问题,参与者在等待时间内无法收到,则默认参与者提交事务,其他事务被回滚,导致事务不一致。TCCTCC事务为了解决事务运行时大粒度的资源锁定问题,业界提出了一种新的事务模型,它基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质上是一种补偿方式。它将交易运行过程分为两个阶段:Try和Confirm/Cancel。每个阶段的逻辑由业务代码控制。这样就可以完全自由控制事务的锁粒度。企业可以以隔离为代价获得更高的性能。TCC分别是Trying、Confirm、Cancel的缩写。与基于数据库级别的2PC和3PC不同,TCC是基于应用程序级别的。TCC的三个动作分别是:Trying:完成所有业务检查(一致性)预留必要的业务资源(准隔离)Confirm:实际执行业务Confirm操作满足幂等性Cancel:释放Try阶段预留的业务资源Cancel操作必须satisfyidempotency上面的说法听起来有点生涩难懂。没关系,我们用实际案例来解释。下面我们模拟一个商城的支付流程。用户下单采用组合支付方式,即余额加红包支付。一个正常的流程是:创建订单,下单,调用余额系统,扣除余额,调用红包系统,扣除红包余额,修改订单状态为已支付,完成后支付。实际流程如下图所示。但是这样的支付流程会调用多个子服务,我们不能保证所有的服务都会成功。比如我们调用红包系统失败扣红包系统。这时,我们遇到了尴尬的一幕。由于红包服务失败,方法异常退出。此时订单状态为初始状态,但已扣除用户余额。这对用户体验非常不友好。所以在这个支付过程中,我们必须要有一种机制,把这个过程当作一个整体的行为来对待,我们要保证这个过程中的服务调用要么成功,要么失败,成为一个整体的交易。这时候,我们可以引入TCC事务,将整个下单流程作为一个整体。引入后,由于余额系统扣款失败,我们此时回滚了订单系统和红包系统。整个过程如下图所示。由于余额系统出现故障,我们需要撤销这个过程中的所有更改,所以我们向订单系统发送取消通知,向红包系统发送取消通知。因此,系统引入TCC事务后,我们需要修改我们的调用流程。系统如何引入TCC交易根据TCC交易的三个步骤,此时我们必须将每个服务转化为TryConfirmCancel三个步骤,TCCTRY:订单系统根据上述业务增加一个try方法来改变订单状态为PAYING。余额系统增加一个try方法,先检查余额是否足够,然后先扣除余额,再将扣除的余额增加到冻结金额。红包系统与余额系统相同。从转换过程可以看出,TCC的try方法需要检查各种业务资源,这个过程需要引入一个中间状态。我们按照下图来看一下整个过程。TCCConfirm:TCC第一步是TRY。如果所有的子服务调用都成功了,此时我们需要对每一个服务进行确认。每个服务添加一个确认方法。比如使用余额系统的confirm方法设置冻结金额为0,红包系统如上。订单系统修改订单状态为SUCCESS。confirm方法需要小心实现幂等性。如果更新订单系统,必须先确定订单状态为PAYING,才能更新订单。整个过程如下图所示。说到这里,必须使用TCC事务框架来驱动服务。TCC事务管理器感知到TRY方法结束后,自动调用各个服务提供的confirm方法,将各个服务的状态修改为最终状态。TCCCancle:如果在TCCTry过程中,冻结红包的方法失败了,那么我们需要撤销之前的所有修改,修改为初始状态。cancle方法也需要幂等,比如confirm方法,如下图所示:看到这里,我们可以看出,如果TCCTry成功,confirm一定成功,try失败,cancle一定成功。因为confirm是系统更新到最终状态的关键。但现实就是这么无情,生产系统confirm或者cancel肯定会失败。此时TCC框架需要记录调用confirm的结果。如果confirm调用失败,TCC框架需要记录下来,然后每隔一定时间再次调用。总结与思考看完全文,分布式事务你一定知道了。我们在此基础上得出结论。要使用分布式事务,我们需要结合自己的实际场景来应用。如果业务还处于起步阶段,我们其实可以选择数据库事务来保证快速上线迭代。当业务发展到一定阶段,系统开始拆分,数据库也随之拆分。这时候,如果业务需要保证一致性,那么就必须使用分布式事务。这时候我们在使用分布式事务的时候,需要根据业务来考虑使用哪一种。使用2PC或3PC实现的分布式框架,业务应用层不需要改动,接入也比较简单。但相应的性能较低,数据资源锁时间较长。不适合互联网等高并发业务场景。但是使用基于TCC的分布式框架比2PC性能更高,并且可以保证数据的最终一致性。但是对于应用层来说,一个方法要转化为三个方法,需要在业务中引入一些中间状态,相对来说应用转化的程度比较大。