ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。本文就简单的分析ZooKeeper,有需要的朋友可以参考学习。
1、分布式协调技术
在给大家介绍ZooKeeper之前先给大家介绍一种技术--分布式协调技术。那什么是分布式协调技术?其实分布式协调技术 主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成"脏数据"的后果。这时,有人可能会说这个简单,写一个调 度算法就轻松解决了。说这句话的人,可能对分布式系统不是很了解,所以才会出现这种误解。如果这些进程全部是跑在一台机上的话,相对来说确实就好办了,问题就在于他是在一个分布式的环境下,这时问题又来了,那什么是分布式呢?下面图示可以帮助大家理解这方面的内容,看下图:
分析一下这张图片,在这张图中有三台机器,每台机器各跑一个应用程序。然后我们将这三台机器通过网络将其连接起来,构成一个系统来为用户提供服务,对用户来说这个系统的架构师透明的,他感觉不到我这个系统是一个怎么样的架构。那么我们就可以把这种系统称作一个分布式系统。
那我们接下来再分析一下,在这个分布式系统中如何对进程进行调度,我假设在第一台机器上挂载了一个资源,然后这三个物理分布的进程都要竞争这个资源,但我们又不希望他们同时进行访问,这时候我们就需要一个协调器,来让他们有序的来访问这个资源。这个协调器就是我们经常提到的那个锁,比如说"进程-1"在使用该资源的时候,会先去获得锁,"进程1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程1"用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。这个分布式锁也就是我们分布式协调技术实现的核心内容。
2、分布式锁的实现者
目前,在分布式协调技术方面做得比较好的就是Google的Chubby还有Apache的ZooKeeper他们都是分布式锁的实现者。有人会问既然有了Chubby为什么还要弄一个ZooKeeper,难道Chubby做得不够好吗?不是这样的,主要是Chbby是非开源的,Google自家 用。后来雅虎模仿Chubby开发出了ZooKeeper,也实现了类似的分布式锁的功能,并且将ZooKeeper作为一种开源的程序捐献给了 Apache,那么这样就可以使用ZooKeeper所提供锁服务。而且在分布式领域久经考验,它的可靠性,可用性都是经过理论和实践的验证的。所以我们 在构建一些分布式系统的时候,就可以以这类系统为起点来构建我们的系统,这将节省不少成本,而且bug也将更少。
3、ZooKeeper概述
ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务,它提供了一项基本服务:分布式锁服务。由于ZooKeeper的开源特性,后来我们的开发者在分布式锁的基础上,摸索了出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。
ZooKeeper设计目的
1)最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是ZooKeeper最重要的性能。
2)可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。
3)实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
4)等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
5)原子性:更新只能成功或者失败,没有中间状态。
6)顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
ZooKeeper数据模型
Zookeeper会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,如图所示:
Zookeeper这种数据结构有如下这些特点:
1)每个子目录项如NameService都被称作为znode,这个znode是被它所在的路径唯一标识,如Server1这个znode的标识为/NameService/Server1。
2)znode可以有子节点目录,并且每个znode可以存储数据,注意EPHEMERAL(临时的)类型的目录节点不能有子节点目录。
3)znode是有版本的(version),每个znode中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据,version号自动增加。
4)znode可以是临时节点(EPHEMERAL),可以是持久节点(PERSISTENT)。如果创建的是临时节点,一旦创建这个EPHEMERALznode的客户端与服务器失去联系,这个znode也将自动删除,Zookeeper的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为session,如果znode是临时节点,这个session失效,znode也就删除了。
5)znode的目录名可以自动编号,如App1已经存在,再创建的话,将会自动命名为App2。
6)znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是Zookeeper的核心特性,Zookeeper的很多功能都是基于这个特性实现的。
7)ZXID:每次对Zookeeper的状态的改变都会产生一个zxid(ZooKeeper Transaction Id),zxid是全局有序的,如果zxid1小于zxid2,则zxid1在zxid2之前发生。
ZooKeeper Session
Client和Zookeeper集群建立连接,整个session状态变化如图所示:
如果Client因为Timeout和Zookeeper Server失去连接,client处在CONNECTING状态,会自动尝试再去连接Server,如果在session有效期内再次成功连接到某个Server,则回到CONNECTED状态。
注意:如果因为网络状态不好,client和Server失去联系,client会停留在当前状态,会尝试主动再次连接Zookeeper Server。client不能宣称自己的session expired,session expired是由Zookeeper Server来决定的,client可以选择自己主动关闭session。
ZooKeeper Watch
Zookeeper watch是一种监听通知机制。Zookeeper所有的读操作getData(), getChildren()和 exists()都可以设置监视(watch),监视事件可以理解为一次性的触发器,官方定义如下: a watch event is one-time trigger, sent to the client that set the watch, whichoccurs when the data for which the watch was set changes。Watch的三个关键点:
*(一次性触发)One-time trigger
当设置监视的数据发生改变时,该监视事件会被发送到客户端,例如,如果客户端调用了getData("/znode1", true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对/znode1 设置监视,否则客户端不会收到事件通知。
*(发送至客户端)Sent to the client
Zookeeper客户端和服务端是通过 socket 进行通信的,由于网络存在故障,所以监视事件很有可能不会成功地到达客户端,监视事件是异步发送至监视者的,Zookeeper 本身提供了顺序保证(ordering guarantee):即客户端只有首先看到了监视事件后,才会感知到它所设置监视的znode发生了变化(a client will never see a change for which it has set a watch until it first sees the watch event)。网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。
*(被设置 watch 的数据)The data for which the watch was set
这意味着znode节点本身具有不同的改变方式。你也可以想象 Zookeeper 维护了两条监视链表:数据监视和子节点监视(data watches and child watches) getData() 和exists()设置数据监视,getChildren()设置子节点监视。或者你也可以想象 Zookeeper 设置的不同监视返回不同的数据,getData() 和 exists() 返回znode节点的相关信息,而getChildren() 返回子节点列表。因此,setData() 会触发设置在某一节点上所设置的数据监视(假定数据设置成功),而一次成功的create() 操作则会出发当前节点上所设置的数据监视以及父节点的子节点监视。一次成功的 delete操作将会触发当前节点的数据监视和子节点监视事件,同时也会触发该节点父节点的child watch。
Zookeeper 中的监视是轻量级的,因此容易设置、维护和分发。当客户端与 Zookeeper 服务器失去联系时,客户端并不会收到监视事件的通知,只有当客户端重新连接后,若在必要的情况下,以前注册的监视会重新被注册并触发,对于开发人员来说这通常是透明的。只有一种情况会导致监视事件的丢失,即:通过exists()设置了某个znode节点的监视,但是如果某个客户端在此znode节点被创建和删除的时间间隔内与zookeeper服务器失去了联系,该客户端即使稍后重新连接 zookeeper服务器后也得不到事件通知。
Consistency Guarantees
Zookeeper是一个高效的、可扩展的服务,read和write操作都被设计为快速的,read比write操作更快。
顺序一致性(Sequential Consistency):从一个客户端来的更新请求会被顺序执行。
原子性(Atomicity):更新要么成功要么失败,没有部分成功的情况。
唯一的系统镜像(Single System Image):无论客户端连接到哪个Server,看到系统镜像是一致的。
可靠性(Reliability):更新一旦有效,持续有效,直到被覆盖。
时间线(Timeliness):保证在一定的时间内各个客户端看到的系统信息是一致的。
ZooKeeper的工作原理
在zookeeper的集群中,各个节点共有下面3种角色和4种状态:
角色:leader,follower,observer
状态:leading,following,observing,looking
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议(ZooKeeper Atomic Broadcast protocol)。Zab协议有两种模式,它们分别是恢复模式(Recovery选主)和广播模式(Broadcast同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
每个Server在工作过程中有4种状态:
LOOKING:当前Server不知道leader是谁,正在搜寻。
LEADING:当前Server即为选举出来的leader。
FOLLOWING:leader已经选举出来,当前Server与之同步。
OBSERVING:observer的行为在大多数情况下与follower完全一致,但是他们不参加选举和投票,而仅仅接受(observing)选举和投票的结果。
Leader Election
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。先介绍basic paxos流程:
1.选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
2.选举线程首先向所有Server发起一次询问(包括自己);
3.选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
4.收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
5.线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数,设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。
通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1.
每个Server启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信息,zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。
fast paxos流程是在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。
Leader工作流程
Leader主要有三个功能:
1.恢复数据;
2.维持与follower的心跳,接收follower请求并判断follower的请求消息类型;
3.follower的消息类型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根据不同的消息类型,进行不同的处理。
PING消息是指follower的心跳信息;REQUEST消息是follower发送的提议信息,包括写请求及同步请求;
ACK消息是follower的对提议的回复,超过半数的follower通过,则commit该提议;
REVALIDATE消息是用来延长SESSION有效时间。
Follower工作流程
Follower主要有四个功能:
1. 向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
2.接收Leader消息并进行处理;
3.接收Client的请求,如果为写请求,发送给Leader进行投票;
4.返回Client结果。
Follower的消息循环处理如下几种来自Leader的消息:
1.PING消息:心跳消息
2.PROPOSAL消息:Leader发起的提案,要求Follower投票
3.COMMIT消息:服务器端最新一次提案的信息
4.UPTODATE消息:表明同步完成
5.REVALIDATE消息:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息
6.SYNC消息:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。
Zab: Broadcasting State Updates
Zookeeper Server接收到一次request,如果是follower,会转发给leader,Leader执行请求并通过Transaction的形式广播这次执行。Zookeeper集群如何决定一个Transaction是否被commit执行?通过“两段提交协议”(a two-phase commit):
Leader给所有的follower发送一个PROPOSAL消息。
一个follower接收到这次PROPOSAL消息,写到磁盘,发送给leader一个ACK消息,告知已经收到。
当Leader收到法定人数(quorum)的follower的ACK时候,发送commit消息执行。
Zab协议保证:
1) 如果leader以T1和T2的顺序广播,那么所有的Server必须先执行T1,再执行T2。
2) 如果任意一个Server以T1、T2的顺序commit执行,其他所有的Server也必须以T1、T2的顺序执行。
“两段提交协议”最大的问题是如果Leader发送了PROPOSAL消息后crash或暂时失去连接,会导致整个集群处在一种不确定的状态(follower不知道该放弃这次提交还是执行提交)。Zookeeper这时会选出新的leader,请求处理也会移到新的leader上,不同的leader由不同的epoch标识。切换Leader时,需要解决下面两个问题:
Never forget delivered messages
Leader在COMMIT投递到任何一台follower之前crash,只有它自己commit了。新Leader必须保证这个事务也必须commit。
Let go of messages that are skipped
Leader产生某个proposal,但是在crash之前,没有follower看到这个proposal。该server恢复时,必须丢弃这个proposal。
Zookeeper会尽量保证不会同时有2个活动的Leader,因为2个不同的Leader会导致集群处在一种不一致的状态,所以Zab协议同时保证:
1) 在新的leader广播Transaction之前,先前Leader commit的Transaction都会先执行。
2) 在任意时刻,都不会有2个Server同时有法定人数(quorum)的支持者。
这里的quorum是一半以上的Server数目,确切的说是有投票权力的Server(不包括Observer)。
4、ZooKeeper应用举例
为了方便大家理解ZooKeeper,在此就给大家举个例子,看看ZooKeeper是如何实现的他的服务的,我以ZooKeeper提供的基本服务分布式锁为例。
4.1 分布式锁应用场景
在分布式锁服务中,有一种最典型应用场景,就是通过对集群进行Master选举,来解决分布式系统中的单点故障。什么是分布式系统中的单点故障:通常分布式系统采用主从模式,就是一个主控机连接多个处理节点。主节点负责分发任务,从节点负责处理任务,当我们的主节点发生故障时,那么整个系统就都瘫痪了,那么我们把这种故障叫作单点故障。如下图所示:
4.2 传统解决方案
传统方式是采用一个备用节点,这个备用节点定期给当前主节点发送ping包,主节点收到ping包以后向备用节点发送回复Ack,当备用节点收到回复的时候就会认为当前主节点还活着,让他继续提供服务。如图所示:
当主节点挂了,这时候备用节点收不到回复了,然后他就认为主节点挂了接替他成为主节点如下图所示:
但是这种方式就是有一个隐患,就是网络问题,来看一网络问题会造成什么后果,如下图所示:
也就是说我们的主节点的并没有挂,只是在回复的时候网络发生故障,这样我们的备用节点同样收不到回复,就会认为主节点挂了,然后备用节点将他的Master实例启动起来,这样我们的分布式系统当中就有了两个主节点也就是---双Master, 出现Master以后我们的从节点就会将它所做的事一部分汇报给了主节点,一部分汇报给了从节点,这样服务就全乱了。为了防止出现这种情况,我们引入了ZooKeeper,它虽然不能避免网络故障,但它能够保证每时每刻只有一个Master。我么来看一下ZooKeeper是如何实现的。
4.3 ZooKeeper解决方案
(1) Master启动
在引入了Zookeeper以后我们启动了两个主节点,"主节点-A"和"主节点-B"他们启动以后,都向ZooKeeper去注册一个节点。我们 假设"主节点-A"锁注册地节点是"master-00001","主节点-B"注册的节点是"master-00002",注册完以后进行选举,编号最 小的节点将在选举中获胜获得锁成为主节点,也就是我们的"主节点-A"将会获得锁成为主节点,然后"主节点-B"将被阻塞成为一个备用节点。那么,通过这 种方式就完成了对两个Master进程的调度。
(2) Master故障
如果"主节点-A"挂了,这时候他所注册的节点将被自动删除,ZooKeeper会自动感知节点的变化,然后再次发出选举,这时候"主节点-B"将在选举中获胜,替代"主节点-A"成为主节点。
(3) Master 恢复
如果主节点恢复了,他会再次向ZooKeeper注册一个节点,这时候他注册的节点将会是"master-00003",ZooKeeper会感知节点的变化再次发动选举,这时候"主节点-B"在选举中会再次获胜继续担任"主节点","主节点-A"会担任备用节点。
好了,以上就是对ZooKeeper的介绍,如有错误之处,欢迎大家指正!喜欢我们的分享,那就关注课课家吧,获取更多内容。
上一篇:解析Overlay网络技术
下一篇:分析云计算的发展趋势
¥199.00
¥199.00
¥10500.00
¥199.00