1. ZooKeeper 的学习 与应用
1.1. 概述
ZooKeeper 是 Apache 在非常多云计算项目中的一个,与 Hadoop 密切相关,这样的情况导致我一開始觉得 ZooKeeper 的搭建须要 Hadoop 项目作为支持,可是最后发现全然不须要,它是能够单独执行的一个项目。
在网上看到了一个非常不错的关于 ZooKeeper 的介绍: 顾名思义动物园管理员,他是拿来管大象 (Hadoop) 、 蜜蜂 (Hive) 、 小猪 (Pig) 的管理员, Apache Hbase 和 Apache Solr 以及 LinkedIn sensei 等项目中都採用到了 Zookeeper 。 ZooKeeper 是一个分布式的,开放源代码的分布式应用程序协调服务, ZooKeeper 是以 Fast Paxos 算法为基础,实现同步服务,配置维护和命名服务等分布式应用。
从介绍能够看出, ZooKeeper 更倾向于对大型应用的协同维护管理工作。 IBM 则给出了 IBM 对 ZooKeeper 的认知: Zookeeper 分布式服务框架是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中常常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
总之,我觉得它的核心词就是一个单词,协调。
1.2. ZooKeeper 的特征
在 Hadoop 权威指南中看到了关于 ZooKeeper 的一些核心特征,阅读之后感觉总结的甚是精辟,在这里引用并总结。
1.2.1. 简易
ZooKeeper 的最重要核心就是一个精简文件系统,提供一些简单的操作以及附加的抽象(比如排序和通知)。
1.2.2. 易表达
ZooKeeper 的原型是一个丰富的集合,它们是一些已建好的块,能够用来构建大型的协作数据结构和协议,比如:分布式队列、分布式锁以及一组对等体的选举。
1.2.3. 高可用性
ZooKeeper 执行在一些集群上,被设计成可用性较高的,因此应用程序能够依赖它。 ZooKeeper 能够帮助你的系统避免单点故障,从而建立一个可靠的应用程序。
1.2.4. 松散耦合
ZooKeeper 的交互支持參与者之间并不了解对方。比如: ZooKeeper 能够被当做一种公共的机制,使得进程彼此不知道对方的存在也能够相互发现而且交互,对等方可能甚至不是同步的。
这一特点我感觉最能体如今集群的部署启动过程中。像 Hadoop 当把配置文件写好之后,然后执行启动脚本,则 251 , 241 , 242 中作为集群的虚拟机是同步启动的,也就是 DataNode , NameNode , TaskTracker ,以及 JobTracker 的启动并执行时在一次启动过程中启动的,就是执行一次启动脚本文件,则都启动起来。可是 ZooKeeper 的启动过程却不是这种。我在 251 , 241 , 242 部署了 ZooKeeper 集群,并进行启动,则启动的过程是这种:首先 ssh 到 251 然后启动,这时候 251 的集群节点启动起来,可是控制台一直报错,大概的含义就是没有检測到其它两个结点。接着分别 ssh 到 241 , 242 ,分别启动集群中的剩下的结点,当 241 启动起来时,回到 251 查看,发现报错的信息降低,意思是仅仅差一个结点。当 251 , 241 , 242 三台server的结点所有启动起来,则三台的server的控制台打印出正常的信息。
1.2.5. ZooKeeper 是一个库
ZooKeeper 提供了一个开源的、共享的运行存储,以及通用协作的方法,分担了每一个程序猿写通用协议的负担。随着时间的推移,人们能够添加和改进这个库来满足自己的需求。
1.3. Zookeeper 基本知识
在这一小结,我介绍关于 ZooKeeper 的一些基本理论知识,以便对 ZooKeeper 有一个基本感性的认识吧,因为学习的时间不长,有些的认识可能是比較片面的,之后假设有了更深层次的认识,会补充于之后的月总结中。
1.3.1. 层次化的名字空间
ZooKeeper 的整个名字空间的结构是层次化的,和一般的 Linux 文件系统结构非常相似,一颗非常大的树。这也就是 ZooKeeper 的数据结构情况。名字空间的层次由斜杠 / 来进行切割,在名称空间里面的每个结点的名字空间唯一由这个结点的路径来确定。
图 3.1 ZooKeeper 的层次化名字空间
每个节点拥有自身的一些信息,包含:数据、数据长度、创建时间、改动时间等等。从这样一类既含有数据,又作为路径表标示的节点的特点中,能够看出, ZooKeeper 的节点既能够被看做是一个文件,又能够被看做是一个文件夹,它同一时候具有二者的特点。为了便于表达,今后我们将使用 Znode 来表示所讨论的 ZooKeeper 节点。
1.3.2. Znode
Znode 维护着数据、 ACL ( access control list ,訪问控制列表)、时间戳等交换版本等数据结构,它通过对这些数据的管理来让缓存生效而且令协调更新。每当 Znode 中的数据更新后它所维护的版本将添加,这很类似于数据库中计数器时间戳的操作方式。
另外 Znode 还具有原子性操作的特点:命名空间中,每个 Znode 的数据将被原子地读写。读操作将读取与 Znode 相关的全部数据,写操作将替换掉全部的数据。除此之外,每个节点都有一个訪问控制列表,这个訪问控制列表规定了用户操作的权限。
ZooKeeper 中相同存在暂时节点。这些节点与 session 同一时候存在,当 session 生命周期结束,这些暂时节点也将被删除。暂时节点在某些场合也发挥着很关键的数据。
1.3.3. Watch 机制
Watch 机制就和单词本身的意思一样,看。看什么?详细来讲就是某一个或者一些 Znode 的变化。官方给出的定义:一个 Watch 事件是一个一次性的触发器,当被设置了 Watch 的数据发生了改变的时候,则server将这个改变发送给设置了 Watch 的client,以便通知它们。
Watch 机制主要有下面三个特点:
1 一次性的触发器( one-time trigger )
当数据改变的时候,那么一个 Watch 事件会产生而且被发送到client中。可是client仅仅会收到一次这种通知,假设以后这个数据再次发生改变的时候,之前设置 Watch 的client将不会再次收到改变的通知,由于 Watch 机制规定了它是一个一次性的触发器。
2 发送给client
这个表明了 Watch 的通知事件是从server发送给client的,是异步的,这就表明不同的client收到的 Watch 的时间可能不同,可是 ZooKeeper 有保证:当一个client在看到 Watch 事件之前是不会看到结点数据的变化的。比如: A=3 ,此时在上面设置了一次 Watch ,假设 A 突然变成 4 了,那么client会先收到 Watch 事件的通知,然后才会看到 A=4 。
3 被设置 Watch 的数据
这表明了一个结点能够变换的不同方式。一个 Znode 变化方式有两种,结点本身数据的变化以及结点孩子的变化。因此 Watch 也能够设置为这个 Znode 的结点数据,当然也能够设置为 Znode 结点孩子。
1.3.4. ACL 訪问控制列表
这是另外一个和 Linux 操作系统很相似的地方, ZooKeeper 使用 ACL 来控制对旗下 Znode 结点们的訪问。 ACL 的实现和 Linux 文件系统的訪问权限十分类似:它通过设置权限为来表明是否同意对一个结点的相关内容的改变。
可是与传统 Linux 机制不太同样,一个结点的数据没有类似“拥有者,组用户,其它用户”的概念,在 ZooKeeper 中, ACL 通过设置 ID 以及与其关联的权限来完毕訪问控制的。 ACL 的权限组成语法是:
(scheme:expression, perms)
前者表明设置的 ID ,逗号后面表示的是 ID 相关的权限,比如:
( ip:172.16.16.1 , READ)
指明了 IP 地址为如上的用户的权限为仅仅读。
下面列举下面 ACL 所具有的权限
CREATE :表明你能够创建一个 Znode 的子结点。
READ :你能够得到这个结点的数据以及列举该结点的子结点情况。
WRITE :设置一个结点的数据。
DELETE :能够删除一个结点
ADMIN :对一个结点设置权限。
1.4. ZooKeeper 的部署以及简单使用
要想使用 ZooKeeper ,首先就要把它部署在server上跑起来,就想 Apache , Tomcat , FtpServer 等server一样。 ZooKeeper 的部署方式主要有三种,单机模式、伪集群模式、集群模式。事实上剩下的两种模式都是集群模式的特殊情况。
1.4.1. 主要的环境变量配置
Java 大型的项目中,环境变量的配置非常重要,假设没有非常好的配置环境变量的话,甚至项目连启动都是难事。
export ZOOKEEPER_HOME=/ home /zookeeper-3.3. 3
export PATH=$PATH:$ZOOKEEPER_HOME/bin:$ZOOKEEPER_HOME/conf
1.4.2. ZooKeeper 的单机模式部署
ZooKeeper 的单机模式一般是用来高速測试client应用程序的,在实际过程中不可能是单机模式。单机模式的配置也比較简单。
l 编写配置文件 zoo.cfg
zookeeper-3.3.3/conf 目录以下就是要编写配置文件的位置了。在目录以下新建一个文件 zoo.cfg 。 ZooKeeper 的执行默认是读取 zoo.cfg 文件中面的内容的。以下是一个最简单的配置文件的例子:
tickTime=2000
dataDir=/var/zookeeper
clientPort=2181
在这个文件里,我们须要指定 dataDir 的值,它指向了一个文件夹,这个文件夹在開始的时候须要为空。以下是每一个參数的含义:
tickTime :基本事件单元,以毫秒为单位。这个时间是作为 Zookeeper server之间或client与server之间维持心跳的时间间隔,也就是每一个 tickTime 时间就会发送一个心跳。
dataDir :存储内存中数据库快照的位置,顾名思义就是 Zookeeper 保存数据的文件夹,默认情况下, Zookeeper 将写数据的日志文件也保存在这个文件夹里。
clientPort :这个port就是client连接 Zookeeper server的port, Zookeeper 会监听这个port,接受client的訪问请求。
使用单机模式时用户须要注意:这样的配置方式下没有 ZooKeeper 副本,所以假设 ZooKeeper server出现问题, ZooKeeper 服务将会停止。
l 执行执行脚本
在 zookeeper-3.3.3/bin 目录以下执行 zkServer.sh 就可以,执行完成之后则 ZooKeeper 服务变启动起来。
./ zkServer.sh start
脚本默认调用 zoo.cfg 里面的配置,因此程序正常启动。
1.4.3. ZooKeeper 的集群模式部署
ZooKeeper 的集群模式下,多个 Zookeeper server在工作前会选举出一个 Leader ,在接下来的工作中这个被选举出来的 Leader 死了,而剩下的 Zookeeper server会知道这个 Leader 死掉了,在活着的 Zookeeper 集群中会继续选出一个 Leader ,选举出 Leader 的目的是为了能够在分布式的环境中保证数据的一致性。如图所看到的:
图 3.2 ZooKeeper 集群模式图
l 确认集群server的数量
因为 ZooKeeper 集群中,会有一个 Leader 负责管理和协调其它集群server,因此server的数量通常都是单数,比如 3 , 5 , 7... 等,这样 2n+1 的数量的server就能够同意最多 n 台server的失效。
l 编写配置文件
配置文件须要在每台server中都要编写,下面是一个配置文件的样本:
# Filename zoo.cfg
tickTime=2000
dataDir=/var/zookeeper/
clientPort=2181
initLimit=5
syncLimit=2
server.1= 202.115.36.251 :2888:3888
server.2= 202.115.36.241 :2888:3888
server.3= 202.115.36.242 :2888:3888
initLimit :这个配置项是用来配置 Zookeeper 接受client(这里所说的client不是用户连接 Zookeeper server的client,而是 Zookeeper server集群中连接到 Leader 的 Follower server)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是 tickTime )长度后 Zookeeper server还没有收到client的返回信息,那么表明这个client连接失败。总的时间长度就是 5*2000=10 秒 。
syncLimit :这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2*2000=4 秒
server.A=B : C : D :当中 A 是一个数字,表示这个是第几号server; B 是这个server的 ip 地址; C 表示的是这个server与集群中的 Leader server交换信息的port; D 表示的是万一集群中的 Leader server挂了,须要一个port来又一次进行选举,选出一个新的 Leader ,而这个port就是用来运行选举时server相互通信的port。假设是伪集群的配置方式,因为 B 都是一样,所以不同的 Zookeeper 实例通信port号不能一样,所以要给它们分配不同的port号。
l 创建 myid 文件
除了改动 zoo.cfg 配置文件,集群模式下还要配置一个文件 myid ,这个文件在 dataDir 文件夹下,这个文件中面就仅仅有一个数据就是 A 的值, Zookeeper 启动时会读取这个文件,拿到里面的数据与 zoo.cfg 里面的配置信息比較从而推断究竟是那个 server 。
l 执行执行脚本
和单机模式下的执行方式基本同样,值得注意的地方就是要分别在不同server上执行一次,比如分别在 251 , 241 , 242 上执行:
./ zkServer.sh start
这样才干使得整个集群启动起来。
1.4.4. ZooKeeper 的集群伪分布
事实上在企业中式不会存在的,另外为了測试一个client程序也没有必要存在,仅仅有在物质条件比較匮乏的条件下才会存在的模式。
集群伪分布模式就是在单机下模拟集群的 ZooKeeper 服务,在一台机器上面有多个 ZooKeeper 的 JVM 同一时候执行。
l 确认集群伪server的数量
2n+1 ,和之前的集群分布同样。
l 编写配置文件
在 /conf 目录新建三个配置文件, zoo1.cfg , zoo2.cfg 以及 zoo3.cfg 。配置文件分别例如以下编写:
Zoo1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/hadoop-0.20.2/zookeeper-3.3.1/d_1
clientPort=2181
server.1=localhost:2887:3887
server.2=localhost:2888:3888
server.3=localhost:2889:3889
Zoo2.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/hadoop-0.20.2/zookeeper-3.3.1/d_ 2
clientPort=218 2
server.1=localhost:2887:3887
server.2=localhost:2888:3888
server.3=localhost:2889:3889
Zoo3.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/hadoop-0.20.2/zookeeper-3.3.1/d_ 3
clientPort=218 3
server.1=localhost:2887:3887
server.2=localhost:2888:3888
server.3=localhost:2889:3889
因为三个服务都在同一台电脑上,因此这里要保证地址的唯一性,因此要特别注意 IP 地址和port号不要互相冲突,以免影响程序的正确运行。
l 创建 myid 文件
这个同集群模式部署,在各自的目录以下创建。
l 执行执行脚本
因为全部的配置文件都在 /conf 目录以下,因此要运行三次,并且要加文件名称的參数,不然会默认运行 zoo.cfg 这个文件,例如以下:
./ zkServer.sh start zoo1.cfg
./ zkServer.sh start zoo2.cfg
./ zkServer.sh start zoo3.cfg
运行完毕后,将完毕 ZooKeeper 的集群伪分布的启动。
1.4.5. 通过 ZooKeeper 命令行工具訪问 ZooKeeper
ZooKeeper 命令行工具类似于 Linux 的 shell 环境,只是功能肯定不及 shell 啦,可是使用它我们能够简单的对 ZooKeeper 进行訪问,数据创建,数据改动等操作。
当启动 ZooKeeper 服务成功之后,输入下述命令,连接到 ZooKeeper 服务:
zkCli.sh –server 202.115.36.251 :2181
连接成功后,系统会输出 ZooKeeper 的相关环境以及配置信息,并在屏幕输出“ Welcome to ZooKeeper ”等信息。
命令行工具的一些简单操作例如以下:
1 )使用 ls 命令来查看当前 ZooKeeper 中所包括的内容:
[zk: 202.115.36.251:2181(CONNECTED) 1] ls /
2 )创建一个新的 znode ,使用 create /zk myData 。这个命令创建了一个新的 znode 节点“ zk ”以及与它关联的字符串:
[zk: 202.115.36.251:2181(CONNECTED) 2] create /zk "myData"
3 )我们执行 get 命令来确认 znode 是否包括我们所创建的字符串:
[zk: 202.115.36.251:2181(CONNECTED) 3] get /zk
4 )以下我们通过 set 命令来对 zk 所关联的字符串进行设置:
[zk: 202.115.36.251:2181(CONNECTED) 4] set /zk "zsl"
5 )以下我们将刚才创建的 znode 删除:
[zk: 202.115.36.251:2181(CONNECTED) 5] delete /zk
1.4.6. 使用 API 来訪问 ZooKeeper
API 訪问 ZooKeeper 才是client基本的使用手段,通过在client编写丰富多彩的程序,来达到对 ZooKeeper 的利用。这里给出一个简单的样例:(深入的还没能力给出啊,样例是从网上找的非常清晰明了)
1. import java.io.IOException;
2.
3. import org.apache.zookeeper.CreateMode;
4. import org.apache.zookeeper.KeeperException;
5. import org.apache.zookeeper.Watcher;
6. import org.apache.zookeeper.ZooDefs.Ids;
7. import org.apache.zookeeper.ZooKeeper;
8.
9. public class demo {
10. // 会话超时时间,设置为与系统默认时间一致
11. private static final int SESSION_TIMEOUT=30000;
12.
13. // 创建 ZooKeeper 实例
14. ZooKeeper zk;
15.
16. // 创建 Watcher 实例
17. Watcher wh=new Watcher(){
18. public void process(org.apache.zookeeper.WatchedEvent event)
19. {
20. System.out.println(event.toString());
21. }
22. };
23.
24. // 初始化 ZooKeeper 实例
25. private void createZKInstance() throws IOException
26. {
27. zk=new ZooKeeper("localhost:2181",demo.SESSION_TIMEOUT,this.wh);
28.
29. }
30.
31. private void ZKOperations() throws IOException,InterruptedException,KeeperException
32. {
33. System.out.println("\n1. 创建 ZooKeeper 节点 (znode : zoo2, 数据: myData2 ,权限: OPEN_ACL_UNSAFE ,节点类型: Persistent");
34. zk.create("/zoo2","myData2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
35.
36. System.out.println("\n2. 查看是否创建成功: ");
37. System.out.println(new String(zk.getData("/zoo2",false,null)));
38.
39. System.out.println("\n3. 改动节点数据 ");
40. zk.setData("/zoo2", "shenlan211314".getBytes(), -1);
41.
42. System.out.println("\n4. 查看是否改动成功: ");
43. System.out.println(new String(zk.getData("/zoo2", false, null)));
44.
45. System.out.println("\n5. 删除节点 ");
46. zk.delete("/zoo2", -1);
47.
48. System.out.println("\n6. 查看节点是否被删除: ");
49. System.out.println(" 节点状态: ["+zk.exists("/zoo2", false)+"]");
50. }
51.
52. private void ZKClose() throws InterruptedException
53. {
54. zk.close();
55. }
56.
57. public static void main(String[] args) throws IOException,InterruptedException,KeeperException {
58. demo dm=new demo();
59. dm.createZKInstance( );
60. dm.ZKOperations();
61. dm.ZKClose();
62. }
63.}
此类包括两个基本的 ZooKeeper 函数,分别为 createZKInstance ()和 ZKOperations ()。当中 createZKInstance ()函数负责对 ZooKeeper 实例 zk 进行初始化。 ZooKeeper 类有两个构造函数,我们这里使用 “ ZooKeeper ( String connectString, , int sessionTimeout, , Watcher watcher ) ” 对其进行初始化。因此,我们须要提供初始化所需的,连接字符串信息,会话超时时间,以及一个 watcher 实例。 17 行到 23 行代码,是程序所构造的一个 watcher 实例,它可以输出所发生的事件。
ZKOperations ()函数是我们所定义的对节点的一系列操作。它包含:创建 ZooKeeper 节点( 33 行到 34 行代码)、查看节点( 36 行到 37 行代码)、改动节点数据( 39 行到 40 行代码)、查看改动后节点数据( 42 行到 43 行代码)、删除节点( 45 行到 46 行代码)、查看节点是否存在( 48 行到 49 行代码)。另外,须要注意的是:在创建节点的时候,须要提供节点的名称、数据、权限以及节点类型。此外,使用 exists 函数时,假设节点不存在将返回一
个 null 值。
1.5. 小结
对于 ZooKeeper 的认识眼下处在比較浅显的状态,了解到了主要的服务的部署以及大概 ZooKeeper 的工作原理。非常多东西都是仅仅懂得皮毛,如今可以深深地感受到“仅仅有结合详细的应用才干使你对一个东西有较深的了解”这句话的深刻含义了。