深入学习Zookeeper(1)--基础总结

一、Zookeeper简介

1.1. Zookeeper 简介

  Zookeeper是一套分布式锁管理系统,运用到了paxos 算法解决的一个分布式事务管理的系统,主要是用来解决分布式应用中经常遇到的一些数据管理问题,可以高可靠的维护元数据。提供的功能包括:配置维护、名字服务、分布式同步、组服务等。ZooKeeper的设计目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
  Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储。需要注意,zookeeper并不是用来专门存储数据的,它主要用来维护和监控系统存储的数据的状态的变化的。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。ZooKeeper所提供的服务主要是通过:数据结构+原语+watcher机制,三个部分来实现的。

1.2 “数据”是有限制的:
(1) 从数据大小来看:
    ZooKeeper的数据存储在一个叫ReplicatedDataBase 的 数据库中,该数据是一个内存数据库,既然是在内存当中,
数据量不会很大,这与HDFS的数据存储在磁盘上。
(2) 从数据类型来看:
    Zookeeper的数据存储在内存中,由于内存空间的限制,那么久不能再上面存储过多数据故,存储的数据需要根据需求和功能
进行选择,都是由ZK节点的性质和该节点所关联的数据实现的。
例如: 
  ① 集群管理:利用临时节点特性,节点关联的是机器的主机名,ip地址等相关信息,集群单点故障也属于这个范畴。
  ② 统一命名:主要利用节点的唯一性和目录节点树结构。、
  ③ 配置管理:节点关联的是配置信息。
  ④ 分布式锁:节点关联的是要竞争的资源。
1.3 数据访问

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

1.4 节点类型

  ZooKeeper中的节点有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。

  • ① 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话(Session)结束,临时节点将被自动删除,当然可以也可以手动删除。虽然每个临时的Znode都会绑定到一个客户端会话,但他们对所有的客户端还是可见的。另外,ZooKeeper的临时节点不允许拥有子节点。

  • ② 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。

1.5 引用方式

Zonde通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串”/zookeeper”用以保存管理信息,比如关键配额信息。

二、ZooKeeper应用场景

  ZooKeeper是一个高可用的分布式数据管理与系统协调框架。是基于Paxos算法实现的,使得该框架能够保证分部署环境中数据的强一致性,基于此特性,使得zookeeper能够引用于很多场景,任何功能的实现都是利用"Znode结构特性+节点关联的数据"来实现的
Zookeeper数据结构 ,如下图:

《深入学习Zookeeper(1)--基础总结》 Zookeeper数据结构.png

Zookeeper 这种数据结构有如下这些特点:

①  每个子目录项如NameService都被称作znode,这个znode是被它所在的路径的唯一标识,
        如,Server1这个znode的标识为/NameService/Server1
②  znode可以有子节点目录,并且每个znode都可以存储数据,注意:EPHEMERAL类型的目录节点不能有子节点目录;
③  znode 是有版本的,每个znode中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据;
④  znode 可以是临时节点,一旦创建了这个znode的客户端与服务器失去联系,这个znode也将自动删除,Zookeeper的客户端和
        服务器端通信采用长连接,每个客户端和服务器通过心跳来保持连接,这个连接状态成为session,如果znode是临时节点,
        这个session失效,znode也就删除了。
⑤  znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2;
⑥  znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,
       这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的。

  ZooKeeper命名空间中的Znode,兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分。图中的每个节点称为一个Znode。ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,但常规使用中应该远小于此值。

Znode由3部分组成:

① stat:此为状态信息, 描述该Znode的版本, 权限等信息

② data:与该Znode关联的数据

③ children:该Znode下的子节点

2.1数据发布与订阅(即配置管理)

(1) 典型场景描述

  发布与订阅,即所谓的配置管理,就是将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中管理和动态更新。例如:全局的配置信息,地址列表等就非常适合使用。 集中式的配置管理在应用集群中是非常常见的,一般商业公司内部都会实现一套集中的配置管理中心,应对不同的应用集群对于共享各自配置的需求,并且在配置变更时能够通知到集群中的每个机器。

(2) 应用
①  索引信息和集群中机器节点状态存放在zk的一些指定节点,供各个客户端订阅使用。
②  系统日志(经过处理后)的存储日志,通常为2-3天,然后会被清理。
③  应用中用到的一些配置信息集中管理,在应用启动的时候主动来获取一次,并且在节点上注册一个watcher,以后每次配置有更新,
       实时通知应用,获取最新配置信息。
④  业务逻辑中需要用到一些全局变量,比如一些消息中间件的消息队列通常有个offset,此时这个offset存放在zk上,这样集群每次
       发送者都能知道当前的发送进度。
⑤ 系统中有些信息需要动态获取,并且还会存在人工手动去修改这个信息。以前通常是暴露出接口,例如JMX接口,有了ZK后,只要将这
       些信息存放到ZK节点上即可
(3) 应用举例

  例如:同一个应用系统需要多台PC Server运行,但是他们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么久必须同时修改每台运行这个应用系统的PC Server,这样非常麻烦,而且容易出错。将配置信息保存在zookeeper的某个节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到zookeeper的通知,然后从zookeeper获取新的配置信息应用到系统中。ZooKeeper配置管理服务如下图所示:

《深入学习Zookeeper(1)--基础总结》 配置管理结构图 (1).png

  Zookeeper非常容易实现这种集中式的配置管理,比如,将所需要的配置信息放到
/Configurtaion节点上,集群中所有机器一启动就会通过
Client
/Configuration 这个节点进行监控
zk.exist("/Configuration″,true),并且实现Watcher回调方法
process(),那么在zookeeper上
/Configurtaion节点下数据发生变化的时候,每个机器都会收到通知,Watcher回调方法将会被执行,那么应用再去下数据即可
zk.getData("/Configuration″,false,null)

2.2统一命名服务(Name Service)

(1) 典型场景描述

  分布式应用中,通常需要有一套完整的命名规则,能够产生唯一的名称,通常情况先,用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,即对人友好又不会重复。将有层次的目录结构关联到一定资源上。

(2) 应用

  在分布式系统中个,通过使用命名服务,客户端应该能够根据指定的名字来获取资源服务的地址、提供者等信息。被命名的实体通常可以是集群中的机器,提供服务地址、进程对象等,这些统称为名字(Name)。其中较为常见的是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称Name。Name Service已经是Zookeeper内置的功能。你只要调用zookeeper的api就能实现。

(3) 应用举例

  分布式服务框架Dubbo中使用zookeeper来作为其命名服务,维护全局的服务地址列表。在Dubbo是实现中:服务提供者provider在启动的时候,向Zookeeper的指定节点:~/dubbo/${serviceName}/providers目录下写入自己的URL地址,这个操作就会完成服务的发布。一般数据存放路径为 /zookeeper-xxx/data/version-xx/log.x
  服务消费者启动的时候,订阅~/dubbo/${serviceName}/providers目录下的提供者URL地址。注意:所有向ZK上注册的地址都是临时节点,这样就能够保证服务提供者和消费者能够自动感应资源的变化。Dubbo还有针对服务粒度的监控,方法是订阅/dubbo/${serviceName}目录下所有提供者和消费者的信息。

2.3分布通知/协调(Distribution of notification/coordination)

(1) 典型场景描述

  Zookeeper中特有的watcher注册和异步通知机制,能够很好的实现分布式环境下不同系统之间的通知和协调,实现对数据变更的实时处理。使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的变化),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理。

(2) 应用
① 一种心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是通过ZK上某个节点关联,大大减少系统耦合。
② 一种系统调度模式:某系统由控制台和推送系统两部分组成,控制台的职责和控制推送系统进行相应的推送工作。管理人员在控制台的
       一些操作,实际上是修改了zk上某些节点的状态,而zk就把这些变化通知给他们注册watcher的客户端,即推送系统。从而做出
       相应的推送任务。
③ 一种工作汇报模式:一些类似于任务分发系统,子任务启动后,到ZK来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写
       回这个临时节点),这样任务管理者就能够实时知道任务进度。

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

2.4分布式锁(Distribute Lock)

(1) 典型场景描述

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

  • 保持独占:
      就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把ZK上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。
  • 控制时序:
      就是所有试图来获取这个锁的客户端,最终都是会被安排执行,只是有 个全局时序了。做法和上面基本类似,只是这里 /distribute_lock 已经预先存在,客户端在它下面创建临时有序节点。Zk的父节点(/distribute_lock)维持一份sequence,保证子节点创建的时序性, 从而也形成了每个客户端的全局时序。
(2) 应用

  共享锁在同一个进程中很容易实现,但是在跨进程或者在不同 Server 之间就不好实现了。Zookeeper 却很容易实现这个功能,实现方式也是需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用 exists(String path, boolean watch)方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。

其中节点被组织成目录树的形式,每个节点下面都可以有一些子节点。节点可以是以下四种类型:
PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;
PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;
EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;
EPHEMERAL_SEQUENTIAL:临时自动编号节点。监控节点变化时,可以监控一个节点的变化,也可以监控一个节点所有子节点的变化。

《深入学习Zookeeper(1)--基础总结》 共享锁流程图

(2) 详细描述

  在分布式锁服务中,有一种最典型应用场景,就是通过对集群进行Master选举,来解决分布式系统中的单点故障。什么是分布式系统中的单点故障:通常分布式系统采用主从模式,就是一个主控机连接多个处理节点。主节点负责分发任务,从节点负责处理任务,当我们的主节点发生故障时,那么整个系统就都瘫痪了,那么我们把这种故障叫作单点故障。

《深入学习Zookeeper(1)--基础总结》 主从模式分布式系统
《深入学习Zookeeper(1)--基础总结》 单点故障

  • 传统解决方案
       传统方式是采用一个备用节点,这个备用节点定期给当前主节点发送ping包,主节点收到ping包以后向备用节点发送回复Ack,当备用节点收到回复的时候就会认为当前主节点还活着,让他继续提供服务。

《深入学习Zookeeper(1)--基础总结》 传统解决方案

主节点挂了,这时候备用节点收不到回复了,然后他就认为主节点挂了接替他成为主节点

《深入学习Zookeeper(1)--基础总结》 传统解决方案.png

但是这种方式就是有一个
隐患,就是
网络问题,来看一网络问题会造成什么后果

《深入学习Zookeeper(1)--基础总结》 网络故障

也就是说我们的主节点的并没有挂,只是在回复的时候网络发生故障,这样我们的备用节点同样收不到回复,就会认为主节点挂了,然后备用节点将他的Master实例启动起来,这样我们的分布式系统当中就有了两个主节点也就是—
双Master,出现Master以后我们的从节点就会将它所做的事一部分汇报给了主节点,一部分汇报给了从节点,这样服务就全乱了。为了防止出现这种情况,我们引入了ZooKeeper,它虽然不能避免网络故障,但它能够保证每时每刻只有一个Master。我么来看一下ZooKeeper是如何实现的。

  • ZooKeeper解决方案
    (1) Master启动
       在引入了Zookeeper以后我们启动了两个主节点,”主节点-A”和”主节点-B”他们启动以后,都向ZooKeeper去注册一个节点。我们假设”主节点-A”锁注册地节点是"master-00001",”主节点-B”注册的节点是"master-00002",注册完以后进行选举编号最小的节点将在选举中获胜获得锁成为主节点,也就是我们的"主节点-A"将会获得锁成为主节点,然后"主节点-B"将被阻塞成为一个备用节点。那么,通过这种方式就完成了对两个Master进程的调度。

《深入学习Zookeeper(1)--基础总结》 ZooKeeper Master选举

(2) Master故障

如果"主节点-A"挂了,这时候他所注册的节点将被自动删除,ZooKeeper会自动感知节点的变化,然后再次发出选举,这时候"主节点-B"将在选举中获胜,替代"主节点-A"成为主节点。

《深入学习Zookeeper(1)--基础总结》 ZooKeeper Master选举

(3) Master 恢复

如果主节点恢复了,他会再次向ZooKeeper注册一个节点,这时候他注册的节点将会是"master-00003",ZooKeeper会感知节点的变化再次发动选举,这时候"主节点-B"在选举中会再次获胜继续担任"主节点","主节点-A"会担任备用节点。

《深入学习Zookeeper(1)--基础总结》 ZooKeeper Master选举

三、Session机制

3.1会话概述

   每个ZooKeeper客户端的配置中都包括集合体中服务器的列表。在启动时,客户端会尝试连接到列表中的一台服务器。如果连接失败,它会尝试连接另一台服务器,以此类推,直到成功与一台服务器建立连接或因为所有ZooKeeper服务器都不可用而失败。

《深入学习Zookeeper(1)--基础总结》 image.png

   一旦客户端与一台ZooKeeper服务器建立连接,这台服务器就会为该客户端创建一个新的会话。
每个会话都会有一个超时的时间设置,这个设置由创建会话的应用来设定。如果服务器在超时时间段内没有收到任何请求,则相应的会话会过期。一旦一个会话已经过期,就无法重新打开,并且任何与该会话相关联的短暂znode都会丢失。会话通常长期存在,而且会话过期是一种比较罕见的事件,但对应用来说,如何处理会话过期仍是非常重要的。

   只要一个会话空闲超过一定时间,都可以通过客户端发送ping请求(也称为心跳)保持会话不过期。ping请求由ZooKeeper的客户端库自动发送,因此在我们的代码中不需要考虑如何维护会话。这个时间长度的设置应当足够低,以便能档检测出服务器故障(由读超时体现),并且能够在会话超时的时间段内重新莲接到另外一台服务器。

3.2 故障切换

ZooKeeper客户端可以自动地进行故障切换,切换至另一台ZooKeeper服务器。并且关键的一点是,在另一台服务器接替故障服务器之后,所有的会话和相关的短暂Znode仍然是有效的。在故障切换过程中,应用程序将收到断开连接和连接至服务的通知。当客户端断开连接时,观察通知将无法发送;但是当客户端成功恢复连接后,这些延迟的通知会被发送。当然,在客户端重新连接至另一台服务器的过程中,如果应用程序试图执行一个操作,这个操作将会失败。这充分体现了在真实的ZooKeeper应用中处理连接丢失异常的重要性。

四、 Watch机制

   Zookeeper客户端在数据节点上设置监视,则当数据节点发生变化时,客户端会收到提醒。ZooKeeper中的各种读请求,如getDate(),getChildren(),和exists(),都可以选择加”监视点”(watch)。”监视点”指的是一种一次性的触发器(trigger),当受监视的数据发生变化时,该触发器会通知客户端。
(1) 监视机制有三个关键点:

①  "监视点"是一次性的,当触发一次之后,除非重新设置,新的数据变化不会提醒客户端
②  "监视点"将数据改变通知客户端。如果数据改变是客户端A引起的,不能保证"监视点"通知事件会在引发数据修改的函数返回前到达客户端A。
③  对于"监视点",ZooKeeper有如下保证:
      客户端一定是在接收到"监视"事件(watch event)之后才接收到数据的改变信息。

(3) ZooKeeper的”监视”机制保证以下几点:

①  "监视"事件的触发顺序和事件的分发顺序一致。
②  客户端将先接收到"监视"事件,然后才收到新的数据
③  "监视"事件触发的顺序与ZooKeeper服务器上数据变化的顺序一致

(4) 关于ZooKeeper”监视”机制的注意点:

①  "监视点"是一次性的。
②  由于"监视点"是一次性的,而且,从接收到"监视"事件到设置新"监视点"是有延时的,所以客户端可能监控不到数据的所有变化。
③  一个监控对象,只会被相关的通知触发一次。如果一个客户端设置了关于某个数据点exists和getData的监控,
    则当该数据被删除的时候,只会触发"文件被删除"的通知。
④  当客户端断开与服务器的连接时,客户端不再能收到"监视"事件,直到重新获得连接。所以关于Session的信息将被发送给所有ZooKeeper服务器。
    由于当连接断开时收不到"监视",所以在这种情况下,模块行为需要容错方面的设计。
    原文作者:未名枯草
    原文地址: https://www.jianshu.com/p/ccdf722597c1
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞