事务,其实是包含一系列操作的、一个有边界的工作序列,有明确的开始和结束标志,且要么被完全执行,要么完全失败,即 all or nothing。通常情况下,我们所说的事务指的都是本地事务,也就是在单机上的事务。
而分布式事务,就是在分布式系统中运行的事务,由多个本地事务组合而成。在分布式场景下,对事务的处理操作可能来自不同的机器,甚至是来自不同的操作系统。文章开头提到的电商处理订单问题,就是典型的分布式事务。
要深入理解分布式事务,首先需要了解它的特征。分布式事务是多个事务的组合,那么事务的特征 ACID,也是分布式事务的基本特征,其中 ACID 具体含义如下:
分布式事务基本能够满足 ACID,其中的 C 是强一致性,也就是所有操作均执行成功,才提交最终结果,以保证数据一致性或完整性。但随着分布式系统规模不断扩大,复杂度急剧上升,达成强一致性所需时间周期较长,限定了复杂业务的处理。为了适应复杂业务,出现了 BASE 理论,该理论的一个关键点就是采用最终一致性代替强一致性,最终一致性也称为弱一致性。
实际上,分布式事务主要是解决在分布式环境下,组合事务的一致性问题。实现分布式事务有以下 3 种基本方法:
XA 是一个分布式事务协议,规定了事务管理器和资源管理器接口。因此,XA 协议可以分为两部分,即事务管理器和本地资源管理器。
XA 实现分布式事务的原理,类似于集中式算法:事务管理器作为协调者,负责各个本地资源的提交和回滚;而资源管理器就是分布式事务的参与者,通常由数据库实现,比如 Oracle、DB2 等商业数据库都实现了 XA 接口。
基于 XA 协议的二阶段提交方法中,二阶段提交协议(The 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 的二阶段提交算法基本满足了事务的 ACID 特性,但存在以下缺点:
三阶段提交协议(Three-phase commit protocol,3PC),是对二阶段提交(2PC)的改进。为了解决两阶段提交的同步阻塞和数据不一致问题,三阶段提交引入了超时机制和准备阶段。
也就是说,除了引入超时机制之外,3PC 把 2PC 的提交阶段一分为二,这样三阶段提交协议就有 CanCommit、PreCommit、DoCommit 三个阶段。
第一:CanCommit 阶段:
CanCommit 阶段与 2PC 的投票阶段类似:协调者向参与者发送请求操作(CanCommit 请求),询问参与者是否可以执行事务提交操作,然后等待参与者的响应;参与者收到 CanCommit 请求之后,回复 Yes,表示可以顺利执行事务;否则回复 No。
CanCommit 阶段不同节点之间的事务请求成功和失败的流程,如下所示
第二,PreCommit 阶段:
协调者根据参与者的回复情况,来决定是否可以进行 PreCommit 操作。
第三,DoCommit 阶段:
DoCmmit 阶段进行真正的事务提交,根据 PreCommit 阶段协调者发送的消息,进入执行提交阶段或事务中断阶段。
执行提交阶段:
事务中断阶段:
执行阶段不同节点上事务执行成功和失败 (事务中断) 的流程,如下所示:
在 DoCommit 阶段,当参与者向协调者发送 Ack 消息后,如果长时间没有得到协调者的响应,在默认情况下,参与者会自动将超时的事务进行提交,不会像两阶段提交那样被阻塞住。
2PC 和 3PC 这两种方法,有两个共同的缺点:
因此,便有了通过分布式消息来确保事务最终一致性的方案——基于分布式消息的最终一致性方案解决了分布式事务的问题。
基于分布式消息的最终一致性方案的事务处理,引入了一个消息中间件(Message Queue,MQ),用于在多个应用之间进行消息传递。基于消息中间件协商多个节点分布式事务执行操作的示意图,如下所示:
仍然以网上购物为例。假设用户 A 在某电商平台下了一个订单,需要支付 50 元,发现自己的账户余额共 150 元,就使用余额支付,支付成功之后,订单状态修改为支付成功,然后通知仓库发货。
在该事件中,涉及到了订单系统、支付系统、仓库系统,这三个系统是相互独立的应用,通过远程服务进行调用。
根据基于分布式消息的最终一致性方案,用户 A 通过终端手机首先在订单系统上操作,然后整个购物的流程如下所示。
可以看出,分布式事务中,当且仅当所有的事务均成功时整个流程才成功。所以,分布式事务的一致性是实现分布式事务的关键问题,目前来看还没有一种很简单、完美的方案可以应对所有场景。
现在,为从算法一致性、执行方式、性能等角度对以上三种分布式事务进行了对比:
在讨论事务的时候,经常会提到刚性事务与柔性事务,但却很难区分这两种事务。下面将说明两者之间的区别
总结来讲,与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致。而柔性事务的最终一致性,遵循的是 BASE 理论。
BASE 理论包括基本可用(Basically Available)、柔性状态(Soft State)和最终一致性(Eventual Consistency)。
可见,BASE 理论为了支持大型分布式系统,通过牺牲强一致性,保证最终一致性,来获得高可用性,是对 ACID 原则的弱化。具体到今天的三种分布式事务实现方式,二阶段提交、三阶段提交方法,遵循的是 ACID 原则,而消息最终一致性方案遵循的就是 BASE 理论。
你介绍了常见的三种分布式事务实现方式,即基于 XA 协议的二阶段提交方法,三阶段方法以及基于分布式消息的最终一致性方法。二阶段和三阶段方法是维护强一致性的算法,它们针对刚性事务,实现的是事务的 ACID 特性。而基于分布式消息的最终一致性方案更适用于大规模分布式系统,它维护的是事务的最终一致性,遵循的是 BASE 理论,因此适用于柔性事务。