Zookeeper
一、Zookeeper简介
1.时代背景:
互联网信息时代的当下,到处可见的WEB应用网站,庞大的用户访问量,催生着各大互联网公司不得不想出一些更好的方式来提高网站的高可用性、高并发性,而分布式系统的架构设计应运而生。下面就让我们一起了解一下分布式系统的概念以及分布式系统环境下我们面临的问题。
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。
2.Zookeeper概述
ZooKeeper 是一个开源的高可用分布式应用系统服务协调框架,是 Google 的 Chubby 一个开源的实现。旨在为分布式应用提供一致性服务。主要提供的功能有配置维护、统一命名服务、域名服务、分布式同步等。
Zookeeper的所有设计都是围绕和针对分布式系统环境的特点展开的,采用一种znode的数据结构的设计,和针对这种数据结构操作的相关原语,并采用watcher(通知)机制通过网络给分布式应用程序传递消息。总的来说,Zookeeper提供所有服务实现的核心都是基于它的数据模型(znode)、原语、watcher机制来完成的,接下来就从这三个方面我们来认识一下Zookeeper
二、Zookeeper的特性
1.Zookeeper的数据模型(znode)
在Zookeeper中,znode是一个跟Unix(linux系统是由Unix演变发展而来)文件系统路径相似的节点,可以往这个节点存储或获取数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xJxNpk9S-1602475186661)(C:\Users\T480\AppData\Roaming\Typora\typora-user-images\1578137168936.png)]
1.1引用方式
znode通过路径引用,如同Unix中的文件路径。**路径必须是绝对的,因此他们必须由斜杠字符来开头。**除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理信息,比如关键配额信息。
1.2 znode结构
ZooKeeper命名空间中的znode,兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分。每个znode主要由3部分组成:
① stat:此为状态信息, 描述该znode的版本, 权限等信息
② data:与该znode关联的数据
③ children:该znode下的子节点
ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,但常规使用中应该远小于此值。
1.3 数据访问
ZooKeeper中的每个节点存储的数据要被原子性的操作。也就是说读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作。
1.4 节点类型
ZooKeeper中的节点有两种大类型,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。
**① 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话(Session)结束,临时节点将被自动删除,当然可以也可以手动删除。虽然每个临时的znode都会绑定到一个客户端会话,但他们对所有的客户端还是可见的。**另外,ZooKeeper的临时节点不允许拥有子节点。
**② 永久节点:**该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
顺序节点:临时节点和永久节点分别还可以创建为临时顺序节点和永久顺序节点
当创建znode的时候,用户可以请求在ZooKeeper的路径结尾添加一个递增的计数。这个计数对于此节点的父节点来说是唯一的,它的格式为"%10d"(10位数字,没有数值的数位用0补充,例如"0000000001")。当计数值大于232-1时,计数器将溢出。
1.5 节点属性
属性 | 描述 |
---|---|
czxid | 节点被创建的zxid |
mzxid | 节点被修改的zxid |
ctime | 节点被创建的时间 |
mtime | 节点被修改的zxid |
version | 节点被修改的版本号 |
cversion | 节点所拥有的子节点被修改的版本号 |
aversion | 节点的ACL被修改的版本号 |
ephemeralOwner | 如果此节点为临时节点,那么他的值为这个节点拥有者的会话ID,否则值为0 |
dataLength | 节点数长度 |
numChildren | 节点用的子节点长度 |
pzxid | 最新修改的zxid(与mzxid一样?) |
2.ZooKeeper包含一个简单的原语集
Zookeeper暴露了由一小部分调用方法组成的类似文件系统的API,以便允许应用实现自己的原语。这里我们主要也是使用Zookeeper的原语集暴露出来的相关API操作,比如:
ls / : 查看所有znode
create:创建一个znode
delete:删除一个znode
名词解释:
原语一次主要在操作系统中提出的比较多,而操作系统的原语,是由若干多机器指令构成的完成某种特定功能的一段程序,具有不可分割性.即原语的执行必须是连续的,在执行过程中不允许被中断 。
3.Zookeeper的监听机制
(1) watch机制
客户端可以在节点上设置watch,我们称之为监视器。当节点状态发生改变时(znode的增、删、改)将会触发watch所对应的操作。当watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次,这样可以减少网络流量。
ZooKeeper可以为所有的读操作设置watch,这些读操作包括:exists()、getChildren()及getData()。当watch的对象状态发生改变时,将会触发此对象上watch所对应的事件。watch事件将被异步地发送给客户端,并且ZooKeeper为watch机制提供了有序的一致性保证。
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
(3) watch注册与处触发
下图 watch设置操作及相应的触发器如图下图所示:
设置watch | watch触发器 | ||||
create | delete | setData | |||
znode | child | znode | child | znode | |
exists | NodeCreated | NodeDeleted | NodeDataChanged | ||
getdata | NodeDeleted | NodeDataChanged | |||
getChildren | NodeChildrenChanged | NodeDeleted | NodeDeletedChanged |
① exists操作上的watch,在被监视的Znode创建、删除或数据更新时被触发。
② getData操作上的watch,在被监视的Znode删除或数据更新时被触发。在被创建时不能被触发,因为只有Znode一定存在,getData操作才会成功。
③ getChildren操作上的watch,在被监视的Znode的子节点创建或删除,或是这个Znode自身被删除时被触发。可以通过查看watch事件类型来区分是Znode,还是他的子节点被删除:NodeDelete表示Znode被删除,NodeDeletedChanged表示子节点被删除。
Watch由客户端所连接的ZooKeeper服务器在本地维护,因此watch可以非常容易地设置、管理和分派。当客户端连接到一个新的服务器时,任何的会话事件都将可能触发watch。另外,当从服务器断开连接的时候,watch将不会被接收。但是,当一个客户端重新建立连接的时候,任何先前注册过的watch都会被重新注册。
(4) 需要注意的几点
Zookeeper的watch实际上要处理两类事件:
① 连接状态事件(type=None, path=null)
这类事件不需要注册,也不需要我们连续触发,我们只要处理就行了。
② 节点事件
节点的建立,删除,数据的修改。它是one time trigger,我们需要不停的注册触发,还可能发生事件丢失的情况。
上面2类事件都在Watch中处理,也就是重载的process(Event event)
节点事件的触发,通过函数exists,getData或getChildren来处理这类函数,有双重作用:
① 注册触发事件
② 函数本身的功能
函数的本身的功能又可以用异步的回调函数来实现,重载processResult()过程中处理函数本身的的功能。
Zookeeper特性可以总结为:
znode节点的特性:
※ znode 可以有⼦节点⽬录,并且每个 znode 可以存储数据
※ znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是⼀个访问路径中可以存储多份数据
※ znode 可以被监控,包括这个⽬录节点中存储的数据的修改,⼦节点⽬录的变化等,⼀旦变化可以通知设置
节点的分类:
※ 持久节点(PERSISTENT)
是指在节点创建后,就⼀直存在,直到有删除操作来主动删除这个节点——不会因为创建该节点的客户端会话失
效⽽消失
※ 持久顺序节点(PERSISTENT_SEQUENTIAL)
这类节点的基本特性和上⾯的节点类型是⼀致的。额外的特性是,在ZK中,每个⽗节点会为他的第⼀级⼦节点维
护⼀份时序,会记录每个⼦节点创建的先后顺序。基于这个特性,在创建⼦节点的时候,可以设置这个属性,那
么在创建节点过程中,ZK会⾃动为给定节点名加上⼀个数字后缀,作为新的节点名。这个数字后缀的范围是整型
的最⼤值。
※ 临时节点(EPHEMERAL)
和持久节点不同的是,临时节点的⽣命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点
就会⾃动被清除掉。注意,这⾥提到的是会话失效,⽽⾮连接断开。另外,在临时节点下⾯不能创建⼦节点。
※ 临时顺序节点(EPHEMERAL_SEQUENTIAL)
节点监听机制:
※ 客户端可以监测znode节点的变化。Zonode节点的变化触发相应的事件,然后清除对该节点的监测。当监
测⼀个znode节点时候,Zookeeper会发送通知给监测节点。
※ watch机制说明:⼀个Watch事件是⼀个⼀次性的触发器,当被设置了Watch的数据发⽣了改变的时候,则
服务器将这个改变发送给设置了Watch的客户端,以便通知它们。
※ 通过这个特性可以实现的功能包括配置的集中管理,集群管理,分布式锁等等。
三、Zookeeper的安装
下载zookeeper的安装包
官网:https://zookeeper.apache.org/
安装环境要求: Zookeeper的运行需要Java环境,先安装好JDK(最新版本的zookeeper要求最低jdk1.8)
开始安装
~# tar -zxvf zookeeper-xx.tar.gz
//修改配置文件
~# vi conf/zoo_simple.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/zookeeper/zkdata
clientPort=2181
//zookeeper在启动服务时会寻找名为 zoo.cfg的配置文件,作为启动配置,下面是将编辑完毕的配置文件重新命名
~# mv conf/ zoo_simple.cfg zookeeper/conf/ zoo.cfg
//启动服务
~# ./bin/zkServer.sh start
//客户端连接
~# ./bin/zkCli.sh -server IP:port
//查看zookeeper进程
~# jps
tickTime=2000 #心跳基本时间单位,毫秒级,ZK基本上所有的时间都是这个时间的整数倍 initLimit=10 # tickTime的个数,表示在leader选举结束后,followers与leader同步需要的时间,如果followers比较多或者说leader的数据灰常多时,同步时间相应可能会增加,那么这个值也需要相应增加。当然,这个值也是follower和observer在开始同步leader的数据时的最大等待时间(setSoTimeout) syncLimit=5 # tickTime的个数,这时间容易和上面的时间混淆,它也表示follower和observer与leader交互时的最大等待时间,只不过是在与leader同步完毕之后,进入正常请求转发或ping等消息交互时的超时时间。 dataDir=/usr/zookeeper/zkdata #保存snapshot⽂件的路径 clientPort=2181 #配置ZK监听客户端连接的端口
目录结构
1. bin:放置运行脚本和工具脚本,如果是 Linux 环境还会有有 zookeeper 的运 行日志 zookeeper.out
2. conf:zookeeper 默认读取配置的目录,里面会有默认的配置文件
3. contrib:zookeeper 的拓展功能
4. dist-maven:zookeeper的 maven 打包目录
5. docs:zookeeper 相关的文档
6. lib:zookeeper 核心的 jar
7. recipes:zookeeper 分布式相关的 jar 包
8. src:zookeeper 源码
四、客户端基本指令操作
1.使用ls命令查看当前Zookeeper中所包含的内容:ls /
[zk: 192.168.246.138:2181(CONNECTED) 1] ls /
[zookeeper]
[zk: 192.168.246.138:2181(CONNECTED) 2]
2.查看节点状态
[zk: 192.168.246.138:2181(CONNECTED) 2] stat zookeeper
cZxid = 0x0
ctime = Wed Dec 31 19:00:00 EST 1969
mZxid = 0x0
mtime = Wed Dec 31 19:00:00 EST 1969
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
[zk: 192.168.246.138:2181(CONNECTED) 3]
3.节点的增删改查操作
3.1 创建节点
create /node1 持久节点
create -e /tmp-node1 临时节点
create -s /node1 持久顺序节点
create -s -e /tmp-node1 临时顺序节点
close 关闭当前会话后使用 connect重新连接,查看上次创建的临时节点已经消失了
3.2 设置节点内容&获取节点内容
set /tmp-node1 "baby00" [version]
get /tmp-node1
3.3 删除节点
delete /tmp-node1 # 如果包含子节点需要使用rmr删除
3.4 监听节点
ls /node true //监听本节点数据的修改和删除。同时监听⼦节点的添加和删除
get /path true //监听本节点数据的修改和删除。同时监听⼦节点的添加和删除
以下命令需要配合linux系统的nc命令一起使用
如: echo conf | nc 192.168.246.139 3001
ZooKeeper 四字命令 | 功能描述 |
---|---|
conf | 输出相关服务配置的详细信息。 |
cons | 列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息。 |
dump | 列出未经处理的会话和临时节点。 |
envi | 输出关于服务环境的详细信息(区别于 conf 命令)。 |
reqs | 列出未经处理的请求 |
ruok | 测试服务是否处于正确状态。如果确实如此,那么服务返回“imok ”,否则不做任何相应。 |
stat | 输出关于性能和连接的客户端的列表。 |
wchs | 列出服务器 watch 的详细信息。 |
wchc | 通过 session 列出服务器 watch 的详细信息,它的输出是一个与watch 相关的会话的列表。 |
wchp | 通过路径列出服务器 watch 的详细信息。它输出一个与 session相关的路径。 |
4.权限控制相关命令
ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限。子节点不会继承父节点的权限,比如:有的时候客户端虽然无权访问某节点,但可能可以访问它的子节点。
权限认证方案:
方案 | 描述 |
---|---|
world | 只有一个用户:anyone,代表所有人(默认) |
ip | 使用IP地址认证,通常是一个IP地址或是IP段,比如:192.168.0.1/110 |
auth | 使用已添加认证的用户认证 |
digest | 使用“用户名:密码”方式认证 |
权限:
权限 | ACL简写 | 描述 |
---|---|---|
CREATE | c | 可以创建子节点 |
DELETE | d | 可以删除子节点(仅下一级节点) |
READ | r | 可以读取节点数据及显示子节点列表 |
WRITE | w | 可以设置节点数据 |
ADMIN | a | 可以设置节点访问控制列表权限 |
权限操作命令:
命令 | 使用方式 | 描述 |
---|---|---|
getAcl | getAcl [path] | 读取ACL权限 |
setAcl | setAcl [path] [acl] | 设置ACL权限 |
addauth | addauth [scheme] [auth] | 添加认证用户 |
示例命令
1.world方案:
setAcl /node1 world:anyone:cdrwa
2.IP方案
setAcl /node2 ip:192.168.100.1:cdrwa #设置IP:192.168.100.1 拥有所有权限
3.auth方案:
addauth digest yoonper:123456 #添加认证用户
setAcl /node3 auth:yoonper:cdrwa
4.digest方案
setAcl /node4 digest:yoonper:UvJWhBril5yzpEiA2eV7bwwhfLs=:cdrwa
addauth digest yoonper:123456 #添加认证用户
echo -n : | openssl dgst -binary -sha1 | openssl base64
五、ZK的集群相关特性
1.集群的结构和集群中的角色
【待补图】
1. 集群的特点
2.1 最终一致性:client 不论连接到哪个 Server,展示给它都是同一个视图,这是 zookeeper 最重要的性能。
2.2 可靠性:具有简单、健壮、良好的性能,如果消息 m 被一台服务器接受,那么它 将被所有的服务器接受。
2.3 实时性:Zookeeper 保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper 不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用 sync()接口。
2.4 等待无关(wait-free):慢的或者失效的 client 不得干预快速的 client 的请求,使得每 个 client 都能有效的等待。
2.5 原子性:更新只能成功或者失败,没有中间状态。
2.6 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息 a 在消息 b 前发布,则在所有 Server 上消息 a 都将在消息 b 前被发布;偏序是指如果一个消息 b 在消 息 a 后被同一个发送者发布,a 必将排在 b 前面。
六、ZK集群的搭建
在 Zookeeper 集群中,每个节点需要一个唯一标识。这个唯一标识要求是自然数。且唯 一标识保存位置是:$dataDir/myid。其中 dataDir 为配置文件 zoo.cfg 中的配置参数 在 data 目录中创建文件 myid : touch myid 为应用提供唯一标识。
1、创建三个dataDir
[root@c60 zookeeper]# mkdir zkdata1 zkdata2 zkdata3
2、分别在三个dataDir⽬录下⾯myid⽂件
[root@c60 zookeeper]# touch ./zkdata1/myid
myid的内容是 服务器的唯一标识 1|2|3
3、在/conf⽬录下创建三个zk配置⽂件,分别为 zoo1.cfg,zoo2.cfg,zoo3.cfg
zoo1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/zookeeper/zkdata1
clientPort=3001
server.1=192.168.246.138:3002:3003
server.2=192.168.246.138:4002:4003
server.3=192.168.246.138:5002:5003
zoo2.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/zookeeper/zkdata2
clientPort=4001
server.1=192.168.246.138:3002:3003
server.2=192.168.246.138:4002:4003
server.3=192.168.246.138:5002:5003
zoo3.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/zookeeper/zkdata3
clientPort=5001
server.1=192.168.246.138:3002:3003
server.2=192.168.246.138:4002:4003
server.3=192.168.246.138:5002:5003
ps: server.X :x为服务器的唯⼀标识。
192.168.246.138:服务器所在的ip地址
3002:数据同步使⽤的端⼝号
3003:选举使⽤的端⼝号
4、分别启动各个zk服务器
[root@c60 zookeeper]# ./bin/zkServer.sh start /usr/zookeeper/conf/zoo1.cfg
[root@c60 zookeeper]# ./bin/zkServer.sh start /usr/zookeeper/conf/zoo2.cfg
[root@c60 zookeeper]# ./bin/zkServer.sh start /usr/zookeeper/conf/zoo3.cfg
5、查看各个zk服务器的⻆⾊信息
[root@c60 zookeeper]# ./bin/zkServer.sh status /usr/zookeeper/conf/zoo1.cfg
6、客户端连接任意zk服务器进⾏节点操作
[root@c60 zookeeper]# ./bin/zkCli.sh -server 192.168.246.138:3001
7.停⽌特定zk服务器
[root@c60 zookeeper]# ./bin/zkServer.sh stop /usr/zookeeper/conf/zoo1.cfg
七、java操作ZK
创建ZkClient客户端对象
/**
* 节点创建测试
*/
@Test
public void test0() {
/**
* param1 : zookeeper服务器地址
* param2 : 会话超时时间
* param3 : 连接超时时间
* param4 : 序列化工具对象
*/
ZkClient client = new ZkClient("192.168.246.139:2181", 2000, 5000, new SerializableSerializer());
//释放资源
client.close();
}
创建持久节点
//创建持久节点
client.createPersistent("/znode1","node_data");
//创建持久顺序节点
client.createPersistentSequential("/znode2", "node_data");
创建临时节点
//创建临时节点
client.createEphemeral("/tmp-znode1","node_data");
//创建临时顺序节点
client.createEphemeralSequential("/tmp-znode2","node_data");
给节点设置数据
client.writeData("/znode1","xiaobao");
查询节点数据
Object data = client.readData("/znode1");
创建节点时同时创建父节点
client.createPersistent("/znode3/admin1",true);// 父节点不存在会创建父节点
删除节点
//删除没有子节点的节点 返回值:是否删除成功
boolean delete = client.delete("/node1");
//递归删除节点信息 返回值:是否删除成功
boolean recursive = client.deleteRecursive("/node1");
查看节点的子节点
List<String> children = client.getChildren("/node30");
监听节点数据变化
@Test
public void test4() throws IOException {
ZkClient client = new ZkClient("192.168.246.139:2181", 2000, 5000, new SerializableSerializer());
client.subscribeDataChanges("/node30", new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("data:"+data);//新设置的数据
System.out.println("dataPath:"+dataPath);//节点路径
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println("dataPath:"+dataPath);//节点路径
}
});
System.in.read();
}
监听子节点变化
@Test
public void test1() throws IOException {
ZkClient client = new ZkClient("192.168.246.139:2181", 2000, 5000, new SerializableSerializer());
client.subscribeChildChanges("/node30", new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
//当前所有子节点
System.out.println("currentChilds:" + currentChilds);
System.out.println("parentPath:" + parentPath);
}
});
System.in.read();
}