I-Framework[无处不在的ZooKeeper]
Posted 通信互联网技术笔记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了I-Framework[无处不在的ZooKeeper]相关的知识,希望对你有一定的参考价值。
摘要
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现(模仿Chubby做的),是Hadoop、Hbase、DCOS的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
I-Framework[无处不在的ZooKeeper]
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
概述中把ZooKeeper引向了另外一个陌生的概念分布式协调服务,那么这篇文章我就从这个分布式协调服务入手开始讲ZooKeeper吧。
那么什么是分布式协调技术?分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成"脏数据"的后果。有人可能会说认为写一个调度算法就轻松解决这个问题,但这个算法仅能协调单个服务器中的进程,如果这些进程全部是跑在一台机上的话,相对来说确实就好办了,问题就在于他是在一个分布式的环境下。先来认识一下什么是分布式。
那什么又是分布式呢?其实看过我之前几期文章的小伙伴们对于分布式已经相对了解了,这里给用一张图帮助大家理解这方面的内容。如图1.
图1-分布式系统
分析一下这张图,在这图中有三台机器,每台机器各跑一个应用程序。然后我们将这三台机器通过网络将其连接起来,构成一个系统来为用户提供服务(Server1+ Server2+ Server3),对用户来说这个系统的架构是透明的,他感觉不到我这个系统是一个什么样的架构,只能看到系统提供的整体服务,这种系统称作一个分布式系统。
从技术的角度上再补充一下什么是分布式应用。分布式应用可以在给定时间(同时)在网络中的多个系统上运行,通过协调它们以快速有效的方式完成特定任务。通过将分布式应用配置为在更多系统上运行,可以进一步减少完成任务的时间。分布式应用正在运行的一组系统称为集群,而在集群中运行的每台机器被称为节点。
分布式应用有两部分,Server(服务器)应用程序和 Client(客户端)应用程序。服务器应用程序实际上是分布式的,并具有通用接口,以便客户端可以连接到集群中的任何服务器并获得相同的结果。客户端应用程序是与分布式应用进行交互的工具.
客户端(Client)或称为用户端,是指与服务器相对应,与服务端互相配合运行为客户提供本地服务的程序。
图1-分布式系统
那在这个分布式系统中如何对进程进行调度?再来看图1,假设在第一台机器上挂载了一个资源(图中的Peer Source),然后这三个物理分布的进程都要竞争这个资源,但我们又不希望他们同时进行访问,这时候我们就需要一个协调器,来让他们有序的来访问这个资源。这个协调器就是我们经常提到的分布式锁,比如说"进程-1"在使用该资源的时候,会先去获得锁,"进程1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程1"用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。这个分布式锁也就是我们分布式协调技术实现的核心内容。
概念扫盲:
临界资源:多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。
脏数据(Dirty Read)是指源系统中的数据不在给定的范围内或对于实际业务毫无意义,或是数据格式非法,以及在源系统中存在不规范的编码和含糊的业务逻辑。
挂载:在linux操作系统中,挂载是指将一个设备(通常是存储设备)挂接到一个已存在的目录上。 我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的目录上, 然后通过访问这个目录来访问存储设备。
一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。
1、分布式系统面临的调度问题
图1所示的分布式环境,有人可能会感觉实现资源调度不是很难,无非是将原来在同一台机器上对进程调度的原语,通过网络实现在分布式环境中。是的,表面上是可以这么说。但是问题就在网络这,在分布式系统中,所有在同一台机器上的假设都不存在,因为网络是不可靠的。
比如,在同一台机器上,你对一个服务的调用如果成功,那就是成功,如果调用失败,比如抛出异常那就是调用失败。但是在分布式环境中,由于网络的不可靠,你对一个服务的调用失败了并不表示一定是失败的,可能是执行成功了,但是响应返回的时候失败了。还有,A和B都去调用C服务,在时间上 A还先调用一些,B后调用,那么最后的结果是不是一定A的请求就先于B到达呢? 这些在同一台机器上的种种假设,我们都要重新思考,我们还要思考这些问题给我们的设计和编码带来了哪些影响。还有,在分布式环境中为了提升可靠性,我们往往会部署多套服务,但是如何在多套服务中达到一致性,这在同一台机器上多个进程之间的同步相对来说比较容易办到,但在分布式环境中确实一个大难题。
所以分布式协调远比在同一台机器上对多个进程的调度要难得多,而且如果为每一个分布式应用都开发一个独立的协调程序。一方面,协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器。另一方面,协调程序开销比较大,会影响系统原有的性能。所以,急需一种高可靠、高可用的通用协调机制来用以协调分布式应用。
2、分布式锁的实现者
目前,在分布式协调技术方面做得比较好的就是Google的Chubby还有Apache的ZooKeeper,他们都是分布式锁的实现者。既然有了Chubby为什么还要弄一个ZooKeeper?主要是Chbby是非开源的,Google自家用。后来雅虎模仿Chubby开发出了ZooKeeper,也实现了类似的分布式锁的功能,并且将ZooKeeper作为一种开源的程序捐献给了Apache,那么这样就可以使用ZooKeeper所提供锁服务。而且ZK在分布式领域久经考验,它的可靠性,可用性都是经过理论和实践的验证的。下面我们就正式开始介绍ZooKeeper。
ZooKeeper英文意思是动物园饲养员,寓意为管理协调分布式系统中多种多样的服务。
ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务,它提供了一项基本服务:分布式锁服务。由于ZooKeeper的开源特性,后来我们的开发者在分布式锁的基础上,摸索了出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。
ZooKeeper性能上的特点决定了它能够用在大型的、分布式的系统当中。从可靠性方面来说,它并不会因为一个节点的错误而崩溃。除此之外,它严格的序列访问控制意味着复杂的控制原语可以应用在客户端上。ZooKeeper在一致性、可用性、容错性的保证,也是ZooKeeper的成功之处,它获得的一切成功都与它采用的协议——Zab协议是密不可分的。Zab就是原子广播的过程,详见后文。
前面提到了那么多的服务,比如分布式锁、配置维护、组服务等,那它们是如何实现的呢?
ZooKeeper在实现这些服务的基础是一种新的数据结构——Znode,然后在该数据结构的基础上定义了一些原语,也就是一些关于该数据结构的一些操作。有了这些数据结构和原语还不够,因为我们的ZooKeeper是工作在一个分布式的环境下,我们的服务是通过消息以网络的形式发送给我们的分布式应用程序,所以还需要一个通知机制——Watcher机制。那么总结一下,ZooKeeper所提供的服务主要是通过:数据结构+原语+watcher机制,三个部分来实现的。那么我就从这三个方面,给大家介绍一下ZooKeeper。
1、 ZooKeeper数据模型Znode
ZooKeeper拥有一个层次的命名空间(namespace),这个和标准的文件系统非常相似,因此ZooKeeper可以被看成一个文件系统。如下图所示。
从图中我们可以看出ZooKeeper的数据模型,在结构上和标准文件系统的非常相似,都是采用这种树形层次结构,ZooKeeper树中的每个节点被称为—Znode。和文件系统的目录树一样,ZooKeeper树中的每个节点可以拥有子节点。Znode具有以下特点:
(1) 引用方式
Zonde通过路径(path)引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头(例如”/NameService”)。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串"/ZooKeeper"用以保存管理信息,比如关键配额信息。
(2) Znode结构
ZooKeeper命名空间中的Znode,兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分。图中的每个节点称为一个Znode。每个Znode由3部分组成:
stat:此为状态信息, 描述该Znode的版本, 权限等信息
data:与该Znode关联的数据
children:该Znode下的子节点
ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,但常规使用中应该远小于此值。
(3) 数据访问
ZooKeeper中的每个节点存储的数据要被原子性的操作。也就是说”读操作”将获取与节点相关的所有数据,”写操作”也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作。
(4) 节点类型
ZooKeeper中的节点有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。
① 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话(Session)结束,临时节点将被自动删除,当然可以也可以手动删除。虽然每个临时的Znode都会绑定到一个客户端会话,但他们对所有的客户端还是可见的。另外,ZooKeeper的临时节点不允许拥有子节点。
② 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
(5) 顺序节点
当创建Znode的时候,用户可以请求在ZooKeeper的路径结尾添加一个递增的计数。这个计数对于此节点的父节点来说是唯一的,它的格式为"%10d"(10位数字,没有数值的数位用0补充,例如"0000000001")。当计数值大于232-1时,计数器将溢出。
(6) 观察watch
客户端可以在节点上设置watch,我们称之为监视器。当节点状态发生改变时(Znode的增、删、改)将会触发watch所对应的操作。当watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次,这样可以减少网络流量。
现在我们来看一下ZooKeeper的数据模型Znode:
在ZK中有命名空间节点,有配置节点,有组节点,有App节点。
2、ZooKeeper中的时间
ZooKeeper有多种记录时间的形式,以明确服务操作的时间顺序,其中包含以下几个主要属性:
(1) Zxid
致使ZooKeeper节点状态改变的每一个操作都将使节点接收到一个Zxid格式的时间戳,并且这个时间戳全局有序。也就是说,每个对节点的改变都将产生一个唯一的Zxid。如果Zxid1的值小于Zxid2的值,那么Zxid1所对应的事件发生在Zxid2所对应的事件之前。实际上,ZooKeeper的每个节点维护着三个Zxid值,为别为:cZxid、mZxid、pZxid。
cZxid:是节点的创建时间所对应的Zxid格式时间戳。
mZxid:是节点的修改时间所对应的Zxid格式时间戳。
实现中Zxid是一个64为的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch。低32位是个递增计数。
(2) 版本号
对节点的每一个操作都将致使这个节点的版本号增加。每个节点维护着三个版本号,他们分别为:
version:节点数据版本号
cversion:子节点版本号
aversion:节点所拥有的ACL版本号
3、ZooKeeper节点属性
通过前面的介绍,我们可以了解到,一个节点自身拥有表示其状态的许多重要属性,如下图所示。
4、ZooKeeper服务中操作
在ZooKeeper中有9个基本操作,如下图所示:
更新ZooKeeper操作是有限制的。delete或setData必须明确要更新的Znode的版本号,我们可以调用exists找到。如果版本号不匹配,更新将会失败。更新ZooKeeper操作是非阻塞式的。因此客户端如果失去了一个更新(由于另一个进程在同时更新这个Znode),他可以在不阻塞其他进程执行的情况下,选择重新尝试或进行其他操作。
尽管ZooKeeper可以被看做是一个文件系统,但是处于便利,摒弃了一些文件系统地操作原语。因为文件非常的小并且使整体读写的,所以不需要打开、关闭或是寻地的操作。
5、Watch监视触发器
(1) watch概述
ZooKeeper可以为所有的读操作设置watch,这些读操作包括:exists()、getChildren()及getData()。watch事件是一次性的触发器,当watch的对象状态发生改变时,将会触发此对象上watch所对应的事件。watch事件将被异步地发送给客户端,并且ZooKeeper为watch机制提供了有序的一致性保证。理论上,客户端接收watch事件的时间要快于其看到watch对象状态变化的时间。
(2) watch类型
ZooKeeper所管理的watch可以分为两类:
① 数据watch(data watches):getData和exists负责设置数据watch
② 孩子watch(child watches):getChildren负责设置孩子watch
我们可以通过操作返回的数据来设置不同的watch:
① getData和exists:返回关于节点的数据信息
② getChildren:返回孩子列表
因此
① 一个成功的setData操作将触发Znode的数据watch
② 一个成功的create操作将触发Znode的数据watch以及孩子watch
③ 一个成功的delete操作将触发Znode的数据watch以及孩子watch
(3) watch注册与处触发
① exists操作上的watch,在被监视的Znode创建、删除或数据更新时被触发。
② getData操作上的watch,在被监视的Znode删除或数据更新时被触发。在被创建时不能被触发,因为只有Znode一定存在,getData操作才会成功。
③ getChildren操作上的watch,在被监视的Znode的子节点创建或删除,或是这个Znode自身被删除时被触发。可以通过查看watch事件类型来区分是Znode,还是他的子节点被删除:NodeDelete表示Znode被删除,NodeDeletedChanged表示子节点被删除。
Watch由客户端所连接的ZooKeeper服务器在本地维护,因此watch可以非常容易地设置、管理和分派。当客户端连接到一个新的服务器时,任何的会话事件都将可能触发watch。另外,当从服务器断开连接的时候,watch将不会被接收。但是,当一个客户端重新建立连接的时候,任何先前 注册过的watch都会被重新注册。
我们来列举一下ZooKeeper的基本概念,部分概念在前文中已经有所涉猎:
Architecture(架构)
Hierarchical namespace(层次命名空间)
Session(会话)
Watches(监视)
ZooKeeper的“客户端-服务器架构”如下图所示:
对于图中的组件这里做一个大概的描述,对于Server的类型下一部分ZK角色描述部分有详细解释。
与ZooKeeper集合(Ensemble)进行交互的应用程序称为 ZooKeeper客户端或简称客户端,例如Master。
ZooKeeper通常为分布式系统提供了命名服务、.配置管理、集群管理、分布式锁、队列管理5种服务,下面分别简单概述一下这几个功能。
1、命名服务
ZooKeeper命名服务:在ZooKeeper的文件系统里创建一个目录,即有唯一的path。在我们使用tborg无法确定上游程序的部署机器时即可与下游程序约定好path,通过path即能互相探索发现。
2、配置管理
ZooKeeper的配置管理:程序总是需要配置的,如果程序分散部署在多台机器上,要逐个改变配置就变得困难。现在把这些配置全部放到ZooKeeper上去,保存在 ZooKeeper 的某个目录节点(Config)中,然后所有相关应用程序对这个目录节点进行监听(客户端设置Watch、),一旦配置信息发生变化,每个应用程序就会收到 ZooKeeper 的通知,然后从 ZooKeeper 获取新的配置信息应用到系统中就好
3、集群管理
ZooKeeper集群管理:所谓集群管理包括两个内容,第一是否有机器退出和加入,第二选举master。
对于管理机器退出,所有机器约定在父目录GroupMembers下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 ZooKeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。新机器加入也是类似,所有机器收到通知,新兄弟目录加入,highcount又有了。
对于选举Master,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。
4、分布式锁
ZooKeeper分布式锁:有了ZooKeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
对于保持独占的锁,我们将ZooKeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock (分配锁)节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock 节点就释放出锁。
对于控制时序的锁,/distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。
5、队列管理
ZooKeeper两种类型的队列:
同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。
队列按照 FIFO 方式进行入队和出队操作。
同步队列,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。 另一种队列和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。
ZooKeeper活动参与的角色包括领导者、学习者与客户端,每一类角色的功能及任务如下表所示。
Leader与follower都有自己的Znode,每个Znode包括自己所有应有的节点和内容。
ZooKeeper与客户端Client的关系如下图所示。
最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是ZooKeeper最重要的性能。
可靠性:具有简单、健壮、良好的性能,如果消息被到一台服务器接受,那么它将被所有的服务器接受。
实时性:ZooKeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,ZooKeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
原子性:更新只能成功或者失败,没有中间状态。
顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
一旦ZooKeeper集合启动,它将等待客户端连接。客户端将连接到ZooKeeper集合中的一个节点。它可以是leader或follower节点。一旦客户端被连接,节点将向特定客户端分配会话ID并向该客户端发送确认。如果客户端没有收到确认,它将尝试连接ZooKeeper集合中的另一个节点。一旦连接到节点,客户端将以有规律的间隔向节点发送心跳,以确保连接不会丢失。
下图描述了ZooKeeper工作流,后面的表说明了它的不同组件。
1、原子广播
ZooKeeper 的核心是原子广播,这个机制保证了各个Server之间的同步(负责广播从leader节点到follower节点的变化)。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
为了保证事务的顺序一致性,ZooKeeper采用了递增的事务id号(zxid时间戳)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。
2、ZooKeeper下Server工作状态
每个Server在工作过程中有三种状态:
LOOKING:当前Server不知道leader是谁,正在搜寻
LEADING:当前Server即为选举出来的leader
FOLLOWING:leader已经选举出来,当前Server与之同步
3、ZooKeeper选主流程
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。
4、ZooKeeper同步流程
选完Leader以后,zk就进入状态同步过程,同步过程如下:
①Leader等待server连接;
②Follower连接leader,将最大的zxid发送给leader;
③Leader根据follower的zxid确定同步点;
④完成同步后通知follower 已经成为uptodate(最新的)状态;
⑤Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。
前文介绍了很多ZooKeeper的基本关键,这一部分讲一下其在DCOS中的应用,看看ZooKeeper是如何实现的他的服务的,以ZooKeeper提供的基本服务分布式锁为例。
在分布式锁服务中,有一种最典型应用场景,就是Mesos中通过对集群进行Master选举,来解决分布式系统中的单点故障。什么是分布式系统中的单点故障:通常分布式系统采用主从模式,就是一个主控机连接多个处理节点(Master与Slave)。主节点负责分发任务,从节点负责处理任务,当我们的主节点发生故障时,那么整个系统就都瘫痪了,那么我们把这种故障叫作单点故障。如下图所示:
传统方式是采用一个备用节点,这个备用节点定期给当前主节点发送ping包,主节点收到ping包以后向备用节点发送回复Ack,当备用节点收到回复的时候就会认为当前主节点还活着,让他继续提供服务。
当主节点挂了,这时候备用节点收不到回复了,然后他就认为主节点挂了接替他成为主节点。
但是这种方式就是有一个隐患,就是网络问题,也就是说我们的主节点的并没有挂,只是在回复的时候网络发生故障,这样我们的备用节点同样收不到回复,就会认为主节点挂了,然后备用节点将他的Master实例启动起来,这样我们的分布式系统当中就有了两个主节点也就是---双Master, 出现Master以后我们的从节点就会将它所做的事一部分汇报给了主节点,一部分汇报给了从节点,这样服务就全乱了。为了防止出现这种情况,我们引入了 ZooKeeper,它虽然不能避免网络故障,但它能够保证每时每刻只有一个Master。
1、Master启动
在引入了ZooKeeper以后我们启动了两个主节点,"主节点-A"和"主节点-B"他们启动以后,都向ZooKeeper去注册一个节点。我们 假设"主节点-A"锁注册地节点是"master-00001","主节点-B"注册的节点是"master-00002"(在GroupMembers目录下创建临时节点),注册完以后进行选举,编号最小的节点将在选举中获胜获得锁成为主节点,也就是我们的"主节点-A"将会获得锁成为主节点,然后"主节点-B"将被阻塞成为一个备用节点。那么,通过这 种方式就完成了对两个Master进程的调度。
2、 Master故障
如果"主节点-A"挂了,这时候他所注册的节点将被自动删除,ZooKeeper会自动感知节点的变化,然后再次发出选举,这时候"主节点-B"将在选举中获胜,替代"主节点-A"成为主节点。
3、Master 恢复
如果主节点恢复了,他会再次向ZooKeeper注册一个节点,这时候他注册的节点将会是"master-00003",ZooKeeper会感知节点的变化再次发动选举,这时候"主节点-B"在选举中会再次获胜继续担任"主节点","主节点-A"会担任备用节点。
请长按扫码关注
通信互联网笔记——易懂的通信IT知识共享平台
点 “阅读原文” 查看系统架构已发文章列表
以上是关于I-Framework[无处不在的ZooKeeper]的主要内容,如果未能解决你的问题,请参考以下文章