ZooKeeper是Google的Chubby提供的一个开源的、分布式的框架,它是Hadoop集群的管理者,同时提供一致性协调服务,就像“人民法官”一样监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终实现将简单易用的接口和性能高效、功能稳定的系统提供给用户的功能。

Zookeeper主要负责存储和管理大家都关心的数据,一旦这些数据的状态发生变化,Zookeeper就会通知那些注册在Zookeeper上的服务。简单来讲就是**zookeeper=文件系统+通知机制**。

Zookeeper的数据结构

Zookeeper的数据结构与Unix文件系统很类似,整体上可以看作是一棵树,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,与Unix文件系统唯一不同的是Zookeeper的每个节点都可以存放数据,每个节点称作一个ZNode,默认存储1MB的数据, 可以通过配置修改, 通常不建议在ZNode上存储大量的数据。Znode的引用方式是路径的引用,每个ZNode都可以通过其路径唯一标识

zookeeper与hadoop的版本 hadoop和zookeeper的关系_大数据

四种类型的ZNode

  • 持久化目录节点(ERSISTENT):客户端与Zookeeper断开连接后,该节点依旧存在。
  • 持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL):客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号。
  • 临时目录节点(EPHEMERAL):客户端与Zookeeper断开连接后,该节点被删除。
  • 临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL):客户端与Zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号。

说明:创建ZNode时设置顺序标识,ZNode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。

具体如下:

zookeeper与hadoop的版本 hadoop和zookeeper的关系_zookeeper与hadoop的版本_02

stat结构体

stat 查看根目录的详细信息:

[zk: localhost:2181(CONNECTED) 0] stat /
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1

如上所述,ZNode主要包含以下信息:

  • czxid-创建节点的事务 zxid:每次修改 ZooKeeper 状态都会收到一个 zxid 形式的时间戳,也就是 ZooKeeper 事务 ID。事务 ID 是 ZooKeeper 中所有修改总的次序。每个修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么 zxid1 在 zxid2 之前发生。
  • ctime :znode 被创建的毫秒数(从 1970 年开始)
  • mzxid:znode 最后更新的事务 zxid
  • mtime:znode 最后修改的毫秒数(从 1970 年开始)
  • pZxid:znode 最后更新的子节点 zxid
  • cversion:znode 子节点变化号,znode 子节点修改次数
  • dataversion:znode 数据变化号
  • aclVersion:znode 访问控制列表的变化号
  • ephemeralOwner:如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0
  • dataLength:znode 的数据长度
  • numChildren:znode 子节点数量

Zookeeper的应用场景

Zookeeper的主要应用场景有统一命名服务,统一配置管理,统一集群管理,服务器节点动态上下线等。

统一命名服务

在zookeeper的文件系统里创建一个目录,即有唯一的path。在我们使用tborg无法确定上游程序的部署机器时即可与下游程序约定好path,通过path即能互相探索发现。

举个例子来说:在分布式环境中,经常需要对服务进行统一命名,假如有一个服务部署了两个副本,直接调用具体的服务肯定有些不合适,因为我们并不清楚哪个服务可以更快的处理我们的请求,这时候我们可以将这三个服务进行统一命名,然后其内部再去负载调用。这样就可以调用最优的那个服务了。

zookeeper与hadoop的版本 hadoop和zookeeper的关系_选举机制_03

统一配置管理

程序总是需要配置的,如果程序分散部署在多台机器上,要逐个改变配置就变得困难。现在把这些配置全部放到zookeeper上去,保存在 Zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中就好。

分布式环境下,配置文件的同步可以由Zookeeper来实现具体步骤如下:

  1. 将配置文件写入Zookeeper的一个ZNode
  2. 各个客户端服务监听这个ZNode
  3. 一旦ZNode发生改变,Zookeeper将通知各个客户端服务

zookeeper与hadoop的版本 hadoop和zookeeper的关系_zookeeper_04

统一集群管理

Zookeeper可以实现实时监控节点状态变化。所有服务节点约定在父目录GroupMembers下创建临时目录节点,然后监听父目录节点的子节点变化消息,当三个节点的服务有一个假如死机了,其他两个节点可立即收到消息,实现实时监控。将这三个节点写入Zookeeper的一个ZNode,每个节点都去监听这个ZNode,当ZNode发生变化时,这些节点可实时收到变化状态,这个节点ZNode通常为主机master。

新服务节点加入也是一样,所有其他节点收到通知:新节点目录加入。所有其他节点创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。

zookeeper与hadoop的版本 hadoop和zookeeper的关系_选举机制_05

监听器的原理

zookeeper与hadoop的版本 hadoop和zookeeper的关系_选举机制_06

  1. 创建一个Main()线程
  2. 在Main()线程中创建两个线程,一个负责网络连接通信(connect),一个负责监听(listener)
  3. 通过connect线程将注册的监听事件发送给Zookeeper
  4. 将注册的监听事件添加到Zookeeper的注册监听器列表中
  5. Zookeeper监听到有数据或路径发生变化时,把这条消息发送给Listener线程
  6. Listener线程内部调用process()方法

Zookeeper分布式锁

有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

对于第一类,我们将Zookeeper上的一个ZNode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock 节点就释放出锁。

对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

Zookeeper队列管理

两种类型的队列:

1、同步队列,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。

2、队列按照 FIFO 方式进行入队和出队操作。 和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。

分布式与数据复制

Zookeeper作为一个集群提供一致的数据服务,自然,它要在所有机器间做数据复制。数据复制的好处:

1、容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作;

2、提高系统的扩展能力 :把负载分布到多个节点上,或者增加节点来提高系统的负载能力;

3、提高性能:让客户端本地访问就近的节点,提高用户访问速度。

从客户端读写访问的透明度来看,数据复制集群系统分下面两种:

1、写主(WriteMaster) :对数据的修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读写分离;

2、写任意(Write Any):对数据的修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。

对zookeeper来说,它采用的方式是写任意。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降(这也是它建立observer的原因),而响应能力则取决于具体实现方式,是延迟复制保持最终一致性,还是立即复制快速响应。

Zookeeper集群

Zookeeper集群虽然没有指定Master和Slave。但是,在Zookeeper工作时,会通过内部选举机制产生一个Leader节点,其他节点为Follower或者是Observer。

zookeeper与hadoop的版本 hadoop和zookeeper的关系_zookeeper与hadoop的版本_07

被声明为Observer的节点,不参与选举过程,也不参与写操作的”过半写成功“策略。

过半写成功策略:Leader节点接收到写请求后,这个Leader会将写请求广播给各个server,各个server会将该写请求加入待写队列,并向Leader发送成功信息,当Leader收到一半以上的成功消息后,说明该写操作可以执行。Leader会向各个server发送提交消息,各个server收到消息后开始写。

Follower和Observer只提供数据的读操作,当他们接收的写请求时,会将该请求转发给Leader节点。

集群中只要有半数以上的节点存活,Zookeeper集群就能正常服务。因此Zookeeper集群适合安装奇数台机器。

当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。

选举机制

Zookeeper集群是一主多从的模式,主为leader,从为follower,其中leader是通过选举得到。具体如下:

(1)服务器 1 启动,发起一次选举。服务器 1 投自己一票。此时服务器 1 票数一票,不够半数以上(3 票),选举无法完成,服务器 1 状态保持为 LOOKING;

(2)服务器 2 启动,再发起一次选举。服务器 1 和 2 分别投自己一票并交换选票信息:此时服务器 1 发现服务器 2 的 ID 比自己目前投票推举的(服务器 1)大,更改选票为推举服务器 2。此时服务器 1 票数 0 票,服务器 2 票数 2 票,没有半数以上结果,选举无法完成,服务器 1,2 状态保持 LOOKING;

(3)服务器 3 启动,发起一次选举。此时服务器 1 和 2 都会更改选票为服务器 3。此次投票结果:服务器 1 为 0 票,服务器 2 为 0 票,服务器 3 为 3 票(这只是本次的投票结果,算上之前的2步结果:服务器 1 为 2 票,服务器 2 为 2 票,服务器 3 为 3 票)。此时服务器 3 的票数已经超过半数,服务器 3 当选 Leader。服务器 1,2 更改状态为 FOLLOWING,服务器 3 更改状态为 LEADING;

(4)服务器 4 启动,发起一次选举。此时服务器 1,2,3 已经不是 LOOKING 状态,不会更改选票信息。交换选票信息结果:服务器 3 为 3 票,服务器 4 为 1 票。此时服务器 4服从多数,更改选票信息为服务器 3,并更改状态为 FOLLOWING;

(5)服务器 5 启动,同 4 一样当小弟。

此外,Zookeeper集群有如下特点:

  • Zookeeper:一个领导者(leader),多个跟随者(follower)组成的集群
  • Leader负责进行投票的发起和决议,更新系统状态
  • Follower用于接收客户请求并向客户端返回结果,在选举Leader过程中参与投票
  • 集群中只要有半数以上节点存活,Zookeeper集群就能正常服务,所以Zookeeper适合安装奇数台服务器
  • 全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
  • 更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
  • 数据更新原子性,一次数据更新要么成功,要么失败
  • 实时性,在一定时间范围内,client能读到最新数据