ZooKeeper(一)基础

引言

因为一台服务器的处理能力是有限的,在大用户量和高并发的状况下,一般须要不少台服务器同时工做对外提供服务。这么多机器同时工做,怎么来管理这些服务器呢?好比某台服务器宕机了,就要确保请求再也不发送到这台服务器;某个程序的配置修改了,多台服务器上的配置要做相应修改;或者多个服务必须按照特定的顺序执行;若是多个服务是有事务的,若是中间某个服务失败了,那这多个服务都必须回滚。对于这些状况最好是有一个专门的管理中心来管理,而ZooKeeper就能够做为这个管理中心。node

ZooKeeper是什么

ZooKeeper是Apache下的一个Java开源项目(最初由Yahoo开发,后捐献给了Apache)。
ZooKeeper的原始功能很简单,基于它的层次型的目录树的数据结构,并经过对树上的节点进行有效管理,能够设计出各类各样的分布式集群管理功能。此外,ZooKeeper自己也是分布式的。算法

ZooKeeper的数据模型

Zookeeper 会维护一个具备层次关系的树状的数据结构,它很是相似于一个标准的文件系统,以下图所示:
数据库

ZooKeeper树状结构中的每个节点称做——<font color=#D2691E>Znode</font>。每个节点都有一个名称,且以 / 开头,其中最顶层的节点是 / ,最终的状况就是每个节点在树状结构中,会有一个相似绝对路径的惟一标识,如上图中的 Server1 这个Znode 的标识为 /NameService/Server1服务器

Znode的结构

虽然ZooKeeper的树状结构相似文件系统,可是Znode兼有文件和目录的特色,一个Znode既能在它下面建立子节点,做为路径标识的一部分,同时这个节点同时也能存储数据,但这个存储不是设计用来做常规的数据库存储,而主要存放分布式应用的配置信息、状态信息等,这些数据的共同特性就是它们都是很小的数据,一般以KB为单位。网络

ZooKeeper数据模型中的每一个Znode都维护着一个 <font color=#D2691E>stat</font> 结构。
一个stat仅提供一个Znode的元数据。它由版本号,操做控制列表(ACL),时间戳和数据长度组成。session

  • 版本号 - 每一个Znode都有版本号,这意味着每当与Znode相关联的数据发生变化时,其对应的版本号也会增长。当多个ZooKeeper客户端尝试在同一Znode上执行操做时,版本号的使用就很重要。数据结构

    • version : 当前节点内容(数据)的版本号
    • cversion : 当前节点子节点的版本号
    • aversion : 当前节点ACL的版本号
  • 操做控制列表(ACL) - ACL基本上是访问Znode的认证机制。它管理全部Znode读取和写入操做。每个节点都拥有本身的ACL,这个列表规定了用户的权限,即限定了特定用户对目标节点能够执行的操做。权限的种类有:并发

    • CREATE : 建立子节点的权限
    • READ : 获取节点数据和节点列表的权限
    • WRITE : 更新节点数据的权限
    • DELETE : 删除子节点的权限
    • ADMIN : 设置节点ACL的权限
  • 时间戳 - 导致ZooKeeper节点状态改变的每个操做都将使节点接收到一个Zxid格式的时间戳,而且这个时间戳全局有序。也就是说,每一个对节点的改变都将产生一个惟一的Zxid。若是Zxid1的值小于Zxid2的值,那么Zxid1所对应的事件发生在Zxid2所对应的事件以前。实际上,ZooKeeper的每一个节点维护者三个Zxid值,为别为:cZxid、mZxid、pZxid。app

    • cZxid: 是节点的建立时间所对应的Zxid格式时间戳。
    • mZxid:是节点的修改时间所对应的Zxid格式时间戳。
  • 数据长度 - 存储在znode中的数据总量是数据长度。最多能够存储1MB的数据。

每一个Znode由3部分组成:异步

  1. stat:此为状态信息, 描述该Znode的版本, 权限等信息
  2. data:与该Znode关联的数据
  3. children:该Znode下的子节点

节点类型

ZooKeeper中的节点有两种,分别为临时(ephemeral)节点和永久(persistent)节点。节点的类型在建立时即被肯定,而且不能改变。

  • <font color=#D2691E>临时节点</font>:该节点的生命周期依赖于建立它们的会话。一旦会话(Session)结束,临时节点将被自动删除,固然能够也能够手动删除。虽然每一个临时的Znode都会绑定到一个客户端会话,但他们对全部的客户端仍是可见的。另外,ZooKeeper的临时节点不容许拥有子节点。<br/>
  • <font color=#D2691E>永久节点</font>:该节点的生命周期不依赖于会话,客户端建立一个永久节点后即便断开链接,改节点仍然存在,而且只有在客户端显式执行删除操做后,永久节点才被删除。默认建立的节点都是永久节点

顺序节点

<font color=#D2691E>顺序节点</font>能够是持久的或临时的。当一个新的Znode被建立为一个顺序节点时,ZooKeeper经过将10位的序列号附加到原始名称来设置Znode的路径。例如,若是将具备路径/myapp的Znode建立为顺序节点,则ZooKeeper会将路径更改成/myapp0000000001,并将下一个序列号设置为0000000002,这个序列号由父节点维护。若是两个顺序节点是同时建立的,那么ZooKeeper不会对每一个Znode使用相同的数字。顺序节点在锁定和同步中起重要做用,顺序号能够被用于为全部的事件进行全局排序,这样客户端能够经过顺序号推断事件的顺序。

综合上面两节内容,ZooKeeper有四种形式的节点:

  • PERSISTENT(永久节点)
  • PERSISTENT_SEQUENTIAL(永久有序节点)
  • EPHEMERAL(临时节点)
  • EPHEMERAL_SEQUENTIAL(临时有序节点)

Znode的属性

一个节点自身拥有表示其状态的许多重要属性,以下图所示。
Znode的属性

ZooKeeper Session(会话)

Zookeeper 的客户端和服务器通讯采用长链接方式,每一个客户端和服务器经过心跳来保持链接,这个链接状态称为 Session。

会话对于ZooKeeper的操做很是重要。会话中的请求按FIFO顺序执行。一旦客户端链接到服务器,将创建会话并向客户端分配会话ID 。

客户端以特定的时间间隔发送心跳以保持会话有效。若是ZooKeeper集群在超过指定的时间都没有从客户端接收到心跳,则会话会被认为结束(会话超时)。会话超时一般以毫秒为单位。

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会通知客户端。,监视事件能够理解为一次性的触发器。官方定义以下:

a watch event is one-time trigger, sent to the client that set the watch, which occurs 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)。网络延迟或者其余因素可能致使不一样的客户端在不一样的时刻感知某一监视事件,可是不一样的客户端所看到的一切具备一致的顺序(有序一致性)。

ZooKeeper能够为全部的读操做设置watch,这些读操做包括:exists()、getChildren()及getData()。

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

watch注册与处触发

  • exists操做上的watch,在被监视的Znode建立、删除或数据更新时被触发。
  • getData操做上的watch,在被监视的Znode删除或数据更新时被触发。在被建立时不能被触发,由于只有Znode必定存在,getData操做才会成功。
  • getChildren操做上的watch,在被监视的Znode的子节点建立或删除,或是这个Znode自身被删除时被触发。能够经过查看watch事件类型来区分是Znode,仍是他的子节点被删除:NodeDelete表示Znode被删除,NodeDeletedChanged表示子节点被删除。

Zookeeper 中的监视是轻量级的,所以容易设置、维护和分发。当客户端与 Zookeeper 服务器失去联系时,客户端并不会收到监视事件的通知,只有当客户端从新链接后,若在必要的状况下,之前注册的监视会从新被注册并触发,对于开发人员来讲这一般是透明的。只有一种状况会致使监视事件的丢失,即:经过exists()设置了某个znode节点的监视,可是若是某个客户端在此znode节点被建立和删除的时间间隔内与zookeeper服务器失去了联系,该客户端即便稍后从新链接 zookeeper服务器后也得不到事件通知。

咱们使用ZooKeeper,简单地理解就是使用ZooKeeper的<font color=#D2691E>文件系统+通知机制</font>

ZooKeeper集群

Zookeeper服务自身组成一个集群(2n+1个服务容许n个失效)。在Zookeeper集群中,主要分为三者角色,而每个节点同时只能扮演一种角色,这三种角色分别是:

  • <font color=#D2691E>Leader</font>:

    • 事务请求的惟一调度和处理者,保证集群事务处理的顺序性
    • 集群内各服务器的调度者 leader会与每一个follower和observer创建一个tcp长链接,而且为每一个follower和observer创建一个learnerhandler,进行数据同步,请求转发和proposal投票等功能。
  • <font color=#D2691E>Follower</font>:

    • 处理客户端的非事务请求,转发事务请求给leader
    • 参与事务请求Proposal投票
    • 参与leader选举投票
    • 判断当前请求是否为事务请求,如果则转发给leader完成事务日志记录后,向leader发送ack信息
  • <font color=#D2691E>Observer</font>:

    • 与Leader进行数据交换(同步)
    • 能够接收客户端链接,将写请求转发给Leader节点
    • Observer不参与投票过程,只同步Leader的状态。
Propsal投票:每个事务都须要集群中超过半数的机器投票承认才能被真正地应用到ZK的内存数据库中。

下图描述了 ZooKeeper集群“客户端-服务端”的结构

ZooKeeper集群的结构

ZooKeeper的一致性特色

Zookeeper提供的一致性是弱一致性,数据的同步有以下规则:ZooKeeper确保对znode树的每个修改都会被同步到集群中超过半数的机器上,那么就认为更新成功。因此就有可能有节点的数据不是最新的而被客户端访问到。而且会有一个时间点,数据在集群中是不一致的.也就是Zookeeper只保证最终一致性,可是实时的一致性能够由客户端调用本身来保证,经过调用sync()方法

  • 单一视图性(Single System Image):client不论链接到哪一个server,看到的数据是同样的。
  • 实时性:伪实时性。Zookeeper保证客户端将在一个时间间隔范围内(集群大的时候可能要十几秒)从server得到的信息是实时的。但因为网络延时等缘由,Zookeeper不能保证两个客户端能同时获得刚更新的数据,若是须要最新数据,应该在读数据以前调用sync()接口。
  • 原子性(Atomicity):操做要么所有成功,要么所有失败,没有中间状态。
  • 顺序性:客户端的更新顺序与它们被发送的顺序相一致。具体表现为全局有序和偏序两种:全局有序是指若是在一台服务器上消息a在消息b前发布,则在全部Server上消息a都将在消息b前被发布;偏序是指若是一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
  • 可靠性:一旦一个更新操做被应用,那么在客户端再次更新它以前,它的值将不会改变。这个保证将会产生下面两种结果:

    1. 若是客户端成功地得到了正确的返回代码,那么说明更新已经成果。若是不可以得到返回代码(因为通讯错误、超时等等),那么客户端将不知道更新操做是否生效。
    2. 当从故障恢复的时候,任何client看到的已经执行成功的更新操做将不会被回滚。

有了这些一致性保证, ZooKeeper 更高级功能的设计与实现将会变得很是容易,例如: leader 选举、队列以及可撤销锁等机制的实现。

用分布式系统的CAP原则来分析ZooKeeper.

  1. C: ZooKeeper保证了最终一致性,在十几秒能够sync到各个节点.
  2. A: ZooKeeper保证了可用性,数据老是可用的,没有锁.而且有一大半的节点所拥有的数据是最新的,实时的. 若是想保证取得是数据必定是最新的,须要手工调用sync()
  3. P: 有2点须要分析的.

    • 节点多了会致使写数据延时很是大,由于须要多个节点同步.
    • 节点多了leader选举很是耗时, 就会放大网络的问题. 能够经过引入observer节点缓解这个问题.

ZooKeeper的工做原理

在ZooKeeper的集群中,各个节点共有下面3种角色和4种状态:

  • 角色:leader,follower,observer
  • 状态:leading,following,observing,looking

4种状态的解释:

  • LOOKING:当前server不知道leader是谁,正在搜寻。
  • LEADING:当前server即为选举出来的leader。
  • FOLLOWING:leader已经选举出来,当前server与之同步。
  • OBSERVING:observer的行为在大多数状况下与follower彻底一致,可是他们不参加选举和投票,而仅仅接受(observing)选举和投票的结果。

ZooKeeper的核心是原子广播,这个机制保证了各个server之间的同步。实现这个机制的协议叫作Zab协议(ZooKeeper Atomic Broadcast protocol)。Zab协议有两种模式,它们分别是恢复模式(Recovery选主)和广播模式(Broadcast同步)。
当服务启动或者在leader崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数server完成了和leader的状态同步之后,恢复模式就结束了。状态同步保证了leader和Server具备相同的系统状态。

为了保证事务的顺序一致性,ZooKeeper采用了递增的事务id号(zxid)来标识事务。全部的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch(每leader选举一次+1),标识当前属于那个leader的统治时期。低32位为事务操做次数(每增长一次事务+1)

关于Leader Election

当leader崩溃或者集群启动,这时候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会记录事务日志并按期进行快照,方便在恢复时进行状态恢复。选主的具体流程图以下所示:
basic paxos流程

fast paxos流程是在选举过程当中,某Server首先向全部Server提议本身要成为leader,当其它Server收到提议之后,解决epoch和zxid的冲突,并接受对方的提议,而后向对方发送接受提议完成的消息,重复这个流程,最后必定能选举出Leader。其流程以下所示:
fast paxos流程

相关文章
相关标签/搜索