一、Zookeeper概述
1.1 集中式和分布式
单机架构
一个系统业务量很小的时候所有的代码都放在一个项目中就好了,然后这个项目部署在一台服务器上,整个项目所有的服务都由这台服务器提供。
缺点:
- 服务性能存在瓶颈,用户增长的时候性能下降等。
- 不可伸缩性
- 代码量庞大,系统臃肿,牵一发动全身
- 单点故障问题
集群架构
单机处理到达瓶颈的时候,你就把单机复制几份,这样就构成了一个集群。
集群存在的问题:
当你的业务发展到一定程度的时候,你会发现一个问题无论怎么增加节点,貌似整个集群性能的提升效果并不明显了。这时候,你就需要使用分布式架构了。
分布式架构
分布式架构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信(远程调用)。
分布式的优势:
- 系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。
- 系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。
- 服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。
三者区别
总结:
将一套系统拆分成不同子系统部署在不同服务器上(这叫分布式),然后部署多个相同的子系统在不同的服务器上(这叫集群)。
集群:多个人在一起作同样的事 。
分布式 :多个人在一起作不同的事 。
1.2 CAP定理
分布式系统正变得越来越重要,大型网站几乎都是分布式的。分布式系统的最大难点,就是各个节点的状态如何同步。CAP 定理是这方面的基本定理,也是理解分布式系统的起点。说白了,就是要使各个模块的状态、数据共享,保持数据一致。
分布式系统的三个指标
- Consistency(一致性)
- Availability (可用性)
- Partition tolerance (分区容错性)
分区容错性
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。
结论:
分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A 无法同时做到。
一致性
写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。
接下来,用户的读操作就会得到 v1。这就叫一致性。
问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。
为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成 v1。
可用性
只要收到用户的请求,服务器就必须给出回应.
解释:
用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。
一致性和可用性的矛盾
解释:
如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性不。
一致性和可用性如何选择?
- 一致性(CP)
特别是涉及到重要的数据,就比如钱,商品数量,商品价格。
- 可用性 (AP)
网页的更新不是特别强调一致性,短时期内,一些用户拿到老版本,另一些用户拿到新版本,问题不会特别大。
1.3 什么是Zookeeper
分布式架构
多个节点协同问题:
- 每天的定时任务由谁哪个节点来执行?
- RPC调用时的服务发现?
- 如何保证并发请求的幂等
这些问题可以统一归纳为多节点协调问题,如果靠节点自身进行协调这是非常不可靠的,性能上也不可取。必须由一个独立的服务(zooKeeper)做协调工作,它必须可靠,而且保证性能。
一个应用程序,涉及多个进程协作时,`业务逻辑代码中混杂有大量复杂的进程协作逻辑。
上述多进程协作逻辑,有 2 个特点:
- 处理复杂
- 处理逻辑可重用
因此,考虑将多进程协作的共性问题拎出,作为基础设施,让 RD 更加专注业务逻辑开发,即:
Zookeeper从何而来?
ZooKeeper最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。
解决:
雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
Zookeeper介绍
ZooKeeper是一个开放源代码的分布式协调服务。ZooKeeper的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
说明:
Zookeeper顾名思义就是动物园管理员。 因为Hadoop生态各个项目都是动物得图标。 所以很符合管理员得形象。
1.3 应用场景
数据发布/订阅
数据发布/订阅的一个常见的场景是配置中心,发布者把数据发布到 ZooKeeper 的一个或一系列的节点上,供订阅者进行数据订阅,达到动态获取数据的目的。例如我们可以将A\B\C三个应用的数据库连接信息写到公共的服务中,当数据库异常时可以快速切换到其他正常的数据库。
ZooKeeper 采用的是推拉结合的方式。
- 推: 服务端会推给注册了监控节点的客户端 Wathcer 事件通知
- 拉: 客户端获得通知后,然后主动到服务端拉取最新的数据
具体流程:
- 把配置信息写到一个 Znode 上,例如
/DBConfiguration
- 客户端启动初始化阶段读取服务端节点的数据,并且注册一个数据变更的 Watcher
- 配置变更只需要对 Znode 数据进行 set 操作,数据变更的通知会发送到客户端,客户端重新获取新数据,完成配置动态修改
负载均衡
负载均衡是一种手段,用来把对某种资源的访问分摊给不同的设备,从而减轻单点的压力。
实现的思路:
- 首先建立 Servers 节点,并建立监听器监视 Servers 子节点的状态(用于在服务器增添时及时同步当前集群中服务器列表)
- 在每个服务器启动时,在 Servers 节点下建立临时子节点 Worker Server,并在对应的字节点下存入服务器的相关信息,包括服务的地址,IP,端口等等
- 可以自定义一个负载均衡算法,在每个请求过来时从 ZooKeeper 服务器中获取当前集群服务器列表,根据算法选出其中一个服务器来处理请求
命名服务
命名服务就是提供名称的服务。ZooKeeper 的命名服务有两个应用方面。
功能:
- 提供类 JNDI 功能,可以把系统中各种服务的名称、地址以及目录信息存放在 ZooKeeper,需要的时候去 ZooKeeper 中读取
- 制作分布式的序列号生成器.
分布式协调/通知
总结:
zookeeper是一个文件系统加监听机制,很牛逼就完了!
1.4 为什么要选择Zookeeper?
随着分布式架构的出现,越来越多的分布式应用会面临数据一致性问题。很遗憾的是,在解决分布式数据一致性上,除了ZooKeeper之外,目前还没有一个成熟稳定且被大规模应用的解决方案。
主要:
ZooKeeper无论从易用性还是稳定性上来说,都已经达到了一个工业级产品的标准。ZooKeeper是免费的,你无须为它支付任何费用。这点对于一个小型公司,尤其是初创团队来说,无疑是非常重要的。
广泛应用
最后,ZooKeeper已经得到了广泛的应用。诸如Hadoop、HBase、Storm、kafka等越来越多的大型分布式项目都将Zookeeper作为核心组件。
1.5 基本概念
集群角色
通常在分布式系统中,构成一个集群的每一台机器都有自己的角色,最典型的集群模式就是Master/Slave模式(主备模式)。在这种模式中,我们把能够处理所有写操作的机器称为Master机器,把所有通过异步复制方式获取最新数据,并提供读服务的机器称为Slave机器。
概念颠覆:
而在ZooKeeper中,这些概念被颠覆了。它没有沿用传统的MasterlSlave概念,而是引入了Leader、Follower和 Observer三种角色。
数据节点(znode)
在谈到分布式的时候,我们通常说的“节点”是指组成集群的每一台机器。
在ZooKeeper中节点分为两类
- 第一类同样是指构成集群的机器,我们称之为机器节点
- 第二类则是指数据模型中的数据单元,我们称之为数据节点——ZNode。
ZooKeeper将所有数据存储在内存中,数据模型是一棵树。
Watcher监听机制
Watcher(事件监听器),是ZooKeeper 中的一个很重要的特性。
注意:
ZooKeeper 允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是ZooKeeper实现分布式协调服务的重要特性。
ACL权限控制
ZooKeeper 采用ACL (Access Control Lists)策略来进行权限控制,类似于UNIX文件系统的权限控制。ZooKeeper定义了如下5种权限。
- CREATE:创建子节点的权限
- READ:获取节点数据和子节点列表的权限
- WRITE:更新节点数据的权限
- DELETE:删除子节点的权限
- ADMIN:设置节点ACL的权限
注意:
create和delete这两种权限都是针对子节点的权限控制。
二、Zookeeper部署运行
2.1 伪集群安装
伪集群模式适合在开发和测试的环境下使用。
下载Zookeeper,并上传到Linux
注意:需要配置JDK环境支持。
解压zookeeper
tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz -C /usr/local
#修改名称
mv apache-zookeeper-3.7.0-bin zookeeper
修改配置文件
进入zookeeper的安装目录的conf目录
cd conf
#修改样式文件名称
mv zoo_sample.cfg zoo.cfg
修改zoo.cfg,配置zookeeper数据存放位置和日志存放位置。
创建数据持久化目录
mkdir /usr/local/zookeeper/zkdata
mkdir /usr/local/zookeeper/zklogs
启动zookeeper服务
[root@localhost zookeeper]# ./bin/zkServer.sh start
查看Zookeeper运行状态
[root@localhost zookeeper]# ./bin/zkServer.sh status
关闭zookeeper
[root@localhost bin]# ./zkServer.sh stop
2.2集群安装
1.环境准备
注意在集群部署之前先删除zkdata和zklogs目录下的文件,这两个目录下的文件是在伪集群搭建的时候产生的。
服务器(纯净) 192.168.66.100
服务器(zk-1) 192.168.66.101
服务器(zk-2) 192.168.66.102
2.将纯净中的jdk传到zk-1和zk-2中
# 将纯净虚拟机的jdk传递到zk-1虚拟机,文件的位置和纯净的相同
scp -r jdk1.8/ 192.168.66.101:$PWD
3.分别为zk-1和zk-2两台虚拟机配置JDK环境。
#进入配置文件
vim /etc/profile
#在配置文件添加如下配置
export JAVA_HOME=/usr/local/jdk1.8
export PATH=$PATH:$JAVA_HOME/bin
#使配置文件生效
source /etc/profile
#查看jdk是否配置成功
Java -version
4.修改纯净虚拟机的zookeeper的配置文件zoo.cfg
#进入配置文件
vim zoo.cfg
#在配置文件中添加如下配置
dataDir=/usr/local/zookeeper/zkdata
dataLogDir=/usr/local/zookeeper/zklogs
clientPort=2181
server.1=192.168.66.100:2888:3888
server.2=192.168.66.101:2888:3888
server.3=192.168.66.102:2888:3888
5.将纯净中的zookeeper分发到zk-1和zk-2两台虚拟机。
scp -r zookeeper/ 192.168.66.101:$PWD
scp -r zookeeper/ 192.168.66.102:$PWD
6.为三个虚拟机中的zookeeper分别设置ID
#在纯净虚拟机中的zookeeper下的zkdata目录下使用:
echo 1 > myid
#在zk-1虚拟机中的zookeeper下的zkdata目录下使用:
echo 2 > myid
#在zk-2虚拟机中的zookeeper下的zkdata目录下使用:
echo 3 > myid
7.开启三个虚拟机的zookeeper服务
#先将三个虚拟机的防火墙关了
service firewalld stop
#开启zookeeper服务(因为没有配置环境变量,因此在开启服务的时候要在bin下开启)
[root@localhost bin]# ./zkServer.sh start
#查看服务状态
[root@localhost bin]# ./zkServer.sh status
注意:
启动后,用jps应该能看到一个进程:QuorumPeerMain。光有进程不代表zk已经正常服务,需要用命令检查状态:bin/zkServer.sh status 能看到角色模式:为leader或follower,即正常了。
2.3 服务管理
脚本 | 说明 |
---|---|
zkCleanup | 清理Zookeeper历史数据,包括事务日志文件和快照数据文件 |
zkCli | Zookeeper的简易客户端 |
zkEnv | 设置Zookeeper的环境变量 |
zkServer | Zookeeper服务器的启动、停止和重启脚本 |
配置环境变量(三台虚拟机都需要配置)
#打开配置文件
vim /etc/profile
#在配置文件中添加如下配置
export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
#配置生效
source /etc/profile
这样在任何一个文件下使用zkServer.sh start 都能启动zookeeper服务。
创建一键启动/一键停止脚本
如果集群中存在很多的zookeeper服务的话,一个个开启或者是关闭的话无疑是非常麻烦的事情,因此我们可以使用一个脚本来管理全部的服务的开启和关闭。
#在zookeeper的bin下创建可执行文件
vim zkStart-all.sh
#为文件添加权限
chmod +x zkStart-all.sh
#在配置文件添加如下配置
if [ $# -ne 1 ];then
echo "无效参数,用法为: $1 {start|stop|restart|status}"
exit
fi
#遍历所有节点
for host in 192.168.66.101 192.168.66.102 192.168.66.103
do
echo "========== $host 正在 $1 ========= "
#发送命令给目标机器
ssh $host "source /etc/profile; /usr/local/zookeeper/bin/zkServer.sh $1"
done
#启动脚本
./zkStart-all.sh start
#查看集群中各个虚拟机的状态
./zkStart-all.sh status
#关闭全部zookeeper服务
./zkStart-all.sh stop
三、Zookeeper系统模型
3.1 数据模型
在Zookeeper中,可以说 Zookeeper中的所有存储的数据是由znode组成的,节点也称为 znode,并以 key/value 形式存储数据。
树
介绍:
整体结构类似于 linux 文件系统的模式以树形结构存储。其中根路径以 / 开头。
保存数据
注意:
以 key/value 形式存储数据。key就是znode的节点路径,比如 /java , /server。value就是spring和192.168.66.100
3.2 节点类型及特性
ZooKeeper 节点是有生命周期的,这取决于节点的类型。节点类型可以分为持久节点、临时节点,以及时序节点,具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型:
持久节点
持久节点是zookeeper中最常见的一种节点类型。所谓持久节点,是指改数据节点被创建后,就会一直存在与zookeeper服务器上,直到有删除操作来主动清除这个节点。
/java spring
/zk zk-1
持久顺序节点
这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。
临时节点
- 从名称上可以看出该节点的一个最重要的特性就是临时性。
- 所谓临时性是指,如果将节点创建为临时节点,那么该节点数据不会一直存储在 ZooKeeper 服务器上。
区别:
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。
临时顺序节点
临时顺序节点的基本特性和临时节点是一致的,同样是在临时节点的基础上,添加了顺序的特性。
3.3 客户端命令行
#开启zookeeper集群
zkStart-all.sh start
#进入纯净虚拟机的zookeeper文件系统
zkCli.sh
#在根路径下创建持久节点java,节点存放的数据是spring
[zk: localhost:2181(CONNECTED) 0] create /java spring
#查看根目录下有哪些节点
[zk: localhost:2181(CONNECTED) 1] ls /
[java, zookeeper]
#获取Java节点的值
[zk: localhost:2181(CONNECTED) 2] get /java
spring
#修改Java节点的值为springmvc
[zk: localhost:2181(CONNECTED) 3] set /java springmvc
#删除Java节点
[zk: localhost:2181(CONNECTED) 5] delete /java
创建临时节点:create -e /java spring
创建持久型顺序节点:create -s /java spring
3.4 节点信息
节点的状态结构
每个节点都有属于自己的状态信息,这就很像每个人的身份信息一样。
#查看Java节点的信息
[zk: localhost:2181(CONNECTED) 8] stat /java
cZxid = 0x500000005
ctime = Wed Jun 28 01:58:53 CST 2023
mZxid = 0x500000005
mtime = Wed Jun 28 01:58:53 CST 2023
pZxid = 0x500000005
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
3.5 Watcher监听机制
ZooKeeper 提供了分布式数据的发布/订阅功能。一个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能够让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态变化时,会通知所有订阅者,使它们能够做出相应的处理。
注意:
在ZooKeeper中,引入了Watcher机制来实现这种分布式的通知功能。ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些指定事件触发了这个Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
监听节点变化
#纯净客户端监听Java节点的变化
ls -w /java
#在zk-1客户端创建/java/spring节点
[zk: localhost:2181(CONNECTED) 1] create /java/spring spirngmvc
Created /java/spring
#纯净客户端监听到了Java节点的变化
[zk: localhost:2181(CONNECTED) 11]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/java
注意:
命令如果使用 -w ,那么监听的是节点的变化,而不是值的变化。
监听节点的值的变化
#纯净客户端监听节点/java/spring的值
get -w /java/spring
#zk-1客户端修改/java/spring节点的值
[zk: localhost:2181(CONNECTED) 2] set /java/spring mybatis
#纯净客户端监听到节点值的变化
[zk: localhost:2181(CONNECTED) 2]
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/java/spring
注意:
watch监听机制只能够使用一次,如果下次想要使用,必须重新监听,就比如ls path watch命令,只能监听节点路径的改变一次,如果还想监听,那么需要再执行一次ls path watch命令。
3.6 权限控制ACL
在ZooKeeper的实际使用中,我们的做法往往是搭建一个共用的ZooKeeper集群,统一为若干个应用提供服务。在这种情况下,不同的应用之间往往是不会存在共享数据的使用场景的,因此需要解决不同应用之间的权限问题。
注意:
- ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限
- 每个znode支持设置多种权限控制方案和多个权限
- 子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点
权限模式schema
ZooKeeper内置了一些权限控制方案,可以用以下方案为每个节点设置权限:
方案 | 描述 |
---|---|
world | 只有一个用户:anyone,代表所有人(默认) |
ip | 使用IP地址认证 |
auth | 使用已添加认证的用户认证 |
digest | 使用“用户名:密码”方式认证 |
授权对象ID
授权对象ID是指,权限赋予的用户或者一个实体,例如:IP 地址或者机器。授权模式 schema 与 授权对象 ID 之间关系:
权限模式 | 授权对象 |
---|---|
IP | 通常是一个IP地址或是IP段,例如“192.168.66.101” |
Digest | 自定义,通常是“username:BASE64(SHA-1(username:password))” |
World | 只有一个ID:"anyone" |
Super | 与Digest模式一致 |
权限permission
权限 | ACL简写 | 描述 |
---|---|---|
CREATE | c | 可以创建子节点 |
DELETE | d | 可以删除子节点(仅下一级节点) |
READ | r | 可以读取节点数据及显示子节点列表 |
WRITE | w | 可以设置节点数据 |
ADMIN | a | 可以设置节点访问控制列表权限 |
权限相关命令
命令 | 使用方式 | 描述 |
---|---|---|
getAcl | getAcl | 读取ACL权限 |
setAcl | setAcl | 设置ACL权限 |
addauth | addauth | 添加认证用户 |
示例:World方案
[zk: localhost:2181(CONNECTED) 0] create /node1 1
Created /node1
[zk: localhost:2181(CONNECTED) 1] getAcl /node1
'world,'anyone #默认为world方案
: cdrwa #任何人都拥有所有权限
示例:IP方案
#创建节点
[zk: localhost:2181(CONNECTED) 0] create /node2 1
Created /node2
#设置权限(只有ip地址为192.168.66.100的客户端才能操作该节点)
[zk: localhost:2181(CONNECTED) 1] setAcl /node2 ip:192.168.66.100:cdrwa
#使用IP非 192.168.66.100 的机器
[zk: localhost:2181(CONNECTED) 0] get /node2
Authentication is not valid : /node2 #没有权限
示例:Auth方案
#创建节点
[zk: localhost:2181(CONNECTED) 0] create /node3 1
Created /node3
#添加认证用户zj,密码123456
[zk: localhost:2181(CONNECTED) 1] addauth digest zj:123456
#为创建的权限用户zj设置权限
[zk: localhost:2181(CONNECTED) 2] setAcl /node3 auth:zj:cdrwa
#获取权限
[zk: localhost:2181(CONNECTED) 3] getAcl /node3
'digest,'zj:UvJWhBril5yzpEiA2eV7bwwhfLs=
: cdrwa
示例:Digest方案
#密码加密
echo -n zj:123456 | openssl dgst -binary -sha1 | openssl base64
UvJWhBril5yzpEiA2eV7bwwhfLs=
#创建节点
[zk: localhost:2181(CONNECTED) 0] create /node4 1
Created /node4
#使用是算好的密文密码添加权限:
[zk: localhost:2181(CONNECTED) 1] setAcl /node4 digest:zj:UvJWhBril5yzpEiA2eV7bwwhfLs=:cdrwa
#获取节点数据没有权限
[zk: localhost:2181(CONNECTED) 3] get /node4
Authentication is not valid : /node4
#添加认证用户
[zk: localhost:2181(CONNECTED) 4] addauth digest zj:123456
#成功读取数据
[zk: localhost:2181(CONNECTED) 5] get /node4 1
四、原生api操作Zookeeper
利用Zookeeper官方的原生java api进行连接,然后演示一些创建、删除、修改、查询节点的操作。
1.创建maven项目,引入依赖。
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>
</dependencies>
2.创建连接和节点
注意:在使用Java连接zookeeper服务的时候先要在虚拟机开启zookeeper的服务。(zkSserver.sh start)
public class ZKMain {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//1.创建与服务端的对话
/**
* 第一个参数:zookeeper的服务地址
* 第二个参数:会话超时时间
* 第三个参数:监听机制
*/
ZooKeeper zooKeeper = new ZooKeeper("192.168.66.100:2181," +
"192.168.66.101:2181," +
"192.168.66.102:2181", 4000, null);
//2.查看连接状态
System.out.println(zooKeeper.getState());
//3.创建节点
/*
* 第一个参数:节点的名字
* 第二个参数:节点的数据
* 第三个参数:节点的权限策略
* ZooDefs.Ids.OPEN_ACL_UNSAFE:完全开放任何人都能操作
* ZooDefs.Ids.CREATOR_ALL_ACL:创建者才能操作
* ZooDefs.Ids.READ_ACL_UNSAFE:只读权限
* 第四个参数:节点类型(持久、临时、……)
* CreateMode.PERSISTENT:持久型节点
* CreateMode.EPHEMERAL:临时节点
* CreateMode.PERSISTENT_SEQUENTIAL:持久顺序节点
* CreateMode.EPHEMERAL_SEQUENTIAL:临时顺序节点
*/
zooKeeper.create("/zkTest","1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
znode 类型有四种
- PERSISTENT - 持久化目录节点,客户端与zookeeper断开连接后,该节点依旧存在
- PERSISTENT_SEQUENTIAL - 持久化,并带有序列号
- EPHEMERAL - 临时目录节点,客户端与zookeeper断开连接后,该节点被删除
- EPHEMERAL_SEQUENTIAL - 临时,并带有序列号
3.修改节点数据
//5.修改节点
/**
* 参数一:节点名称
* 参数二:修改的数据
* 参数三:版本,全部修改设为-1
*/
zooKeeper.setData("/zkTest","2".getBytes(),-1);
4.获取节点数据和节点信息
//6.获取节点数据
/**
* 参数一:节点名称
* 参数二:监听机制
* 参数三:状态信息
*/
byte[] data = zooKeeper.getData("/zkTest", null, null);
System.out.println(new String(data));
//7.获取节点的子节点信息
/**
* 参数一:节点名称
* 参数二:监听机制
*/
List<String> children = zooKeeper.getChildren("/zkTest", null);
for (String child : children) {
System.out.println(child);
}
5.监听节点
public class ZKWatcher {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//1.创建与服务端的对话
/*
* 第一个参数:zookeeper的服务地址
* 第二个参数:会话超时时间
* 第三个参数:监听机制,接口类型,使用匿名内部类实现该接口
*/
ZooKeeper zooKeeper = new ZooKeeper("192.168.66.100:2181," +
"192.168.66.101:2181," +
"192.168.66.102:2181", 4000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("正在监听……");
}
});
//2.注册监听机制监听节点
zooKeeper.getChildren("/zkTest", new Watcher() {
/*对 /zkTest 的子节点进行操作的时候会回调该监听方法*/
@Override
public void process(WatchedEvent watchedEvent) {
//查看监听路径
System.out.println("监听路径:"+watchedEvent.getPath());
//查看监听的事件
System.out.println("监听的事件:"+watchedEvent.getType());
}
});
//线程休眠,保持持续监听
Thread.sleep(Long.MAX_VALUE);
}
}
删除 /zkTest 的子节点 /zkTest1 节点的数据时触发监听:
6.监听节点数据
package com.zj;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
public class ZKWatcher {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//1.创建与服务端的对话
/*
* 第一个参数:zookeeper的服务地址
* 第二个参数:会话超时时间
* 第三个参数:监听机制,接口类型,使用匿名内部类实现该接口
*/
ZooKeeper zooKeeper = new ZooKeeper("192.168.66.100:2181," +
"192.168.66.101:2181," +
"192.168.66.102:2181", 4000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("正在监听……");
}
});
//3.监听节点数据
zooKeeper.getData("/zkTest", new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
//查看监听路径
System.out.println("监听路径:"+watchedEvent.getPath());
//查看监听的事件
System.out.println("监听的事件:"+watchedEvent.getType());
}
},null);
//线程休眠,保持持续监听
Thread.sleep(Long.MAX_VALUE);
}
}
修改 /zkTest 节点的数据:
注意:
通过zooKeeper.getchildren("/",new watch()){}来注册监听,监听的是整个根节点,但是这个监听只能监听一次。线程休眠是为了让监听等待事件发生,不然会随着程序直接运行完。
五、zkClient操作Zookeeper
使用zookeeper遇到问题:
- 重复注册watcher
- session失效重连
- 异常处理(删除节点不能有子节点,新增节点必须有父节点等)
zkclient是Github上一个开源的Zookeeper客户端,在Zookeeper原生 API接口之上进行了包装,是一个更加易用的Zookeeper客户端。同时Zkclient在内部实现了诸如Session超时重连,Watcher反复注册等功能,从而提高开发效率。
添加依赖
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
创建会话
//1.创建会话
ZkClient zkClient = new ZkClient("192.168.66.100:2181,192.168.66.101:2181,192.168.66.102:2181");
创建节点
//3.创建节点
zkClient.create("/zkTest/zkTest3","3", CreateMode.PERSISTENT);
获取/zkTest子节点
//2.获取/zkTest子节点
List<String> children = zkClient.getChildren("/zkTest");
children.forEach(System.out::println);
修改节点数据
zkClient.writeData("/zkTest/zkTest3","4");
获取节点数据
//5.获取节点数据
String data = zkClient.readData("/zkTest/zkTest3");
删除节点
//6.删除节点
zkClient.delete("/zkTest/zkTest3");
注册节点监听事件
//7.注册节点监听事件
zkClient.subscribeChildChanges("/zkTest",new IZkChildListener(){
@Override
public void handleChildChange(String s, List<String> list) throws Exception {
System.out.println("节点改变了");
list.forEach(System.out::println);
}
});
注册数据监听事件
//8.注册数据监听事件
zkClient.subscribeDataChanges("/zkTest", new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
System.out.println("数据改变了");
}
@Override
public void handleDataDeleted(String s) throws Exception {
System.out.println("数据删除了");
}
});
六、ApacheCurator操作Zookeeper
Curator是 Netflix公司开源的一套ZooKeeper客户端框架。和ZkClient一样,Curator解决了很多ZooKeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和 NodeExistsException异常等,目前已经成为了Apache的顶级项目,是全世界范围内使用最广泛的ZooKeeper客户端之一。
Curator包
- curator-framework:对zookeeper的底层api的一些封装。
- curator-client:提供一些客户端的操作,例如重试策略等。
- curator-recipes:封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等。
添加Maven依赖
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
创建会话
//1.创建会话
String connStr = "192.168.66.100:2181,192.168.66.101:2181,192.168.66.102:2181";
CuratorFramework cur= CuratorFrameworkFactory.builder()
.connectString(connStr)
.connectionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(1000,3)) //断开重试机制
.build();
//2.连接
cur.start();连接
创建节点
//3.创建节点
cur.create().withMode(CreateMode.PERSISTENT).forPath("/zkTest/zkTest4","4".getBytes());
获取数据
//4.获取数据
byte[] bytes = cur.getData().forPath("/zkTest/zkTest4");
System.out.println(new String(bytes));
删除一个节点
//5.删除节点
cur.delete().forPath("/zkTest/zkTest4");
注意:
此方法只能删除叶子节点,否则会抛出异常。
删除一个节点,并且递归删除其所有的子节点
cur.delete().deletingChildrenIfNeeded().forPath("/zkTest");
更新一个节点的数据内容
client.setData().forPath("path","data".getBytes());
监听节点
//8.监听机制
NodeCache nodeCache = new NodeCache(cur, "/zkTest/zkTest3");
nodeCache.getListenable().addListener(()-> System.out.println("被修改"));
nodeCache.start();
七、Zookeeper高级
7.1 四字命令
之前使用stat命令来验证ZooKeeper服务器是否启动成功,这里的stat命令就是ZooKeeper 中最为典型的命令之一。ZooKeeper中有很多类似的命令,它们的长度通常都是4个英文字母,因此我们称之为“四字命令”。
添加配置(前提)
vim zoo.cfg
#开启四字命令
4lw.commands.whitelist=*
#添加nc命令
yum install nc -y
conf
输出Zookeeper相关服务的详细配置信息,如客户端端口,数据存储路径、最大连接数、日志路径、数据同步端口、主节点推举端口、session超时时间等等。
echo conf| nc localhost 2181
注意:
注意,conf命令输出的配置信息仅仅是输出一些最基本的配置参数。另外,conf命令会根据当前的运行模式来决定输出的信息。如果是单机模式(standalone), 就不会输出诸如initLimit.syncLimit、electionAlg 和electionPort等集群相关的配置信息。
cons
cons 命令用于输出当前这台服务器上所有客户端连接的详细信息,包括每个客户端的客户端IP、会话ID和最后一次与服务器交互的操作类型等。
echo cons | nc localhost 2181
ruok
ruok命令用于输出当前ZooKeeper服务器是否正在运行。该命令的名字非常有趣,其谐音正好是“Are you ok”。执行该命令后,如果当前ZooKeeper服务器正在运行,那么返回“imok”, 否则没有任何响应输出。
echo ruok | nc localhost 2181
stat
stat命令用于获取ZooKeeper服务器的运行时状态信息,包括基本的ZooKeeper版本、打包信息、运行时角色、集群数据节点个数等信息,另外还会将当前服务器的客户端连接信息打印出来。
echo stat | nc localhost 2181
注意:
除了一些基本的状态信息外,stat命令还会输出一些服务器的统计信息,包括延迟情况、收到请求数和返回的响应数等。注意,所有这些统计数据都可以通过srst命令进行重置。
mntr
列出集群的关键性能数据,包括zk的版本、最大/平均/最小延迟数、数据包接收/发送量、连接数、zk角色(Leader/Follower)、node数量、watch数量、临时节点数。
echo mntr | nc localhost 2181
7.2 选举机制
核心选举原则
- Zookeeper集群中只有超过半数以上的服务器启动,集群才能正常工作;
- 在集群正常工作之前,myid小的服务器给myid大的服务器投票,直到集群正常工作,选出Leader;
- 半数机制;
选举机制流程
- 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking(选举状态)。
- 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
- 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。
- 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟。
- 服务器5启动,后面的逻辑同服务器4成为小弟。
选择机制中的概念
Serverid:服务器ID
比如有三台服务器,编号分别是1,2,3。
编号越大在选择算法中的权重越大。
Zxid:数据ID
服务器中存放的最大数据ID.
值越大说明数据越新,在选举算法中数据越新权重越大。
Epoch:逻辑时钟
或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。