[zookeeper]2.zookeeper原理特性以及典型使用案例

  本文将讲解Zookeeper的原理特性以及典型使用案例,部分资源来自网路

1.ZooKeeper 原理

    Apache ZooKeeper是努力发展和维持一个开源的服务器,这使得高度可靠的分布式协调。ZooKeeper是用于维持配置信息,命名,提供分布式同步,并提供分组服务的集中式服务。

  ZooKeeper允许分布式进程相互协调,通过共享式的名字空间数据寄存器(我们称这些寄存器znodes),就像文件系统。不同于普通的文件系zookeeper提供其客户提供高吞吐量、低延迟、高可用、严格有序的进入znodes。在性能方面的优势让它被用在大型的分布式系统。可靠性方面,防止它成为大系统的单点故障。其严格的命令允许复杂的同步原语是在客户端实现。

  ZooKeeper提供的名称空间更像是一个标准的文件系统。一个名字是一个序列的路径元素由斜杠分隔(“/”)。在ZooKeeper的名称空间,每个znode的路径标识。每个znode有父的路径是前缀少一层的znode;此规则的例外是根(“/”)没有父。而且,就像标准的文件系统,一个存在子节点的节点是不能被删除的。

  ZooKeeper和标准文件系统的主要区别是,每个znode可以有与它相关的数据(每个文件也可以是目录,反之亦然),znodes限于它们可以具有的数据量。ZooKeeper被设计用来存储坐标数据:状态信息、配置、位置信息等这样的元信息通常以千字节为单位。ZooKeeper有1M的一个内置的完整性检查,以防止它被用作一个大的数据存储,但一般它是用来存储小得多的数据块。

  该服务本身是复制了一组包括服务的机器。这些机器保持在数据树的存储的镜像以及在持久性存储事务日志和快照。因为数据被保持在内存储,ZooKeeper能够获得很高的吞吐量和低延迟的数据。缺点到内存数据库是该Zookeeper可以管理数据库的大小是由内存的限制。这也是znodes只能存储小的数据量原因。

  组成的ZooKeeper服务的服务器都必须互相了解。只要大多数的服务器可用,ZooKeeper的服务将是可用的。用户还必须知道服务器的列表。客户端创建一个句柄使用的服务器此列表中的ZooKeeper服务。

  客户端只能连接到一个单一的ZooKeeper服务器。客户机维护通过它发送请求时,获取响应,获取观看事件,并发送心跳TCP连接。如果TCP连接到服务器断开,客户端将连接到不同的服务器。当客户端第一次连接到的ZooKeeper服务第一的ZooKeeper服务器将安装客户端的会话。如果客户端需要连接到另一台服务器,将会与新得服务器重建会话。

  阅读由ZooKeeper客户端发送请求在该客户端所连接的服务器的ZooKeeper本地处理。如果该读请求在znode注册的,该watch事件还跟踪在ZooKeeper服务器。写请求转发到其他的ZooKeeper服务器,并通过共识产生一个响应之前。同步请求也被转送到另一台服务器,但实际上并没有达成一致。因此,随着服务器数量的读请求的吞吐量和写请求的吞吐量随服务器的数量而减少。

  顺序在ZooKeeper中是非常重要的; 所有的更新都完全是排序的。ZooKeeper的实际标识反映这个顺序每次更新。我们称这个号zxid(ZooKeeper的事务ID)。每次更新都会有一个独特的zxid。读取(和Watchs)相对于更新顺序。读取响应将加盖上zxid由服务器读取处理服务。

 2.Zookeeper 中几个重要的概念
  • Znode 

  ZooKeeper目录树中每一个节点对应一个Znode。每个Znode维护着一个属性结构,它包含着版本号(dataVersion),时间戳(ctime,mtime)等状态信息,ZooKeeper正是使用节点的这些特性来实现它的某些特定功能。每当Znode的数据改变时,他相应的版本号将会增加。每当客户端检索数据时,它将同时检索数据的版本号。并且如果一个客户端执行了某个节点的更新或删除操作,他也必须提供要被操作的数据版本号。如果所提供的数据版本号与实际不匹配,那么这个操作将会失败。

Znode是客户端访问ZooKeeper的主要实体,它包含以下几个特征:

  (1)Watches

  客户端可以在节点上设置watch(我们称之为监视器)。当节点状态发生改变时(数据的增、删、改)将会触发watch所对应的操作。当watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次。

  (2)数据访问

  ZooKeeper中的每个节点存储的数据要被原子性的操作。也就是说读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作。

  (3)节点类型

  ZooKeeper中的节点有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。
  ZooKeeper的临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。另外,需要注意是, ZooKeeper的临时节点不允许拥有子节点。 
  ZooKeeper的永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。

  (4)顺序节点(唯一性的保证)

   当创建Znode的时候,用户可以请求在ZooKeeper的路径结尾添加一个递增的计数。这个计数对于此节点的父节点来说是唯一的,它的格式为"%10d"(10位数字,没有数值的数位用0补充,例如"0000000001")。当计数值大于232-1时,计数器将溢出。

  创建的Znode有以下几种类型:

  PERSISTENT:永久节点
  EPHEMERAL:临时节点
  PERSISTENT_SEQUENTIAL:永久节点、序列化
  EPHEMERAL_SEQUENTIAL:临时节点、序列化

3.Zookeeper的特点

  • 最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。
  • 可靠性:具有简单、健壮、良好的性能,如果消息m被一台服务器接受,那么它将被所有的服务器接受。
  • 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。 但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
  • 等待无关(wait-free):慢的或者失效的client,不得干预快速的client的请求,使得每个client都能有效的等待。
  • 原子性:更新只能成功或者失败,没有中间状态。
  • 顺序性:包括全局有序和偏序两种:
  • 全局有序:是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;
  • 偏序:是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面

4.应用场景

场景类别 典型场景描述(ZK特性,使用方法) 应用中的具体使用
数据发布与订阅 发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,地址列表等就非常适合使用。

1. 索引信息和集群中机器节点状态存放在zk的一些指定节点,供各个客户端订阅使用。

2. 系统日志(经过处理后的)存储,这些日志通常2-3天后被清除。

3. 应用中用到的一些配置信息集中管理,在应用启动的时候主动来获取一次,并且在节点上注册一个Watcher,以后每次配置有更新,实时通知到应用,获取最新配置信息。

4. 业务逻辑中需要用到的一些全局变量,比如一些消息中间件的消息队列通常有个offset,这个offset存放在zk上,这样集群中每个发送者都能知道当前的发送进度。

5. 系统中有些信息需要动态获取,并且还会存在人工手动去修改这个信息。以前通常是暴露出接口,例如JMX接口,有了zk后,只要将这些信息存放到zk节点上即可。

Name Service 这个主要是作为分布式命名服务,通过调用zk的create node api,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。  
分布通知/协调 ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理。

1. 另一种心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是通过zk上某个节点关联,大大减少系统耦合。

2. 另一种系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改了ZK上某些节点的状态,而zk就把这些变化通知给他们注册Watcher的客户端,即推送系统,于是,作出相应的推送任务。

 

3. 另一种工作汇报模式:一些类似于任务分发系统,子任务启动后,到zk来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者就能够实时知道任务进度。

总之,使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合。

分布式锁 分布式锁,这个主要得益于ZooKeeper为我们保证了数据的强一致性,即用户只要完全相信每时每刻,zk集群中任意节点(一个zk server)上的相同znode的数据是一定是相同的。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

 

所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。

控制时序,就是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。做法和上面基本类似,只是这里 /distribute_lock 已经预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERAL_SEQUENTIAL来指定)。Zk的父节点(/distribute_lock)维持一份sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

 
集群管理 1. 集群机器监控:这通常用于那种对集群中机器状态,机器在线率有较高要求的场景,能够快速对集群中机器变化作出响应。这样的场景中,往往有一个监控系统,实时检测集群机器是否存活。过去的做法通常是:监控系统通过某种手段(比如ping)定时检测每个机器,或者每个机器自己定时向监控系统汇报“我还活着”。 这种做法可行,但是存在两个比较明显的问题:1. 集群中机器有变动的时候,牵连修改的东西比较多。2. 有一定的延时。

 

利用ZooKeeper有两个特性,就可以实时另一种集群机器存活性监控系统:

a. 客户端在节点 x 上注册一个Watcher,那么如果 x 的子节点变化了,会通知该客户端。

b. 创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失。

例如,监控系统在 /clusterServers 节点上注册一个Watcher,以后每动态加机器,那么就往 /clusterServers 下创建一个 EPHEMERAL类型的节点:/clusterServers/{hostname}. 这样,监控系统就能够实时知道机器的增减情况,至于后续处理就是监控系统的业务了。
2. Master选举则是zookeeper中最为经典的使用场景了。

在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算,网络I/O处理),往往只需要让整个集群中的某一台机器进行执行,其余机器可以共享这个结果,这样可以大大减少重复劳动,提高性能,于是这个master选举便是这种场景下的碰到的主要问题。

利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功。

利用这个特性,就能很轻易的在分布式环境中进行集群选取了。

另外,这种场景演化一下,就是动态Master选举。这就要用到 EPHEMERAL_SEQUENTIAL类型节点的特性了。

上文中提到,所有客户端创建请求,最终只有一个能够创建成功。在这里稍微变化下,就是允许所有请求都能够创建成功,但是得有个创建顺序,于是所有的请求最终在ZK上创建结果的一种可能情况是这样: /currentMaster/{sessionId}-1 , /currentMaster/{sessionId}-2 , /currentMaster/{sessionId}-3 ….. 每次选取序列号最小的那个机器作为Master,如果这个机器挂了,由于他创建的节点会马上小时,那么之后最小的那个机器就是Master了。

1. 在搜索系统中,如果集群中每个机器都生成一份全量索引,不仅耗时,而且不能保证彼此之间索引数据一致。因此让集群中的Master来进行全量索引的生成,然后同步到集群中其它机器。

2. 另外,Master选举的容灾措施是,可以随时进行手动指定master,就是说应用在zk在无法获取master信息时,可以通过比如http方式,向一个地方获取master。

分布式队列 队列方面,我目前感觉有两种,一种是常规的先进先出队列,另一种是要等到队列成员聚齐之后的才统一按序执行。对于第二种先进先出队列,和分布式锁服务中的控制时序场景基本原理一致,这里不再赘述。

 

第二种队列其实是在FIFO队列的基础上作了一个增强。通常可以在 /queue 这个znode下预先建立一个/queue/num 节点,并且赋值为n(或者直接给/queue赋值n),表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行了。这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成(或条件就绪)情况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就去 /taskList 下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当 /taskList 发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。

 

 

感谢:

本文引用以下朋友博客,排名不分先后,若有问题,请联系删除

小武哥 : http://www.wuzesheng.com/?p=2609

吴超沉思录:http://www.superwu.cn/

posted @ 2016-05-23 17:17  CodeLife  阅读(2843)  评论(0编辑  收藏  举报