你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
对于网上购物的每一笔订单来说,电商平台一般都会有两个核心步骤:一是订单业务采取下订单操作,二是库存业务采取减库存操作。
通常,这两个业务会运行在不同的机器上,甚至是运行在不同区域的机器上。针对同一笔订单,当且仅当订单操作和减库存操作一致时,才能保证交易的正确性。也就是说一笔订单,只有这两个操作都完成,才能算做处理成功,否则处理失败,充分体现了“All or nothing”的思想。
在分布式领域中,这个问题就是分布式事务问题。那么今天,我们就一起打卡分布式事务吧。
在介绍分布式事务之前,我们首先来看一下什么是事务。
事务(Transaction)提供一种机制,将包含一系列操作的工作序列纳入到一个不可分割的执行单元。只有所有操作均被正确执行才能提交事务;任意一个操作失败都会导致整个事务回滚(Rollback)到之前状态,即所有操作均被取消。简单来说,事务提供了一种机制,使得工作要么全部都不做,要么完全被执行,即all or nothing。
通常情况下,我们所说的事务指的都是本地事务,也就是在单机上的事务。而事务具备四大基本特征ACID,具体含义如下。
只有在数据操作请求满足上述四个特性的条件下,存储系统才能保证处于正确的工作状态。因此,无论是在传统的集中式存储系统还是在分布式存储系统中,任何数据操作请求都必须满足 ACID 特性。
分布式事务,就是在分布式系统中运行的事务,由多个本地事务组合而成。在分布式场景下,对事务的处理操作可能来自不同的机器,甚至是来自不同的操作系统。文章开头提到的电商处理订单问题,就是典型的分布式事务。
分布式事务由多个事务组成,因此基本满足ACID,其中的C是强一致性,也就是所有操作均执行成功,才提交最终结果,以保证数据一致性或完整性。但随着分布式系统规模不断扩大,复杂度急剧上升,达成强一致性所需时间周期较长,限定了复杂业务的处理。为了适应复杂业务,出现了BASE理论,该理论的一个关键点就是采用最终一致性代替强一致性。我会在“知识扩展”模块与你详细展开BASE理论这部分内容。
介绍完什么是事务和分布式事务,以及它们的基本特征后,就进入“怎么做”的阶段啦。所以接下来,我们就看看如何实现分布式事务吧。
实际上,分布式事务主要是解决在分布式环境下,组合事务的一致性问题。实现分布式事务有以下3种基本方法:
其中,基于XA协议的二阶段提交协议方法和三阶段提交协议方法,采用了强一致性,遵从ACID。基于消息的最终一致性方法,采用了最终一致性,遵从BASE理论。下面,我将带你一起学习这三种方法。
XA是一个分布式事务协议,规定了事务管理器和资源管理器接口。因此,XA协议包括事务管理器和本地资源管理器两个部分。
XA实现分布式事务的原理,就类似于我在第3讲中与你介绍的集中式算法:事务管理器相当于协调者,负责各个本地资源的提交和回滚;而资源管理器就是分布式事务的参与者,通常由数据库实现,比如Oracle、DB2等商业数据库都实现了XA接口。
基于 XA协议的二阶段提交方法中,二阶段提交协议(Two-phase Commit Protocol,2PC),用于保证分布式系统中事务提交时的数据一致性,是XA在全局事务中用于协调多个资源的机制。
那么,两阶段提交协议如何保证分布在不同节点上的分布式事务的一致性呢?为了保证它们的一致性,我们需要引入一个协调者来管理所有的节点,并确保这些节点正确提交操作结果,若提交失败则放弃事务。接下来,我们看看两阶段提交协议的具体过程。
两阶段提交协议的执行过程,分为投票(Voting)和提交(Commit)两个阶段。
首先,我们看一下第一阶段投票:在这一阶段,协调者(Coordinator,即事务管理器)会向事务的参与者(Cohort,即本地资源管理器)发起执行操作的CanCommit请求,并等待参与者的响应。参与者接收到请求后,会执行请求中的事务操作,将操作信息记录到事务日志中但不提交(即不会修改数据库中的数据),待参与者执行成功,则向协调者发送“Yes”消息,表示同意操作;若不成功,则发送“No”消息,表示终止操作。
当所有的参与者都返回了操作结果(Yes或No消息)后,系统进入了第二阶段提交阶段(也可以称为,执行阶段)。在提交阶段,协调者会根据所有参与者返回的信息向参与者发送DoCommit(提交)或DoAbort(取消)指令。具体规则如下:
接下来,我以用户A要在网上下单购买100件T恤为例,重点与你介绍下单操作和减库存操作这两个操作,帮助你加深对二阶段提交协议的理解。
第一阶段:订单系统中将与用户A有关的订单数据库锁住,准备好增加一条关于用户A购买100件T恤的信息,并将同意消息“Yes”回复给协调者。而库存系统由于T恤库存不足,出货失败,因此向协调者回复了一个终止消息“No”。
第二阶段:由于库存系统操作不成功,因此,协调者就会向订单系统和库存系统发送“DoAbort”消息。订单系统接收到“DoAbort”消息后,将系统内的数据退回到没有用户A购买100件T恤的版本,并释放锁住的数据库资源。订单系统和库存系统完成操作后,向协调者发送“HaveCommitted”消息,表示完成了事务的撤销操作。
至此,用户A购买100件T恤这一事务已经结束,用户A购买失败。
由上述流程可以看出,二阶段提交的算法思路可以概括为:协调者向参与者下发请求事务操作,参与者接收到请求后,进行相关操作并将操作结果通知协调者,协调者根据所有参与者的反馈结果决定各参与者是要提交操作还是撤销操作。
虽然基于XA的二阶段提交算法尽量保证了数据的强一致性,而且实现成本低,但依然有些不足。主要有以下三个问题:
三阶段提交协议(Three-phase Commit Protocol,3PC),是对二阶段提交(2PC)的改进。为了更好地处理两阶段提交的同步阻塞和数据不一致问题,三阶段提交引入了超时机制和准备阶段。
三阶段提交协议就有CanCommit、PreCommit、DoCommit三个阶段,下面我们来看一下这个三个阶段。
第一,CanCommit阶段。
协调者向参与者发送请求操作(CanCommit请求),询问参与者是否可以执行事务提交操作,然后等待参与者的响应;参与者收到CanCommit请求之后,回复Yes,表示可以顺利执行事务;否则回复No。
3PC的CanCommit阶段与2PC的Voting阶段相比:
CanCommit阶段不同节点之间的事务请求成功和失败的流程,如下所示。
当协调者接收到所有参与者回复的消息后,进入预提交阶段(PreCommit阶段)。
第二,PreCommit阶段。
协调者根据参与者的回复情况,来决定是否可以进行PreCommit操作(预提交阶段)。
如果所有参与者回复的都是“Yes”,那么协调者就会执行事务的预执行:
协调者向参与者发送PreCommit请求,进入预提交阶段。
参与者接收到PreCommit请求后执行事务操作,并将Undo和Redo信息记录到事务日志中。
如果参与者成功执行了事务操作,则返回ACK响应,同时开始等待最终指令。
假如任何一个参与者向协调者发送了“No”消息,或者等待超时之后,协调者都没有收到参与者的响应,就执行中断事务的操作:
协调者向所有参与者发送“Abort”消息。
参与者收到“Abort”消息之后,或超时后仍未收到协调者的消息,执行事务的中断操作。
预提交阶段,不同节点上事务执行成功和失败的流程,如下所示。
预提交阶段保证了在最后提交阶段(DoCmmit阶段)之前所有参与者的状态是一致的。
第三,DoCommit阶段。
DoCmmit阶段进行真正的事务提交,根据PreCommit阶段协调者发送的消息,进入执行提交阶段或事务中断阶段。
执行提交阶段:
若协调者接收到所有参与者发送的Ack响应,则向所有参与者发送DoCommit消息,开始执行阶段。
参与者接收到DoCommit消息之后,正式提交事务。完成事务提交之后,释放所有锁住的资源,并向协调者发送Ack响应。
协调者接收到所有参与者的Ack响应之后,完成事务。
事务中断阶段:
协调者向所有参与者发送Abort请求。
参与者接收到Abort消息之后,利用其在PreCommit阶段记录的Undo信息执行事务的回滚操作,释放所有锁住的资源,并向协调者发送Ack消息。
协调者接收到参与者反馈的Ack消息之后,执行事务的中断,并结束事务。
执行阶段不同节点上事务执行成功和失败(事务中断)的流程,如下所示。
3PC协议在协调者和参与者均引入了超时机制。即当参与者在预提交阶段向协调者发送 Ack消息后,如果长时间没有得到协调者的响应,在默认情况下,参与者会自动将超时的事务进行提交,从而减少整个集群的阻塞时间,在一定程度上减少或减弱了2PC中出现的同步阻塞问题。
但三阶段提交仍然存在数据不一致的情况,比如在PreCommit阶段,部分参与者已经接受到ACK消息进入执行阶段,但部分参与者与协调者网络不通,导致接收不到ACK消息,此时接收到ACK消息的参与者会执行任务,未接收到ACK消息且网络不通的参与者无法执行任务,最终导致数据不一致。
2PC和3PC核心思想均是以集中式的方式实现分布式事务,这两种方法都存在两个共同的缺点,一是,同步执行,性能差;二是,数据不一致问题。为了解决这两个问题,通过分布式消息来确保事务最终一致性的方案便出现了。
在eBay的分布式系统架构中,架构师解决一致性问题的核心思想就是:将需要分布式处理的事务通过消息或者日志的方式异步执行,消息或日志可以存到本地文件、数据库或消息队列中,再通过业务规则进行失败重试。这个案例,就是使用基于分布式消息的最终一致性方案解决了分布式事务的问题。
基于分布式消息的最终一致性方案的事务处理,引入了一个消息中间件(在本案例中,我们采用Message Queue,MQ,消息队列),用于在多个应用之间进行消息传递。实际使用中,阿里就是采用RocketMQ 机制来支持消息事务。
基于消息中间件协商多个节点分布式事务执行操作的示意图,如下所示。
仍然以网上购物为例。假设用户A在某电商平台下了一个订单,需要支付50元,发现自己的账户余额共150元,就使用余额支付,支付成功之后,订单状态修改为支付成功,然后通知仓库发货。
在该事件中,涉及到了订单系统、支付系统、仓库系统,这三个系统是相互独立的应用,通过远程服务进行调用。
根据基于分布式消息的最终一致性方案,用户A通过终端手机首先在订单系统上操作,通过消息队列完成整个购物流程。然后整个购物的流程如下所示。
在上述过程中,可能会产生如下异常情况,其对应的解决方案为:
基于分布式消息的最终一致性方案采用消息传递机制,并使用异步通信的方式,避免了通信阻塞,从而增加系统的吞吐量。同时,这种方案还可以屏蔽不同系统的协议规范,使其可以直接交互。
在不需要请求立即返回结果的场景下, 这些特性就带来了明显的通信优势,并且通过引入消息中间件,实现了消息生成方(如上述的订单系统)本地事务和消息发送的原子性,采用最终一致性的方式,只需保证数据最终一致即可,一定程度上解决了二阶段和三阶段方法要保证强一致性而在某些情况导致的数据不一致问题。
可以看出,分布式事务中,当且仅当所有的事务均成功时整个流程才成功。所以,分布式事务的一致性是实现分布式事务的关键问题,目前来看还没有一种很简单、完美的方案可以应对所有场景。
现在,为了方便你理解并记忆这三种方法,我总结了一张表格,从算法一致性、执行方式、性能等角度进行了对比:
在讨论事务的时候,我们经常会提到刚性事务与柔性事务,但却很难区分这两种事务。所以,今天的知识扩展内容,我就来和你说说什么是刚性事务、柔性事务,以及两者之间有何区别?
总结来讲,与刚性事务不同,柔性事务允许一定时间内,数据不一致,但要求最终一致。而柔性事务的最终一致性,遵循的是BASE理论。
那,什么是BASE理论呢?
eBay 公司的工程师 Dan Pritchett曾提出了一种分布式存储系统的设计模式——BASE理论。 BASE理论包括基本可用(Basically Available)、柔性状态(Soft State)和最终一致性(Eventual Consistency)。
BASE理论为了支持大型分布式系统,通过牺牲强一致性,保证最终一致性,来获得高可用性,是对ACID原则的弱化。ACID 与 BASE 是对一致性和可用性的权衡所产生的不同结果,但二者都保证了数据的持久性。ACID 选择了强一致性而放弃了系统的可用性。与ACID原则不同的是,BASE理论保证了系统的可用性,允许数据在一段时间内可以不一致,最终达到一致状态即可,也即牺牲了部分的数据一致性,选择了最终一致性。
具体到今天的三种分布式事务实现方式,二阶段提交、三阶段提交方法,遵循的是ACID原则,而消息最终一致性方案遵循的就是BASE理论。
我从事务的ACID特性出发,介绍了分布式事务的概念、特征,以及如何实现分布式事务。在关于如何实现分布式的部分,我以网购为例,与你介绍了常见的三种实现方式,即基于XA协议的二阶段提交方法,三阶段方法以及基于分布式消息的最终一致性方法。
二阶段和三阶段方法是维护强一致性的算法,它们针对刚性事务,实现的是事务的ACID特性。而基于分布式消息的最终一致性方案更适用于大规模分布式系统,它维护的是事务的最终一致性,遵循的是BASE理论,因此适用于柔性事务。
在分布式系统的设计与实现中,分布式事务是不可或缺的一部分。可以说,没有实现分布式事务的分布式系统,不是一个完整的分布式系统。分布式事务的实现过程看似复杂,但将方法分解剖析后,你就会发现分布式事务的实现是有章可循的。
我将实现分布式事务常用的三个算法整理为了一张思维导图,以帮助你加深理解与记忆。
你觉得分布式互斥与分布式事务之间的关系是什么呢?
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!