使用Dubbo前你可能要了解的知识系列(1)----分布式和ZooKeeper

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/dwdyoung/article/details/80726883
在讲分布式之前,我要先讲一个事务的概念。
什么是事务?
举个例子,平常我们上网买东西的时候,当我们输入密码,点击最后一下确认支付,这个请求提交到后台时候,后台都了做些什么?它可能做了
1、在我的账户上,把我的余额减去商品的费用。
2、在商家的数据库里,把商品的数量-1
3、在我的账户上,添加一条新的订单


初看好像没什么问题。
但仔细看一下,如果后台在执行完第一条的时候,突然收到这个商品下架的消息,接着把商品数量设成0,这时候再执行第2条,那么商品的数量就会变成了-1。
很显然这是不对的!
所以作为事务的第一条特性是,我们称之为原子性Automicity,就是像原子一样,已经分裂的不能再分裂的操作,要么不执行,要么全部完成。


ok,那么现在出现另外一个问题,同样如果后台在执行完第一条之后,由于软件的异常,崩溃了,客户的余额是不是就无缘无故的减少,但是又没买到商品?
很显然这也是不对的!
作为事务的第二条特性是,我们称之为一致性Consistency,意思是,事务的操作只能从一种一致性的状态,转换成另一种一致性的状态,没有中间的状态。如果中间操作有异常,那么就全部回滚到前一种状态。


现在又有一个问题了,这个商品非常抢手,商品只有10件,但是有1000个人同时下单购买,由于第二个操作,商品不停的-1,-1,最后导致商品数竟然变成了-856。我相信这个情况,在高并发环境下,作为开发人员是非常能理解的。
所以作为事务的第3条特性是,我们称之为隔离性Isolation,做完一个再一个,相互不能干扰。


有时候我们开发人员喜欢把数据放到缓存里,以上123步都做完了,我严格遵循原子性、一致性、隔离性来操作,最后一刻,后台突然挂掉,需要重启。重启完毕之后,上次操作的数据居然都失效了。
这里介绍事务最后一种特性,叫持久性(Durability),事务操作是永久性的,即便后台挂掉,也不会丢失事务的操作。

以上4种特性就是描述一个事务的基本特性,合称ACID。这个有什么用呢,当我们描述一个操作是不是事务时,就会用ACID来评判它,如果是,它就是一个事务,如果不符合其中一条,就不是一个事务

但是严格来讲,上面那句话是不对的,等下会讲到为什么。

ACID在单独的一条进程中是非常容易实现的,软件上可以用各种的锁对进程进行控制,或者例如我们可以单独用一条线程去访问和操作数据,那就永远都不会有另外别的东西跟这个线程抢资源,完全的事务化。


但是在分布式的开发中,情况就不一样了。

这里先简单的说一下分布式是个什么东西。

简单来讲,分布式就是多台电脑工作,代替以往1台电脑来用。生活中我们往往有这样的例子,做一件事情,4个人做的效率,一般会比1个人做要快。同样的,是不是3台服务器的效率就比1台电脑效率要高呢。

我CPU是单台CPU的3倍,内存是单台内存的3倍,以往,一台服务器可以接收300个连接,3台服务器就能接收900个连接。而且运维上也有一定的好处,3台服务器里面,如果有一台挂掉了,也不至于整个网络都瘫痪。


但是在这种环境(分布式环境)下,要实现一个事务就很难了。我先将这3台服务器分别命名为:
AS,BS,CS


首先数据一致性就是一个问题了。
现在有个事务提交的AS,AS把这个事务操作完毕之后,为了保持数据的一致性,会把这个事务同样发送到BS和CS,那么问题来了,谁也不能保证BS和CS一定会成功操作这个事务,说不定BS成功操作完事务,但是CS却操作失败被迫回滚,那么这时候,整个分布式系统就会呈现出一种非一致性状态。


那么有些人可能要问了,那就把数据全部统一保存在AS上不就行了吗,那就省去了多台服务器数据保持一致的问题。但是这样也同样带来其他问题,第一点,数据统一保存在AS上本身就已经失去了分布式的意义,整个系统中,万一AS突然挂掉怎么办,整个系统都会瘫痪。再说,最终处理数据依然是一台服务器,根本没有起到分工合作的作用。


苦思冥想之下,在2000年,有一位教授就提出了一种名叫CAP理论。
CAP分别代表,一致性(Consistency),可用性(Availability),分区容错性(Partition tolerance)。
这种理论描述了,在一个分布式存储系统中,最多只能实现CAP里面的2点。就是说一个分布式不可能完美的执行一个事务。CAP里面只能取其2,那些过于追求完美执行一个事务的系统,最终都会以失败告终。而且由于需要防止硬件的突然出现问题,分区容错性是必须要保留的,作为开发者,我们只能在一致性和可用性之间平衡。


那么基于这个理论,就有人提出了一些分布式存储算法,其中比较流行的就是分布式二阶算法和分布式三阶算法。
二阶算法,顾名思义,这个算法有2个阶段。

第一个阶段:准备阶段
这个集群某一台特别的服务器,我们称之为领导者(Leader),给所有其他所有的服务器跟随者(Follower)发送一条PREPARE消息,附带事务所带的数据,所有收到Prepare的服务器进入一个准备状态,准备好数据,准备好redo和undo的状态,但是不提交,就是一副万事俱备,只欠东风的姿态。然后反馈给Leader一个READY,表示我准备好了。
当Leader 收到所有的 Follower的反馈为 READY 时,就会进入第二个阶段。

第二个阶段:提交阶段
Leader向所有的Follower发送一条 COMMIT,所有的Follower执行之前准备好的提交操作,正式将数据存入硬盘中,释放这个事务占用的所有资源,然后反馈一个ACK给Leader。一个分布式的事务正式完成。

上面第一阶段中,任意一台Follower如果向Leader反馈的是,“我没准备好”,或者超时网络错误,Leader都不会发送Commit,而是改为全部发送Rollback,强行让这个事务回滚。


三阶算法这里只做稍稍介绍,三阶顾名思义,就是三个阶段,也就是比二阶多了一个阶段,这里就不细说了。


这么说其实还是有很多问题没有解决,例如:
Follower在反馈READY之后,由于网络出现错误,导致Follower没有收到Leader发过来的COMMIT,怎么办,只能永远一直在准备状态中了。
再例如,Leader在发送完 PREPARE 之后,某一台Follower突然故障,Leader只能苦苦等待这个已经故障的Follower的回复,即便是设置超时时间,不仅超时时间难以定义,在效率上也会大打折扣。
还有一个非常大的问题,那就是如果Leader故障,那么整个系统都会进入瘫痪。


从以上我们可以看出,二阶算法最大的问题在于
1、网络不确定性
2、Leader必须要有多台,或者说当一台Leader挂掉时,必须要有一个算法快速的选择出另外一台Leader。


为了解决这个问题,接下来就是要说的重头戏,ZooKeeper。


在说ZooKeeper之前,有必要说一下它的来历。ZooKeeper是一个开放源代码的分布式协调服务,由知名互联网公司雅虎创建,先后应用在 Google 和 阿里 的子项目中,目前最为流行的大数据的实现,也有用到ZooKeeper进行分布式协调。
说起ZooKeeper名字的来历,非常有趣,早年间,雅虎的开发团队对于日益新增的系统,就寻思着做一个管理系统,由于当时雅虎很多的项目都是以动物命名,例如比较出名的Yahoo Pig。 当时的一位员工就调侃道:“再搞项目,我们就要变成动物园了。”此话一出,各个工程师纷纷表示,干脆就叫动物园管理员吧。由此,ZooKeeper一词就出现了。
所以,由这个命名大家应该也能猜到,ZooKeeper的设计目的,就是用来管理各种各样的分布式的项目,可以说ZooKeeper就是一个分布式管理工具。


ZooKeeper这个工具可能在我们公司并不能用上,但是它的设计思路,我觉得有非常多的可以参考的地方,这也是我今天为什么要讲这个课题的原因。


下面我将简单说一下ZooKeeper的用法。
说是工具,但是可能跟大家平常理解的工具不一样,ZooKeeper是一个运行在后台上的进程,分别运行在多个服务器上,这些服务器会形成一个集群,这个集群会自动的共享,并同步数据。其中一台服务器的数据发生变化,所有的服务器都会跟着变化,而且在保证分区容错性的基础上,在一致性和可用性之间达到非常大的平衡。
通俗一点的说法就是,ZooKeeper就是把几台机器当成一台机器,你可以用命令行的形式登录到某台服务器上,由于数据都是同步的,所以你登录到哪一台服务器都是一样的。这时候呢,就有点像使用操作系统一样,里面有一个个的目录(我们称为ZNode,节点),每个目录下又可以存放一些文件(同样叫,ZNode,不过是叫数据节点)。

ZooKeeper如何做到分布式同步?
究其原因,归根于ZAB算法,分解开来其实就是ZooKeeper Atomic Broadcast 原子消息广播协议。


首先必须要说明这个算法是建立在一定基础上的,它假设了在一个局域网内有2个基本特性
1、如果接收者接收到了消息P,那么发送者一定也确实发送了消息P。
2、如果接收者接收到了多个消息,这些消息的顺序,一定是发送者发送的顺序。
所以这个算法只能应用在安全的局域网内,任何在网络中,如果存在数据包修改、伪装发送等不安全因素时,这个算法都无法运行。


要了解ZAB的工作过程,以下我会将ZAB分成3个部分来说明,分别是
发现、同步、广播
这3部分,对于参与到ZooKeeper的分布式系统的每一个进程,都会不停的循环执行,通常这样一个循环称为一个周期。


为了便于理解,我会从广播说起。
假设现在已经有一个集群正在运行,里面有一个Leader和多个Follower在协调工作。在这个阶段中,Leader的状态是LEADING,Follower的状态为FOLLOWING,这时ZAB的广播算法其实跟二阶算法非常类似,当Leader收到事务请求之后,他就会向各个Follower转发这个事务,并且等待它们的准备反馈。
不同的是,Leader向Follower发送的事务,还附带了,事务的ID,这里称之为ZXID,ZXID通常是一个64位的数字,并且按照事务请求,不停的递增。Follower接收到事务,会把ZXID记录到本地。
同时Leader不再等待是全部Follower反馈,只需要超过半数的Follower反馈,就会立马向所有的Follower发送Commit消息,让所有的Follower把事务提交。


这样子处理,由于不再等待全部反馈,只需要过半数反馈就执行,使得如果Follower在准备阶段时由于网络问题出现个别Follower无法向Leader反馈ACK时,或者个别Follower在处理事务的过程中突然故障,那么整个系统依然能正常运行。
另外,如果是网络延迟,由于事务请求在Leader和Follower之间传输依然是按照顺序传输,作为Follower,依然会按照顺序接收到Leader发送的Prepare和Commit指令。


那么有小伙伴可能就要问了,那那些故障的或者处理事务失败的Follower怎么办,它们的数据不就和Leader不同步了吗?


这时候Follower会进入一个重连恢复过程,但是即便Follower重新连接上Leader,它们的数据依然不相同。这时候ZXID的作用就体现出来,Follower会把自身的本地的ZXIDmax1发送给Leader,Leader根据自身ZXIDmax2和ZXIDmax1进行比较,把之间缺少的事务补发给Follower。直到与Leader完全同步后,正式重新进入广播阶段。


但是以上依然还没解决Leader突然故障的问题,如你所见,Leader其实就是这个集群的中心点,故障意味着整个集群会呈现一种不可用的状态。
这时ZAB就会进入发现阶段,发现阶段最主要的目的就是经过一个投票算法,在这个集群里快速选出一个Leader。
ZooKeeper默认提供3种选举算法,这3种算法原理基本一致,区别就在于传输方式,下面以UDP为例子。

首先,Follower意识到Leader故障之后,会把自己的状态设成LOOKING。
接着,Follower会广播一条信息,信息的内容大概是这样(SID,ZXID),其中SID为服务器ID,ZXID为这个服务器已经处理的最大事务ID,而这条消息,我们称为投票vote。
我们假设集群中有5台机器,SID分别为1、2、3、4、5,他们的ZXID分别为9、9、9、8、8,其中SID1为Leader,某个时刻SID1出现故障。因此2、3、4、5进行Leader选举,他们会进行2次投票。

第一次投票,由于无法检测到其他机器的状态信息,因此,每台都会为自己进行一次投票。
SID2 => (2, 9)
SID3 => (3, 9)
SID4 => (4, 8)
SID5 => (5, 8)
由于是广播,每台机器在发送自己的投票之后呢,都会收到别机器发来的投票。

第二次变更投票
规则是这样子的
规则一:所有机器,在收到别的机器发过来的投票时,就跟自己的投票比较,如果对方的ZXID大于自己的ZXID,就把这个投票再次发出去。
规则二:如果ZXID相同,则比较SID,如果对方的SID比自己的大,那就把投票发出去。
规则三:以上2种情况都不符合时,就不处理。


以这4台为例子
SID2 会收到 (3,9)(4,8)(5,8),根据规则二,会再次投票(3,9)
SID3 会收到 (2,9)(4,8)(5,8),根据规则三,不会进行投票
SID4 会收到 (2,9)(3,9)(5,8),根据规则一、二,会再次投票(3,9)
SID4 会收到 (2,9)(3,9)(4,8),根据规则一、二,会再次投票(3,9)
最后SID3会成为新的Leader,由于投票都是广播的,所有机器都会认同SID3就是新的Leader,然后向SID3发起连接。


由于广播,有小伙伴可能会担心丢包的问题,事实上,只要超过半数的机器投票SID3,SID3就会成为Leader。一般来讲,拥有最新的数据的机器,会倾向于成为Leader。


完成发现阶段,接下来就是同步阶段。
所有的Follower,都会把自己的ZXID发送给Leader,一般来讲,这时候Follower的ZXID都会小于或等于Leader的ZXID。
如果出现Follower的ZXID比Leader要大,则会再次进行一次投票。


正常来讲,Leader收到Follower的ZXID,然后就会跟自己比较,如果相同,则反馈Follower:“你跟我一样”。至此,该Follower正式将自己的状态改为FOLLOWING。
如果ZXID比自己小,则会把缺的部分的事务发送给Follower,Follower在完成事务提交后,反馈给Leader:“我做好准备了。”,再正式将自己状态改为FOLLOWING。


待同步过程结束后,所有的Follower的状态都为FOLLOWING,Leader会把自己的状态正式改为LEADING。系统正式恢复完毕,继续正常工作。


以上过程就是ZAB算法的大致流程,其实还有很多细节还没讨论
例如
ZooKeeper的布置流程
ZXID溢出问题。
Leader负载均衡问题。
在此就先跳过了。


我相信以上内容,应该已经给技术的同事有一定的启发,而对于非技术开发的同事,则应该对分布式的概念有深的了解。


以上内容,也仅仅只描述了ZooKeeper的服务器部分,而分布式协调思想,更重要的还在ZooKeeper客户端部分。不过再说下去就有点超过这次会议目的的范畴。感兴趣的小伙伴可以在日后一起来和我讨论。
展开阅读全文

没有更多推荐了,返回首页