赞
踩
统一配置管理
统一集群管理
软负载均衡
服务器动态上下线
统一命名服务
安装在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
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项的流程一样,少数服从多数。
几个重要信息:
1.当ZooKeeper集群中的1台服务器出现下列情况之一时,就会开始Leader选举:
2.当1台机器进入Leader选举流程时,当前集群也可能会处于以下两种状态:
SID | Epoch | ZXID | SID |
---|---|---|---|
1 | 1 | 8 | 1 |
2 | 1 | 8 | 2 |
4 | 1 | 7 | 4 |
选举
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
首先创建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.节点类型(永久/临时,带序号/不带序号)
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-----------------------
@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,以下是两种流程:
上图说的是一个层面,从另一个层面来说,对于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控制台:
[]
修改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]
什么叫做分布式锁呢?
比如说,"进程 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---释放锁
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。