天天看点

不好意思,懂分布式事务的你真的很了不起,上篇

分布式事务这个话题,我相信对于身在互联网中的开发者们一定都不陌生。电商系统最容易出现分布式事务的处理,

拿用户在电商平台购买一个商品来切入今天的主题,用户首先下单,然后平台要扣减库存。创建订单方面的工作和库存的扣减工作一般都不在同一台机器上。而用户购买到商品的行为,必须要下单和扣减库存都成功,才算这次的交易成功,反之则失败。

01

分布式事务是什么?

作为开发的我们,语言不限,无论java还是php,肯定知道事务是什么,尤其是参与数据库比如MySql方面的开发,应该最能理解事务。

事务(Transaction),一般是指要做的或所做的事情,由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成,简单的讲就是,要么全部被执行,要么就全部失败。

那分布式事务,自然就是运行在分布式系统中的事务,是由多个不同的机器上的事务组合而成的。同上,只有分布式系统中所有事务执行了才能是成功,否则失败。

我们理解了分布式事务的基本含义,那想要深入理解分布式事务,就得先要搞清楚事务的基本特征ACID。

  • 原子性(Atomicity),一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
  • 一致性(Consistency),指事务执行前和执行后,数据是完整的。举个例子,我们两个人各有1000元,对于银行来说总共是2000元。

现在我给你转500元,这里就会涉及到两个步骤操作,一是我的账户减了500元变成了500元,另一个是你的账户里增加了500元变成了1500元,

总共还是2000元,这就是保证了一致性。但是不允许发生我的减了500元而你的没增加还是1000元,最后这个总数就变成了1500元,所以这就造成了不一致。

  • 隔离性(Isolation),一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(Durability),也称为永久性,一个事务一旦提交,它对数据库中数据的改变就应该是永久性的保存下来了。

02

如何实现分布式事务

首先,我们想一下分布式事务是为了解决分布式系统的什么问题?通过上面事务特征的介绍后,其实我们已经知道了,它就是为了解决分布式环境下,多个独立事务一致性问题。现在业界内常用的有3种方案实现分布式事务:

  • 二阶段提交协议形式,二阶段是基于XA协议的,采取强一致性,遵从ACID
  • 三阶段提交协议形式,采取强一致性,遵从ACID
  • 基于消息的最终一致性形式,采取最终一致性,遵从BASE理论

注:我想讲这三个方案讲的清晰点,因此我将这次分布式事务专题分为上下两篇来说,上篇主要讲二阶段和三阶段提交,下篇讲最终一致性方案。这样是为了节约大家宝贵时间并且不会造成学习疲劳。

二阶段提交形式

二阶段提交协议是协调所有分布式原子事务参与者,并决定提交或取消(回滚)的分布式算法。

二阶段提交中,系统内有两个角色,

  • 协调者
  • 参与者

协调者(老大),只有一个,由它来执行提交和回滚操作;参与者(小弟们),一般有多个,通常是由数据库来实现。

为了保证数据的一致性,所以我们需要有一个协调者(这里的算法就是我前面分享那篇分布式选主算法集中式,忘记的可以再去复习下)来管理所有的节点,来保证各个事务的正确提交,若是有提交失败的则放弃。下面我们来详细看看提交的过程。

二阶段提交过程

既然是二阶段,那也就是说它有两个执行步骤:

  • 投票(vote)
  • 提交(commit)

投票阶段,协调者会像所有参与者发送能提交(canCommit)的请求,然后,参与者收到请求后,会各自在本地执行自己的事务操作并记录行为,但自己并不真正提交,成功就会发送同意Yes指令给协调者,失败则发送终止No指令。

当协调者收到了所有参与者返回的指令yes或者no,则会进入真正提交阶段,这时候就会给协调者发送doCommit或者doAbort消息。

协调者收到参与者的全是yes消息,则会像参与者发送doCommit消息指令,此时参与者就会完成后续的操作真正的提交事务以及释放连接资源等,接着就会向协调者发送haveCommittd已经提交了的消息。

协调者收到的所有参与者中有no指令,那么之前yes的参与者会根据自己的日志进行回滚操作,然后就向协调者发送haveCommitted消息。

协调者收到了所有参与者的已经提交haveCommitted消息,则表明了整个分布式事务的结束。

好了,二阶段提交实现分布式事务,大家应该都很明白了,下面我觉得还是需要结合一个案例来说明一下这个过程帮助你更好的理解,真正做到心里不虚。

03

案例分析

我在我们平台商城里面购买一台Android手机,会涉及到我们的订单系统、库存系统两个主系统的协作。那么,我这样一个购买Android手机的行为怎么体现二阶段提交实现的分布式事务呢?

第一阶段,协调者会像参与者订单系统以及库存系统发送canCommit消息,然后订单系统就会进行订单的相关操作,如锁住订单库进行新增订单的操作,完成后就发送一个Yes消息给协调者;库存系统发现现在我的这款手机库存已经为0了,就会终止扣减库存方面的操作,发送No消息给协调者。

不好意思,懂分布式事务的你真的很了不起,上篇

第二阶段,协调者收到了库存系统失败的消息,则会给订单以及库存系统发送取消提交事务的消息。

此时,订单系统收到老大哥的取消提交事务的指令,就会将之前的操作进行回滚操作;然后订单系统和库存系统向老大协调者返回完成事务提交的消息HaveCommited。最后,本次事务完成。

不好意思,懂分布式事务的你真的很了不起,上篇

二阶段提交基本上满足了ACID特征,且实现的是数据的强一致性。但是它也是有缺点的,看过我前面的架构文章,应该很容易猜出了其中一个缺点吧,那就是单点故障,因为协调者只有一个(不能有多个老大的呀,可以复习下前面分布式选主算法哈),那还有有没有其他的缺点呢?肯定是有的啊:

  • 单点故障:事务的发起、提交还是取消,均是由老大协调者管理的,只要老大宕机,那就凉凉了。
  • 同步阻塞缺点:从上面介绍以及例子可看出,我们的参与系统中在没收到老大的真正提交还是取消事务指令的时候,就是锁定当前的资源,并不真正的做些事务相关操作,所以,整个分布式系统环境就是阻塞的。
  • 数据不一致缺点:就是说在老大协调者像小弟们发送真正提交事务的时候,部分网路故障,造成部分系统没收到真正的指令,那么就会出现部分提交部分没提交,因此,这就会导致数据的不一致。

04

三阶段提交

上面我们清楚了二阶段提交,也明白了它有哪些缺点。正是因为有些缺点,所以,就有了优化方案,那就是三阶段提交。那么,三阶段提交做出了哪些优化呢?

加入超时机制,其实架构思想都是想通的,我前面分享的高可用就也有超时机制。

加入预提交阶段,在第一阶段和第二阶段之间增加了一个预提交的阶段,其实我们就理解为中间缓冲的状态好明白了。

所以,现在三阶段提交就是有这三个主阶段,canCommit、preCommit、doCommit这三个阶段,下面我们来分析下:

第一阶段,canCommit

这第一阶段和上面二阶段提交中的第一阶段是类似的,那这个阶段中在分布式系统中事务的流程是怎样的?看下图:

不好意思,懂分布式事务的你真的很了不起,上篇

第二阶段,preCommit

当所有参与者向老大协调者发送Yes消息的时候,协调者就会发送preCommit指令给他们,告诉他们可以准备提交了,参与者就会做一些本地事务的真正业务操作,但是不真的提交,自己做完了就返回ACK确认消息回去。

当参与者有返回No消息的时候,老大协调者就会告诉小弟们Abort消息,小弟们收到了老大的中断事务提交的指令就会停止真正的事务提交动作。

不好意思,懂分布式事务的你真的很了不起,上篇

第三阶段,doCommit

到了这个阶段就是真正的事务提交部分了,然后宣告真个分布式事务结束。

不好意思,懂分布式事务的你真的很了不起,上篇

总结,今天我们学习到了分布式事务是什么,并且掌握了分布式事务实现的两种方式,二阶段提交以及三阶段提交。这两个方案都是为了实现数据的强一致性的。还有第三种方案基于消息的最终数据一致性,由于时间原因,我们放在下一篇进行讲述。

下一集预告:分布式事务最终数据一致性方案。

关于架构师修炼

本号旨在分享一线互联网各种技术架构解决方案,分布式以及高并发等相关专题,同时会将作者的学习总结进行整理并分享。

更多技术专题,敬请期待