Zookeeper
zookeeper
ZooKeeper是一个开源的分布式应用程序协调服务
简单来说可以理解为zookeeper = 文件系统+监听通知机制
应用场景:
- 集群管理、服务器状态感知
- 分布式应用配置管理
- 统一命名服务
- 分布式锁
小总结:
- 为客户提供写数据功能 数据不大 状态信息数据
- 为客户提供读取据功能
- 为用户提监控通知功能 节点数据的变化 节点的子节点个数
Zookeeper的高度可靠性
是一个分布式的系统,多个节点 并且节点中记录的数据是完全一致(一致性) , 当某个zk的节点宕机之后不会影响工作。因为Zookeeper的主节点不存在单点故障!Zookeeper的主节点是可以动态选举出来的!
Zookeeper的选举机制(奇数台)
zookeeper的进程在不同的工作模式下,有不同的通信端口(比如选举时,通过端口3888通信;作为leader或者follower接收客户端请求时通过端口2181;leader和follower之间通信用2888)
zk集群安装的时候 会人为的为每台机器分配一个唯一的id
Leader选举过程(以3个节点的集群为例):
• 集群初次启动时的选举流程
- 第一台机器(id=1)启动,发现没有leader,进入投票模式,投自己,并收到自己投这一票,得1票,不能当选leader(当leader的条件是,集群机器数量过半的票数)
- 第2台机器(id=2)启动,发现没有leader,进入投票模式,投自己(因为自己的id>1 收到的另一台机器的票的id)
- 第1台机器收到2的票,发现集群中有一个比自己id大的机器上线了,重新投票,投id=2
- 第2台收到的得票数为2票,过半数,自己当选,切换模式:Leader模式
- 第1台就发现有Leader存在了,自己切换模式:Follower
- 第3台启动,发现有Leader,自动进入Follower状态
如果每个节点是同时启动的zk 同时选举自己 ,同时广播 , 同时获取别人的广播,3号机器会当选
• 集群在运行过程中的选举流程
- 在某个时间点上,id=2机器挂了(leader),别的机器发现没有leader了,全体进入投票模式
- 先投自己,票中会携带(自己的id,自己的数据的版本号)
- 大家都投数据版本最新的节点做leader,如果有多个节点数据版本一样,则从中选id最大的那个作为投票目标!
从上述投票机制可以看出:
Zookeeper集群的节点数最好配置为奇数!
Zookeeper集群的节点规模一般在3~5台就够!
数据结构
zookeeper数据模型的结构与Unix文件系统很类似,整体上可看作是一棵树(也类似linux,hdfs),每一个节点称作一个ZNode。每一个ZNode默认只能够存储1MB的数据(无法存储海量数据)。每个ZNode都可以通过其路径唯一标识。
安装
1.上传安装包并解压
2.修改配置文件
cd /opt/apps/zookeeper-3.4.6/conf
# 将 zoo_sample.cfg的文件名修改成zoo.cfg
mv zoo_sample.cfg zoo.cfg
# 进入到zoo.cfg中修改配置信息
vi zoo.cfg
# 修改两个地方:
# 1.数据存储路径
dataDir=/opt/apps/zookeeper-3.4.6/zkData
# 2.zk服务地址
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
server.1=linux01:2888:3888
server.2=linux02:2888:3888
server.3=linux03:2888:3888
3.在各个节点上,手动创建数据存储目录
mkdir -p /opt/apps/zookeeper-3.4.6/zkData
4.在各个节点的数据存储目录中,生成一个myid文件,内容为它的id
# linux01
echo 1 > /opt/apps/zookeeper-3.4.6/zkData/myid
# lunux02
echo 2 > /opt/apps/zookeeper-3.4.6/zkData/myid
# lunux03
echo 3 > /opt/apps/zookeeper-3.4.6/zkData/myid
# 注意:在linux02上,myid里面的值就是2,03上面的值就是3,是依次递增的
5.分发安装包
for i in 2 3
do
scp -r zookeeper-3.4.6 linux0$i:$PWD
done
6.启动zk集群脚本
#!/bin/bash
for i in 1 2 3
do
ssh linux0${i} "source /etc/profile;/opt/apps/zookeeper-3.4.6/bin/zkServer.sh $1"
done
sleep 2
if [ $1 == start ]
then
for i in {1..3}
do
ssh doit0${i} "source /etc/profile;/opt/apps/zookeeper-3.4.6/bin/zkServer.sh status "
done
fi
命令行客户端操作
客户端启动连接
bin/zkCli.sh 本地连接
bin/zkCli.sh -server doit02:2181 连接到指定的服务节点
ls:查看某个路径下的key
ls /
[doit, zookeeper]
ls2:查看指定目录下的节点 详细
ls2 /zookeeper
[quota]
cZxid = 0x0 该数据节点被创建时的事务id
ctime = Thu Jan 01 08:00:00 CST 1970 该数据节点创建时间
mZxid = 0x0 该数据节点被修改时最新的事物id
mtime = Thu Jan 01 08:00:00 CST 1970 该数据节点最后修改时间
pZxid = 0x0 当前节点的父级节点事务ID
cversion = -1 子节点版本号(子节点修改次数,每修改一次值+1)
dataVersion = 0 当前节点版本号(可以理解为修改次数,每修改一次值+1)
aclVersion = 0 当前节点acl版本号(acl节点被修改次数,每修改一次值+1)
ephemeralOwner = 0x0 临时节点标示,当前节点如果是临时节点,则存储的创建者的会话id(sessionId),如果不是,那么值=0
dataLength = 0 当前节点数据长度
numChildren = 1 当前节点子节点个数
Create:创建一个znode
create /aaa ddd
[zk: localhost:2181(CONNECTED) 1] create /doit niubi
# 创建完成后当get的时候会有如下信息
[zk: localhost:2181(CONNECTED) 33] get /doit
niubi
cZxid = 0x2000000013
ctime = Fri May 05 22:25:28 CST 2023
mZxid = 0x2000000013
mtime = Fri May 05 22:25:28 CST 2023
pZxid = 0x2000000013
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
# 可以看到cversion 子节点的版本号还是0,以及dataVersion数据节点版本也还是0
#由于目前是默认创建的(没有使用-s -e),所以创建的这个节点是非顺序的,没有顺序的,并且是持久化的
#如果想要创建一个临时节点,可以加上 -e 这个参数
[zk: localhost:2181(CONNECTED) 36] create -e /doit/abc aaa
Created /doit/abc
[zk: localhost:2181(CONNECTED) 37] stat /doit
cZxid = 0x2000000013
ctime = Fri May 05 22:25:28 CST 2023
mZxid = 0x2000000013
mtime = Fri May 05 22:25:28 CST 2023
pZxid = 0x2000000015
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 1
#用stat命令查看的时候会发现 cversion 变成了1
# 在去查看临时节点的时候
[zk: localhost:2181(CONNECTED) 38] stat /doit/abc
cZxid = 0x2000000015
ctime = Fri May 05 22:28:29 CST 2023
mZxid = 0x2000000015
mtime = Fri May 05 22:28:29 CST 2023
pZxid = 0x2000000015
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x187ec3969a40000
dataLength = 3
numChildren = 0
#会发现临时节点和持久化节点的ephemeralOwner 是不一样的
#那么应该如何删除临时节点呢?只要断开客户端的链接即可。按住ctrl + c进行退出,然后再查询一下子节点
#创建顺序节点 create -s
[zk: localhost:2181(CONNECTED) 39] create -s /doit/aaa abc
Created /doit/aaa0000000001
[zk: localhost:2181(CONNECTED) 40] create -s /doit/aaa abc
Created /doit/aaa0000000002
[zk: localhost:2181(CONNECTED) 41] create -s /doit/aaa abc
Created /doit/aaa0000000003
[zk: localhost:2181(CONNECTED) 42] create -s /doit/aaa abc
Created /doit/aaa0000000004
[zk: localhost:2181(CONNECTED) 43] create -s /doit/aaa abc
Created /doit/aaa0000000005
#可以看到,用-s创建出来的子节点后面会跟上一个递增的序号
[zk: localhost:2181(CONNECTED) 44] ls /doit
[aaa0000000004, aaa0000000003, abc, aaa0000000005, aaa0000000002, aaa0000000001]
get:查看一个key的value
get /paopao
niubi
cZxid = 0x2000000003
ctime = Fri May 05 22:10:47 CST 2023
mZxid = 0x2000000005
mtime = Fri May 05 22:12:23 CST 2023
pZxid = 0x2000000003
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
set:给key的value赋值
set /doit paopao
cZxid = 0x2000000013
ctime = Fri May 05 22:25:28 CST 2023
mZxid = 0x200000001b
mtime = Fri May 05 22:38:04 CST 2023
pZxid = 0x200000001a
cversion = 6
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 6
delete:删除空节点
delete /paopao
# 如果这个节点不是空的,delete是删除不了的,只能用rmr删除
create /paopao aaa
Created /paopao
create /doit/abc aaa
Created /doit/abc
delete /paopao
Node not empty: /paopao
# rmr:删除节点(可以递归删除)
rmr /paopao
事件监听
zookeeper中对znode的变化描述有3种事件类型:
- 节点value变化事件
- 节点的子节点变化事件
- 节点被创建、被删除事件
对应的,客户端向zk注册监听的命令为:
get /abc watch
ls /abc watch
ls2 /abc watch
stat /abc watch
# 第一个客户端
get /paopao watch # 获取节点数据的时候监控(监控当前节点数据的变化)
# 克隆一个会话 在客户端修改节点数据的值
set /paopao bbb
# 第一个客户端会出现如下信息:
WatchedEvent state:SyncConnected type:NodeDataChanged path:/paopao
# 第一个客户端
ls /teache watch
# 克隆一个会话 创建一个客户端 , 在/teacher节点下创建一个新的节点/删除节点下的子节点
create /doit/aaa aaa
Created /doit/aaa
# 第一个客户端会出现如下信息:
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/doit
# 注意:客户端向zk注册的事件监听,只会被通知1次!如果需要持续监听,则需要反复注册
java客户端
添加依赖
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
</dependencies>
获取客户端
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.List;
public class zkCliDemo {
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
String connectionString = "linux01:2181,linux02:2181,linux03:2181" ;
// 超时时间 单位毫秒
int sessionTimeOut = 2000 ;
// 参数三 监听 null
// alt+enter 导包 异常处理
// 快速返回值 .var 或者 ctrl+alt+v
// 获取操作ZK的java客户端对象
ZooKeeper zooKeeper = new ZooKeeper(connectionString, sessionTimeOut, null);
/**
* 获取指定目录下的所有的子节点
* 参数一 路径 一定以/开始
* 参数二 是否监控
*/
List<String> list = zooKeeper.getChildren("/", null);
// 遍历指定节点下的所有的子节点
for (String s : list) {
System.out.println("/节点下的所有的子节点有: /"+s); // "/"s
}
zooKeeper.close();
}
}
根节点下有哪些子节点:cluster
根节点下有哪些子节点:brokers
根节点下有哪些子节点:zookeeper
根节点下有哪些子节点:admin
根节点下有哪些子节点:isr_change_notification
根节点下有哪些子节点:log_dir_event_notification
根节点下有哪些子节点:controller_epoch
根节点下有哪些子节点:doit
根节点下有哪些子节点:consumers
根节点下有哪些子节点:latest_producer_id_block
根节点下有哪些子节点:config
根节点下有哪些子节点:hbase
创建子节点
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import java.util.List;
/**
* 1.获取到zk的对象
* 2.调用api
*/
public class CreateDemo {
public static void main(String[] args) throws Exception {
//创建zk的对象
ZooKeeper zk = new ZooKeeper("linux01:2181",2000,null);
//创建一个子目录,返回创建的目录
/**
* 参数1:需要创建的节点名称,注意需要写绝对路径
* 参数2:该节点对应value的值,需要传二进制数组
* 参数3:该节点的权限,访问控制列表
* OPEN_ACL_UNSAFE:使用完全开放的ACL,允许客户端对znode进行读/写
* CREATOR_ALL_ACL:授予节点创建者所有权限。需要注意的是,设置此权限之前,创建者必须已经通了服务器的认证
* READ_ACL_UNSAFE:仅仅具有读权限
*参数四 节点的类型
* EPHEMERAL临时的 session级别的临时 客户端断开以后 节点会被删除
* EPHEMERA _SEQUENTIAL 临时有序
* PERSISTENT 永久
* PERSISTENT_SEQUENTIAL 永久有序
*/
String s = zk.create("/teacher1", "xiaotao".getBytes(), ZooDefs.Ids.READ_ACL_UNSAFE, CreateMode.PERSISTENT);
//打印返回创建的路径
System.out.println(s);
//关闭资源
zk.close();
}
}
删除子节点
import org.apache.zookeeper.ZooKeeper;
public class DeleteDemo {
public static void main(String[] args) throws Exception {
//创建zk的对象
ZooKeeper zk = new ZooKeeper("linux01:2181",2000,null);
/**
* 注意只能删除空节点
* -1代表任何版本,如果是4,那么到这个版本才能删掉,否则就会报错
* Exception in thread "main" org.apache.zookeeper.KeeperException$BadVersionException:
* KeeperErrorCode = BadVersion for /teacher
*/
zk.delete("/teacher",4);
//zk.delete("/teacher",-1);
zk.close();
}
}
递归删除节点
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import java.util.List;
/**
* 递归删除文件夹
*/
public class RMRDemo {
public static void main(String[] args) throws Exception {
//创建zk的对象
ZooKeeper zk = new ZooKeeper("linux01:2181",2000,null);
rmr("/teacher",zk);
zk.close();
}
public static void rmr(String path,ZooKeeper zk) throws Exception {
List<String> children = zk.getChildren(path, null);
if (children != null && children.size() >0){
for (String child : children) {
rmr(path+"/"+child,zk);
System.out.println("正在删除的子节点是:"+path+"/"+child);
}
}
zk.delete(path,-1)
}
}
获取节点的数据
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.List;
public class GetDemo {
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
String connectionString = "linux01:2181,linux02:2181,linux03:2181" ;
// 超时时间 单位毫秒
int sessionTimeOut = 2000 ;
// 参数三 监听 null
// alt+enter 导包 异常处理
// 快速返回值 .var 或者 ctrl+alt+v
// 获取操作ZK的java客户端对象
ZooKeeper zk = new ZooKeeper(connectionString, sessionTimeOut, null);
byte[] data = zk.getData("/a0000000004", null, null);
String str = new String(data);
System.out.println(str);
zk.close();
}
}
修改节点的数据
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
public class SetDemo {
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
String connectionString = "linux01:2181,linux02:2181,linux03:2181" ;
int sessionTimeOut = 2000 ;
ZooKeeper zk = new ZooKeeper(connectionString, sessionTimeOut, null);
/**
* 更新节点的数据
* 参数一 节点
* 参数二 value的值
* 参数三 数据版本
*/
Stat stat = zk.setData("/a", "abc".getBytes(), -1);
zk.close();
}
}
监控节点中值得变化
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 WatcherDemo {
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
//创建zk的对象
ZooKeeper zk = new ZooKeeper("linux01:2181", 2000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("就单纯的整一个监听器");
}
});
byte[] data = zk.getData("/teacher", new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
byte[] data = new byte[0];
try {
data = zk.getData("/teacher", this, null);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(new String(data));
}
}, null);
// System.out.println(new String(data));
Thread.sleep(Integer.MAX_VALUE);
}
}
监听节点中子节点得个数变化
import com.sun.media.jfxmediaimpl.HostUtils;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.List;
/**
* 监听节点中值的变化
*/
public class WatcherDemo1 {
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
//创建zk的对象
ZooKeeper zk = new ZooKeeper("linux01:2181", 2000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("就单纯的整一个监听器");
}
});
List<String> list = zk.getChildren("/teacher", new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
List<String> children = zk.getChildren("/teacher", this);
for (String child : children) {
System.out.println(new String(zk.getData("/teacher/"+child,null,null)));
}
System.out.println("===========分隔符===============");
} catch (Exception e) {
e.printStackTrace();
}
}
});
//遍历所有的节点
// System.out.println(list);
Thread.sleep(Integer.MAX_VALUE);
}
}
shell客户端:
[zk: localhost:2181(CONNECTED) 68] create /teacher/a 1
Created /teacher/a
[zk: localhost:2181(CONNECTED) 69] create /teacher/b 2
Created /teacher/b
[zk: localhost:2181(CONNECTED) 70] create /teacher/c 3
Created /teacher/c
[zk: localhost:2181(CONNECTED) 71]
控制台:
就单纯的整一个监听器
1
===========分隔符===============
1
2
===========分隔符===============
1
2
3
===========分隔符===============