ZooKeeper学习1

ZooKeeper学习1

工作机制

在这里插入图片描述
在这里插入图片描述

数据结构

在这里插入图片描述

使用场景

统一配置管理
在这里插入图片描述
统一集群管理
在这里插入图片描述
软负载均衡
在这里插入图片描述
服务器动态上下线
在这里插入图片描述
统一命名服务
在这里插入图片描述

本地安装ZooKeeper

安装在Linux虚拟机上,系统我用Cent OS 7版本。要先安装Java,再安装ZooKeeper。我虚拟机里之前已经装了Java,这里就直接开始安装ZooKeeper,ZooKeeper的版本是3.5.7。
1.去官网下载apache-zookeeper-3.5.7-bin.tar.gz,主要是名字里有bin的。
2.在虚拟机里,在/opt文件夹下创建software文件夹和module文件夹
3.把apache-zookeeper-3.5.7-bin.tar.gz放到这个/opt/software目录下,再解压缩

tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/

这里指定了解压到/opt/module/这个文件夹下,到/opt/module/打开apache-zookeeper-3.5.7-bin文件夹,

[root@vmcent apache-zookeeper-3.5.7-bin]# ll
总用量 32
drwxr-xr-x. 2  502 games   232 2月  10 2020 bin
drwxr-xr-x. 2  502 games    77 2月   7 2020 conf
drwxr-xr-x. 5  502 games  4096 2月  10 2020 docs
drwxr-xr-x. 2 root root   4096 9月   1 11:46 lib
-rw-r--r--. 1  502 games 11358 9月  13 2018 LICENSE.txt
-rw-r--r--. 1  502 games   432 2月  10 2020 NOTICE.txt
-rw-r--r--. 1  502 games  1560 2月   7 2020 README.md
-rw-r--r--. 1  502 games  1347 2月   7 2020 README_packaging.txt

在这里创建一个文件夹zkData

mkdir ./zkData
[root@vmcent apache-zookeeper-3.5.7-bin]# cd ./zkData
[root@vmcent zkData]# pwd
/opt/module/apache-zookeeper-3.5.7-bin/zkData

回到apache-zookeeper-3.5.7-bin,打开这里面的conf文件夹,把zoo_sample.cfg文件名改为zoo.cfg

mv zoo_sample.cfg zoo.cfg

打开这个zoo.cfg

vim zoo.cfg

在这里插入图片描述
把这里的dataDir的值修改为/opt/module/apache-zookeeper-3.5.7-bin/zkData,保存退出。
进入/apache-zookeeper-3.5.7-bin/bin文件夹里
在这里插入图片描述
应该先启动Server再启动client。
首先启动Server

./zkServer.sh start

再启动client

./zkCli.sh

启动后简单试一下命令

[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

退出客户端命令

quit

查看ZooKeeper状态./zkServer.sh status

[root@vmcent bin]# ./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone

standalone表示独立的。
停止Zookeeper服务./zkServer.sh stop

[root@vmcent bin]# ./zkServer.sh stop
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED

配置文件zoo.cfg中参数含义

在这里插入图片描述
1.tickTime = 2000:通信心跳时间,Zookeeper服务器与客户端心跳时间,单位毫秒
在这里插入图片描述
2.initLimit = 10:LF初始通信时限
在这里插入图片描述
Leader和Follower初始连接时能容忍的最多心跳数(tickTime的数量)
3.syncLimit = 5:LF同步通信时限
在这里插入图片描述
Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
4.dataDir:保存Zookeeper中的数据
注意:默认的tmp目录,容易被Linux系统定期删除,所以一般不用默认的tmp目录。
5.clientPort = 2181:客户端连接端口,通常不做修改。

集群方式安装

为了模拟集群方式安装,开启多台虚拟机来模拟集群的方式。这里我就用3台虚拟机来模拟。
在这里插入图片描述
3台虚拟机都要有安装JDK和ZooKeeper,因为这里是虚拟机嘛,其余2台都是从第一台克隆出来的,这样就不用再安装了,为了好区分可以修改一下hostname

vim /etc/hostname

在这里插入图片描述
这里2号机就叫vmCent2,1号机就叫vmCent1,以此类推,保存,重启就生效了。
1.在/opt/module/apache-zookeeper-3.5.7-bin/zkData里面,创建一个叫myid的文件(必须是这个名字),3台机都要创建这个这个文件。

[root@vmCent1 zkData]# pwd
/opt/module/apache-zookeeper-3.5.7-bin/zkData
[root@vmCent1 zkData]# vim myid

myid文件的内容就是一个唯一标识,这里我1号机内容就写1,2号机内容就写2,以此类推。
在这里插入图片描述
查看3台机器的ip地址

ifconfig

1号机192.168.88.129
2号机192.168.88.130
3号机192.168.88.131

2.打开zoo.cfg修改配置

[root@vmCent1 conf]# pwd
/opt/module/apache-zookeeper-3.5.7-bin/conf
[root@vmCent1 conf]# vim zoo.cfg

在zoo.cfg里添加以下内容,3台机都要这样做

##########cluster#############
server.1=192.168.88.129:2888:3888
server.2=192.168.88.130:2888:3888
server.3=192.168.88.131:2888:3888


server.A=B:C:D 配置参数含义
A是一个数字,表示这个是第几号服务器;
集群模式下配置一个文件 myid 这个文件在 dataDir目录下,这个文件里面有一个数据就是 A的值, Zookeeper启动时读取此文件,拿到里面的数据与 zoo.cfg里面 的配置信息比较从而判断到底是哪个 server。
B是这个服务器的地址;
C是这个服务器 Follower与集群中的 Leader服务器交换信息的端口;
D是 万一集群中的 Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
3.启动
1号机启动ZooKeeper

[root@vmCent1 bin]# pwd
/opt/module/apache-zookeeper-3.5.7-bin/bin
[root@vmCent1 bin]# ./zkServer.sh start

启动后./zkServer.sh status查看状态

[root@vmCent1 bin]# ./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Error contacting service. It is probably not running.

出现了Error,上面提到ZooKeeper集群要有半数以上的机器才能正常运行,现在总共3台只启动了1台不够半数,当然不能正常运行。
现在在第2台机器上启动ZooKeeper,发现还是出现这个Error,按道理应该是正常运行才对。先停掉已经启动的ZooKeeper。
在这里插入图片描述
这里涉及的端口有2181,2888,3888,可能是没开放端口导致有问题的,那我们就把他们3个端口都打开试试。
开放2181端口命令,其他2个端口把2181替换执行就可以了

firewall-cmd --permanent --add-port=2181/tcp

开放完要重启防火墙才能生效

 firewall-cmd --reload

可以查询指定端口是否开放

firewall-cmd --query-port=2181/tcp

开放了就会显示success
现在我们再启动1号机的ZooKeeper,再启动2号机的ZooKeeper。
1号机

[root@vmCent1 bin]# ./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

1号机可以了,变成follower
2号机

[root@vmCent2 bin]# ./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

2号机也可以了,变成leader。
未正常启动,在启动时的目录下会有zookeeper.out文件,查看报错信息:cat zookeeper.out
3号机启动zookeeper

[root@vmCent3 bin]# ./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

3号机变成follower
./zkServer.sh status命令查看状态执行了几次都是有Error,等一下再执行发现又可以了,可能有点延迟?

第一次启动的选举机制

在这里插入图片描述
假设现在ZooKeeper集群里面有5台服务器,这5台服务器都没有启动。
1.启动服务器1,发起一次选举。服务器1投自己1票,不够半数以上(5的一半2.5,超过2.5也就是最少要3票),选举无法完成,服务1状态为LOOKING
2.启动服务器2,发起一次选举。服务器1和服务器2分别投自己1票,并且告诉对方自己现在有多少票。此时服务器1发现服务器2的myid(服务器2的myid=2)比自己(服务器1)目前投的人(也是服务器1自己,因为他把票给了自己)的myid大,服务器1就更改投票,把自己的一票投给服务器2。此时服务器1有0票,服务器2有2票,也是不够半数以上,选举依然无法完成,服务器1和服务器2的状态都为LOOKING
3.服务器3启动,发起一次选举,也是按照上面第2项的流程走一遍,所以投票结果:服务器1有0票,服务器2有0票,服务器3有3票,此时服务器3 的票数已经超过一半,服务器3就成为Leader。服务器1和服务器2的状态都变为FOLLOWING,服务器3的状态变为LEADING
4.服务器4启动,发起一次选举,此时服务器1,2,3已经不是LOOKING状态,不会更改投票信息 。投票结果:服务器3有3票,服务器4有1票,此时服务器4少数服从多数,改变投票信息,把自己的1票投给服务器3并更改自己的状态为FOLLOWING
5.服务器5启动,与第4项的流程一样,少数服从多数。
几个重要信息:

  • SID:服务器 ID 。 用来唯一标识一台ZooKeeper 集群中的机器,每台机器不能重复, 和 myid 一致 。
  • ZXID:事务 ID 。 ZXID 是一个事务 ID ,用来标识一次服务器状态的变更。 在某一时刻,集群中的每台机器的 ZXID 值不一定完全一致,这和 ZooKeeper 服务器对于客户端“更新请求”的处理逻辑有关。
  • Epoch:每个 Leader 任期的代号 。没有Leader 时同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加。
    可以理解为当前集群所处的年代或者周期,每个leader就像皇帝,都有自己的年号,所以每次改朝换代,leader变更之后,都会在前一个年代的基础上加1。这样就算旧的leader崩溃恢复之后,也没有人听他的了,因为follower只听从当前年代的leader的命令。

非第一次启动的选举机制

在这里插入图片描述

1.当ZooKeeper集群中的1台服务器出现下列情况之一时,就会开始Leader选举:

  • 服务器初始化启动。(就是上一小节)
  • 服务器运行期间无法和Leader保持连接。(比如一台follower服务器挂了)
    假设服务器5有故障了连不上Leader,但是服务器5并不会认为自己故障了,而是认为其他的服务器故障了(有点搞笑),这时服务器5就想要举行一次选举选出1个老大(Leader)

2.当1台机器进入Leader选举流程时,当前集群也可能会处于以下两种状态:

  • 集群中已经存在1个Leader。
    继续用上面服务器5的例子,服务器5想举行一次选举,它就会尝试去连接其他服务器,包括Leader服务器,然后就会被告知当前服务器的 Leader 信息(哥们,Leader还在呢),对于该机器来说,仅仅需要和 Leader 机器建立连接,并进行状态同步即可。
  • 集群中的Leader服务器确实挂了。
    假设ZooKeeper 由 5 台服务器组成,
    SID 分别为 1 、 2 、 3 、 4 、 5
    ZXID 分别为 8 、 8 、 8 、 7 、 7
    ZXID解释:
    并且此时 SID 为 3 的服务器是 Leader 。某一时刻,3 和 5 服务器出现故障,因此开始进行 Leader选举。
SIDEpochZXIDSID
1181
2182
4174

选举
Leader 规则: ①EPOCH 大的直接胜出 ②EPOCH 相同,事务 id 大的胜出 ③事务id 相同,服务器 id 大的胜出

集群启动停止脚本

之前每次启动、查看状态、停止ZooKeeper时,都要在每台机器上输入命令,如果有100台那就很浪费时间了,这里写一个脚本一次性操作集群里的全部机器。
安装一个软件sshpass

yum install sshpass

使用这个软件只是方便学习, 正式生产环境还是用SSH 无密码身份验证吧。
参考:https://linux.cn/article-8086-1.html

我在/root目录下创建了一个bin文件夹,在这个bin文件夹里创建zk.sh脚本

vim zk.sh

把下面的代码复制进去

#!/bin/bash

case $1 in
start){
        for i in 192.168.88.129 192.168.88.130 192.168.88.131
        do
                echo -------------- zookeeper $i 启动 ---------------
                sshpass -p 1 ssh -o StrictHostKeyChecking=no $i /opt/module/apache-zookeeper-3.5.7-bin/bin/zkServer.sh start
        done
}
;;
stop){
        for i in 192.168.88.129 192.168.88.130 192.168.88.131
        do
                echo -------------- zookeeper $i 停止 ---------------
                sshpass -p 1 ssh -o StrictHostKeyChecking=no $i /opt/module/apache-zookeeper-3.5.7-bin/bin/zkServer.sh stop
        done
}
;;
status){
        for i in 192.168.88.129 192.168.88.130 192.168.88.131
        do
                echo -------------- zookeeper $i 状态 ---------------
                sshpass -p 1 ssh -o StrictHostKeyChecking=no $i /opt/module/apache-zookeeper-3.5.7-bin/bin/zkServer.sh status
        done
}
;;
esac

for i in 192.168.88.129 192.168.88.130 192.168.88.131这里我写的是这3台服务器的ip地址,sshpass -p 1,这个1是因为我这3台机器的登录密码都是1,所以我就填1了,这些要根据实际情况填。
保存退出,然后修改这个文件的权限

chmod 777 zk.sh

执行这个脚本,启动ZooKeeper

zk.sh start
[root@vmCent1 bin]# zk.sh start
-------------- zookeeper 192.168.88.129 启动 ---------------
Warning: Permanently added '192.168.88.129' (ECDSA) to the list of known hosts.
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
-------------- zookeeper 192.168.88.130 启动 ---------------
Warning: Permanently added '192.168.88.130' (ECDSA) to the list of known hosts.
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
-------------- zookeeper 192.168.88.131 启动 ---------------
Warning: Permanently added '192.168.88.131' (ECDSA) to the list of known hosts.
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

可以看到3台机器都启动了,查看状态

zk.sh status
[root@vmCent1 bin]# zk.sh status
-------------- zookeeper 192.168.88.129 状态 ---------------
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower
-------------- zookeeper 192.168.88.130 状态 ---------------
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader
-------------- zookeeper 192.168.88.131 状态 ---------------
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

可以看到服务器1,2都是follower,服务器3是leader,我直接在服务器2的终端里查看状态也确实是leader

[root@vmCent2 bin]# ./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

停止ZooKeeper集群

zk.sh stop
[root@vmCent1 bin]# zk.sh stop
-------------- zookeeper 192.168.88.129 停止 ---------------
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
-------------- zookeeper 192.168.88.130 停止 ---------------
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
-------------- zookeeper 192.168.88.131 停止 ---------------
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/module/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED

也是可以的。

客户端命令行操作

先启动集群,再启动客户端

./zkCli.sh -server ip地址:2181

如果你想连集群中的服务器2,就把服务器2的ip地址写到上面ip地址的位置,2181是默认端口号。

客户端命令行语法

基本语法描述
help显示所有操作命令
ls path使用 ls 命令查看当前 znode 的子节点,-w 监听子节点变化,-s 附加次级信息
create普通创建,-s 含有序列,-e 临时(重启或者超时消失)
get path获得节点的值【可监听】,-w 监听节点内容变化,-s 附加次级信息
set设置节点的值
stat查看节点状态
delete删除节点
deleteall递归的删除节点
查看当前znode中所包含的内容
ls /

查看当前节点详细数据

ls s /
[zookeeper]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

czxid:创建节点的事务 zxid
每次修改ZooKeeper状态都会 产生一个 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 子节点数量

节点类型

在这里插入图片描述
演示

客户端连接集群中ip地址为192.168.88.129的服务器

 ./zkCli.sh -server 192.168.88.129:2181

连接后光标左边就变为这样:

[zk: 192.168.88.129:2181(CONNECTED) 0] 

查看节点:

[zk: 192.168.88.129:2181(CONNECTED) 0] ls /
[zookeeper]

create命令创建1个(永久+不带序号)的节点

[zk: 192.168.88.129:2181(CONNECTED) 1] create /school "schoolOne"

这里我创建了1个school节点,给它赋值为"schoolOne"
再次查看节点

[zk: 192.168.88.129:2181(CONNECTED) 2] ls /
[school, zookeeper]

可以看到多了school节点。
在school节点下再创一个gradeOne节点并查看

[zk: 192.168.88.129:2181(CONNECTED) 3] create /school/gradeOne "NumberOne"
Created /school/gradeOne
[zk: 192.168.88.129:2181(CONNECTED) 4] ls /school
[gradeOne]

get -s命令获取节点的值和相关信息

[zk: 192.168.88.129:2181(CONNECTED) 5] get -s /school
schoolOne
cZxid = 0x800000002
ctime = Fri Sep 03 17:33:00 CST 2021
mZxid = 0x800000002
mtime = Fri Sep 03 17:33:00 CST 2021
pZxid = 0x800000003
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 1

create -s创建1个(永久+带序号)的节点

[zk: 192.168.88.129:2181(CONNECTED) 6] create -s /school/gradeOne/tom "myNameTom"
Created /school/gradeOne/tom0000000000

可以看到tom节点自动加了0000000000序号。
如果我再创建1个tom节点:

[zk: 192.168.88.129:2181(CONNECTED) 7] create -s /school/gradeOne/tom "myNameTom"
Created /school/gradeOne/tom0000000001

可以看到tom后面的序号自动加1,
查看gradeOne节点

[zk: 192.168.88.129:2181(CONNECTED) 8] ls /school/gradeOne
[tom0000000000, tom0000000001]

也就是说带序号的节点可以重复创建,他会自动增加序号。
如果我们再创建gradeOne节点:

[zk: 192.168.88.129:2181(CONNECTED) 9] create /school/gradeOne
Node already exists: /school/gradeOne

提示节点已存在,因为当时创建gradeOne节点是不带序号的,所以不能重复创建。
带序号的节点可以重复的创建,多个带序号的节点之间使用序号来区分,不带序号的节点不能重复创建,只能创建一次
quit命令退出客户端,再重新开启客户端看看之前创建的节点还在不在:

[zk: 192.168.88.129:2181(CONNECTED) 0] ls /
[school, zookeeper]
[zk: 192.168.88.129:2181(CONNECTED) 1] ls /school
[gradeOne]
[zk: 192.168.88.129:2181(CONNECTED) 2] ls /gradeOne
Node does not exist: /gradeOne
[zk: 192.168.88.129:2181(CONNECTED) 3] ls /school/gradeOne
[tom0000000000, tom0000000001]

依然存在。
create -e创建(临时+不带序号)的节点

[zk: 192.168.88.129:2181(CONNECTED) 6] create -e  /school/gradeTwo "NumberTwo"
Created /school/gradeTwo

quit命令退出客户端,再重新开启客户端看看这个临时节点还在不在:

[zk: 192.168.88.129:2181(CONNECTED) 0] ls /school
[gradeOne]

可以看到已经不在了。
set命令修改节点的值。先看看school的值:

[zk: 192.168.88.129:2181(CONNECTED) 1] get -s /school
schoolOne
cZxid = 0x800000002
ctime = Fri Sep 03 17:33:00 CST 2021
mZxid = 0x800000002
mtime = Fri Sep 03 17:33:00 CST 2021
pZxid = 0x80000000c
cversion = 5
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 1

修改school节点的值并查看:

[zk: 192.168.88.129:2181(CONNECTED) 2] set /school "schoolTwo"
[zk: 192.168.88.129:2181(CONNECTED) 3] get -s /school
schoolTwo
cZxid = 0x800000002
ctime = Fri Sep 03 17:33:00 CST 2021
mZxid = 0x80000000e
mtime = Fri Sep 03 18:02:34 CST 2021
pZxid = 0x80000000c
cversion = 5
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 1

修改成功。

监听器

客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目录节点增加删除)时,ZooKeeper 会通知客户端。监听机制保证ZooKeeper 保存的任何的数据的任何改变都能快速的响应到监听了该节点的应用程序。
在这里插入图片描述
在服务器1,2都开启客户端,这里就不指定server了

./zkCli.sh

监听节点的值有没有变化

[zk: localhost:2181(CONNECTED) 1] get -s /school
schoolTwo
cZxid = 0x800000002
ctime = Fri Sep 03 17:33:00 CST 2021
mZxid = 0x80000000e
mtime = Fri Sep 03 18:02:34 CST 2021
pZxid = 0x80000000c
cversion = 5
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 1

现在/school节点的值为schoolTwo,现在设置在服务器1的客户端那里监听/school节点的值

 get -w /school

在服务器2的客户端那里修改/school的值为SchoolThree,修改后在服务器1的客户端就会显示:

[zk: localhost:2181(CONNECTED) 3] 
WATCHER::

WatchedEvent state:SyncConnected type:NodeDataChanged path:/school

监听到节点数据发生变化。但是在服务器2的客户端那里再次修改/school的值,这时服务器1的客户端是不会监听到数据变化的,他只会监听1次而已。

监听节点的子节点有没有变化

现在我们在school节点下只有1个gradeOne子节点:

[zk: localhost:2181(CONNECTED) 5] ls /school
[gradeOne]

服务器1的客户端监听节点的子节点有没有变化:

ls -w /school

在服务器2的客户端里,在school节点下创建一个gradeTwo子节点:

create /school/gradeTwo

回到服务器1的客户端那边就会监听到:

[zk: localhost:2181(CONNECTED) 7] 
WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/school

这种监听和监听数据变化一样,注册监听1次,就生效1次,后续在监听节点下再创建子节点就不会监听到了。

删除节点

delete /school/gradeOne

如果要删除的节点下还有子节点,是不能删除这个节点的,会提示:

[zk: localhost:2181(CONNECTED) 7] delete /school/gradeOne
Node not empty: /school/gradeOne

如果要删除的节点下还有子节点,要用deleteall

deleteall /school/gradeOne

查看节点状态

stat /school

跟这个命令相似但是不显示节点的数据:

get -s /school

客户端API操作

客户端API-创建节点

首先创建maven项目,导入依赖

	<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.7</version>
        </dependency>
    </dependencies>

resources文件夹下创建log4j.properties用于打印日志设置

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

创建个测试类

package com.rgb3;

import org.apache.zookeeper.*;
import org.junit.Before;
import org.junit.Test;

/**
 * @author tony
 * @date 2021/9/3 21:18
 */
public class ZkClient {
    /*每个地址的左右不能有空格
    *这3个ip地址和端口要根据自己实际情况填,这里我就把3台虚拟机的ip填上
    */
    private String connectString = "192.168.88.129:2181,192.168.88.130:2181,192.168.88.131:2181";
    private int sessionTimeOut = 60000;	//电脑慢的可以试试值大一点
    private ZooKeeper zkClient;

   	@Before
    public void init() throws Exception{
        zkClient = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }

    @Test
    public void create() throws Exception{
		//这里创建1个/company节点,如果集群里已经有这个节点测试会失败的
        String s = zkClient.create("/company", "idea".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }
}

ZooKeeper.create()参数:
1.节点路径
2.节点数据
3.节点权限
4.节点类型(永久/临时,带序号/不带序号)

客户端API-监听节点变化

public class ZkClient {
    //每个地址的左右不能有空格
    private String connectString = "192.168.88.129:2181,192.168.88.130:2181,192.168.88.131:2181";
    private int sessionTimeOut = 60000;
    private ZooKeeper zkClient;

    
    @Before
    public void init() throws Exception{
        zkClient = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
                System.out.println("-----------------process-start-----------------------");
                List<String> children = null;
                try {
                    children = zkClient.getChildren("/", true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                for (String child : children) {
                    System.out.println("监听到的子节点:"+child);
                }
                System.out.println("-----------------process-end-----------------------");
            }
        });
    }
    
    @Test
    public void get() throws Exception{
        System.out.println("-----------------get-start-----------------------");
        List<String> children = zkClient.getChildren("/", true);


        for (String child : children){
            System.out.println("获取到的子节点:"+child);
        }
        System.out.println("-----------------get-end-----------------------");

        //为了程序结束后不关闭,设置线程睡眠
        Thread.sleep(Long.MAX_VALUE);
    }
}

getChildren()参数:
1.获取哪个路径的子节点
2.true就表示用上面init()方法里的Watcher监听,也可以直接在这里new Watcher也可以。
get()方法里Thread.sleep(Long.MAX_VALUE);让主线程睡眠了,但注册线程和监听线程并没有睡。
在get()方法里获取/节点的子节点并注册监听/节点,然后就打印出获取到的子节点名字出来,当/节点增加/减少了子节点时,就会被监听到进入到Watcher里的process()方法,根据上面的小节知道,注册监听1次就生效一次,当/节点增加/减少了子节点时,就监听到1次,后面/节点增加/减少了子节点就不再监听了,所以为了能无限次监听到/节点的子节点有没有变化,就在process()方法里再次获取/节点的子节点并监听:

children = zkClient.getChildren("/", true);

运行后控制台打印:

-----------------get-start-----------------------
2021-09-04 15:10:44,616 INFO [org.apache.zookeeper.ClientCnxn] - Opening socket connection to server 192.168.88.130/192.168.88.130:2181. Will not attempt to authenticate using SASL (unknown error)
2021-09-04 15:10:44,620 INFO [org.apache.zookeeper.ClientCnxn] - Socket connection established, initiating session, client: /192.168.88.1:3879, server: 192.168.88.130/192.168.88.130:2181
2021-09-04 15:10:44,628 INFO [org.apache.zookeeper.ClientCnxn] - Session establishment complete on server 192.168.88.130/192.168.88.130:2181, sessionid = 0x20000ba53510002, negotiated timeout = 40000
-----------------process-start-----------------------
获取到的子节点:market
获取到的子节点:shop
获取到的子节点:zookeeper
获取到的子节点:town
获取到的子节点:school
获取到的子节点:company
获取到的子节点:hospital
-----------------get-end-----------------------
监听到的子节点:market
监听到的子节点:shop
监听到的子节点:zookeeper
监听到的子节点:town
监听到的子节点:school
监听到的子节点:company
监听到的子节点:hospital
-----------------process-end-----------------------

可以看到运行后,监听器和get()方法都有打印内容出来,说明客户端与服务端连接时也会触发一次监听。
在虚拟机那边开一个客户端,在/节点下创建一个drink节点:

create /drink

在idea控制台这边能看到打印:

-----------------process-start-----------------------
监听到的子节点:market
监听到的子节点:shop
监听到的子节点:zookeeper
监听到的子节点:town
监听到的子节点:school
监听到的子节点:company
监听到的子节点:hospital
监听到的子节点:drink
-----------------process-end-----------------------

然后在客户端那里删除1个hospital节点:

delete /hospital

在idea控制台这边能看到打印:

-----------------process-start-----------------------
监听到的子节点:market
监听到的子节点:shop
监听到的子节点:zookeeper
监听到的子节点:town
监听到的子节点:school
监听到的子节点:company
监听到的子节点:drink
-----------------process-end-----------------------

客户端API-节点是否存在

	@Before
    public void init() throws Exception{
        zkClient = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
            }
        });
    }
    @Test
    public void exist() throws Exception{
        Stat stat = zkClient.exists("/company", false);
        System.out.println(stat == null ? "不存在" : "存在");
    }

exists()参数:
1.查询哪个节点是否存在
2.是否监听,这里false就不监听了简单点。

写入请求发送流程

客户端要向集群写入数据时,他向集群发请求,有可能访问到Leader,也有可能访问到Follower,以下是两种流程:

写入请求发送给Leader节点的流程

在这里插入图片描述

写入请求发送给Follower节点的流程

在这里插入图片描述

服务器动态上下线

在这里插入图片描述
上图说的是一个层面,从另一个层面来说,对于ZooKeeper集群,其他任何的都是Client(是有点绕)。图中的服务器123对于ZooKeeper集群来说也是Client,只不过他向ZooKeeper集群发送的是create请求(创建节点),图中下半部分的“客户端”123,对于ZooKeeper集群来说也是Client,只不过他向ZooKeeper集群发送的是get请求(获取节点信息,还可以监听节点)。
ZkClient

public class ZkClient {
    //每个地址的左右不能有空格
    private String connectString = "192.168.88.129:2181,192.168.88.130:2181,192.168.88.131:2181";
    private int sessionTimeOut = 60000;
    private ZooKeeper zkClient;


    public void getConnect() throws Exception{
        zkClient = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
                try {
                    getServerList();//为了再次注册监听
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    //获取服务器列表
    public void getServerList() throws Exception{
        //获取服务器列表并监听/server节点
        List<String> children = zkClient.getChildren("/servers", true);

        //存储节点数据的列表
        ArrayList<String> serverData = new ArrayList<>();

        //获取节点中的数据,这里每个节点存的是名字
        for (String child : children){
            //第三个参数null就是不需要其他信息
            byte[] data = zkClient.getData("/servers/" + child, false, null);
            serverData.add(new String(data));
        }

        System.out.println(serverData);
    }

    //模拟业务功能
    public void business() throws Exception{
        System.out.println("client正在处理业务");

        //模拟处理业务需要很长时间
        Thread.sleep(Long.MAX_VALUE);
    }
}

ZkServer

public class ZkServer {
    private String connectString = "192.168.88.129:2181,192.168.88.130:2181,192.168.88.131:2181";
    private int sessionTimeOut = 60000;
    private ZooKeeper zkClient;

    public void getConnect() throws Exception{
        zkClient = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }

    //注册
    public void register(String hostname,String data) throws Exception{
        //想集群注册其实就是在集群里创建一个节点,这里创建的的是临时+带序号的节点
        String s = zkClient.create("/servers/"+hostname, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    }

    //模拟业务功能
    public void business(String hostname) throws Exception{
        System.out.println(hostname+"正在运行");

        //模拟处理业务需要很长时间
        Thread.sleep(Long.MAX_VALUE);
    }
}

ZkMain

public class ZkMain {

    public static void main(String[] args) throws Exception{
        ZkServer zkServer = new ZkServer();
        ZkClient zkClient = new ZkClient();

        zkClient.getConnect();
        zkClient.getServerList();
        zkClient.business();

        /*String name = "server1";
        zkServer.getConnect();
        zkServer.register(name);
        zkServer.business(name);*/

    }
}

命令行操作

先启动main()方法,
idea控制台打印:

[]
client正在处理业务
[]

使用虚拟机那边的客户端,在ZooKeeper集群的/节点创建一个/servers节点,在/servers节点下创建1个临时带序号节点/servers/server1

create -e -s  /servers/server1 "machine1"

idea控制台打印:

[machine1]

然后删除刚刚创建的/servers/server1节点

delete /servers/server10000000003

idea控制台:

[]

idea操作

修改ZkMain

public class ZkMain {
    ZkServer zkServer = new ZkServer();
    ZkClient zkClient = new ZkClient();

    @Test
    public void testClient() throws Exception{
        zkClient.getConnect();
        zkClient.getServerList();
        zkClient.business();
    }

    @Test
    public void testServer() throws Exception{
        String name = "server1";
        String data = "machine1";
        zkServer.getConnect();
        zkServer.register(name,data);
        zkServer.business(name);
    }
}

先启动testClient(),testClient()的控制控制台:

[]
client正在处理业务
[]

再启动testServer(),testServer() 的控制台:

server1正在运行

再看看testClient()的控制控制台:

[machine1]

curator框架实现分布式锁

什么叫做分布式锁呢?
比如说,"进程 1"想使用该资源的时候,会先去获得锁,"进程 1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程1"用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。
在这里插入图片描述
可以自己按照上图来写一个操作分布式锁的例子,但是情况可以很多和繁琐,可以用curator框架来实现。
添加依赖

		<dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.3.0</version>
        </dependency>

创建一个类演示

public class ZkLock {

   public static void main(String[] args) {
       CuratorFramework zkClient = getZkClient();
       String lockPath = "/locks";
       InterProcessMutex lock1 = new InterProcessMutex(zkClient, lockPath);
       InterProcessMutex lock2 = new InterProcessMutex(zkClient, lockPath);

       new Thread(new Runnable() {
           public void run() {
               try {
                   lock1.acquire();//获取到锁
                   System.out.println("线程1---获取到锁");
                   Thread.sleep(2000);
                   lock1.release();//释放锁
                   System.out.println("线程1---释放锁");
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
       }).start();

       new Thread(new Runnable() {
           public void run() {
               try {
                   lock2.acquire();//获取到锁
                   System.out.println("线程2---获取到锁");
                   Thread.sleep(2000);
                   lock2.release();//释放锁
                   System.out.println("线程2---释放锁");
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
       }).start();

   }

   private static CuratorFramework getZkClient(){
       String address = "192.168.88.129:2181,192.168.88.130:2181,192.168.88.131:2181";
       //3秒后重试,重试3次
       ExponentialBackoffRetry retry = new ExponentialBackoffRetry(3000, 3);
       CuratorFramework zkClient = CuratorFrameworkFactory.builder()
               .connectString(address)
               .sessionTimeoutMs(5000)
               .connectionTimeoutMs(20000)
               .retryPolicy(retry)
               .build();

       //启动客户端
       zkClient.start();
       return zkClient;
   }
}

在集群里创建好/locks节点
启动演示,看控制台打印:

线程2---获取到锁
线程2---释放锁
线程1---获取到锁
线程1---释放锁
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值