1、ZooKeeper概述
1.1、ZooKeeper 简介
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,
ZK 可以用来做什么
1.2、ZooKeeper 内存数据模型
Zookeeper 提供了一个类似于 Linux 文件系统的树形结构(可认为是轻量级的内存文件系统,在zk当中,每一个节点都称为ZNode,每一个节点可以存储节点的路径以及响应的数据,但只适合存少量信息,完全不适合存储大量文件或者大文件),同时提供了对于每个节点的监控与 通知机制。
1.3、ZooKeeper 节点的分类
2、ZooKeeper的安装
2.1、Windows下安装
在进行安装zookeeper之前,我们需要对JDK进行安装。
下载安装包:https://zookeeper.apache.org/releases.html
选择一个版本进行下载:
下载完成后对压缩文件进行解压缩:
打开apache-zookeeper-3.6.1-bin\conf目录,把zoo_sample.cfg重命名成zoo.cfg
之后使用记事本打开这个文件,修改dataDir的值:原先:dataDir=/tmp/zookeeper 修改成dataDir=F:\Spark\Zookeep\Zookeep\apache-zookeeper-3.6.1-bin 文件解压的目录
修改完成后进行添加系统的环境变量:
全部配置完成后,打开cmd,运行zookeeper即可,使用zkserver
2.2、linux下安装
Zookeeper 3.5.7 版本:下载地址
1、上传下载好的gz压缩文件到虚拟机,并且进行解压到指定目录,解压后进行重命名
# 进行解压
tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/
# 重命名
mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7
2、配置修改,将zookeeper-3.5.7/conf 这个路径下的 zoo_sample.cfg 修改为 zoo.cfg;打开 zoo.cfg 文件,修改 dataDir 路径:
# 重命名
mv zoo_sample.cfg zoo.cfg
# 打开文件
vim zoo.cfg
# 修改配置
dataDir=/opt/module/zookeeper-3.5.7/zkData
# 到指定的路径创建文件夹
mkdir zkData
3、Zookeeper的操作
# 启动zookeeper
zkServer.sh start
# 查看进程是否启动
jps
# 查看zookeeper当前状态
zkServer.sh status
# 启动客户端
zkCli.sh
# 停止zookeeper
zkServer.sh stop
2.3、使用 Docker 安装
docker pull zookeeper
docker run --name some-zookeeper --restart always -d zookeeper
2.4、集群搭建
在每台机器数据保持一致的情况下,zookeeper集群可以保证,客户端发起的每次查询操作,集群节点都能返回同样的结果。
但是对于客户端发起的修改、删除等能改变数据的操作呢?集群中那么多台机器,你修改你的,我修改我的,最后返回集群中哪台机器的数据呢?
这就是一盘散沙,需要一个领导,于是在zookeeper集群中,leader的作用就体现出来了,只有leader节点才有权利发起修改数据的操作,而follower节点即使接收到了客户端发起的修改操作,也要将其转交给leader来处理,leader接收到修改数据的请求后,会向所有follower广播一条消息,让他们执行某项操作,follower 执行完后,便会向 leader 回复执行完毕。当 leader 收到半数以上的 follower 的确认消息,便会判定该操作执行完毕,然后向所有 follower 广播该操作已经生效。
所以zookeeper集群中leader是不可缺少的,但是 leader 节点是怎么产生的呢?其实就是由所有follower节点选举产生的,讲究民主嘛,而且leader节点只能有一个,毕竟一个国家不能有多个总统。这也是为什么zk集群搭建的服务器需要是单数的原因。
下面就直接来搭建集群,这里就直接在同一台服务器上搭建了,对于多个服务器搭建也是一样的。
# 创建三个data
mkdir zkdata1 zkdata2 zkdata3
# 往三个目录中写入myid文件
touch zkdata1/myid zkdata2/myid zkdata3/myid
# 直接往文件当中输入内容
echo "1" >> zkdata1/myid
echo "2" >> zkdata2/myid
echo "3" >> zkdata3/myid
# 分别设置对应的配置文件
vim zkdata1/zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tools/zkdata1
clientPort=3001
# 对于多台服务器只需要修改对应的ip地址即可
server.1=192.168.101.128:3002:3003
server.2=192.168.101.128:4002:4003
server.3=192.168.101.128:5002:5003
vim zkdata2/zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tools/zkdata2
clientPort=4001
server.1=192.168.101.128:3002:3003
server.2=192.168.101.128:4002:4003
server.3=192.168.101.128:5002:5003
vim zkdata3/zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tools/zkdata3
clientPort=5001
server.1=192.168.101.128:3002:3003
server.2=192.168.101.128:4002:4003
server.3=192.168.101.128:5002:5003
# 切换目录,直接加载配置文件进行启动
cd tools/zookeeper-3.6.1
./bin/zkServer.sh start /tools/zkdata1/zoo.cfg
./bin/zkServer.sh start /tools/zkdata2/zoo.cfg
./bin/zkServer.sh start /tools/zkdata3/zoo.cfg
# 查看节点的状态,leader 和 follower
./bin/zkServer.sh status /tools/zkdata1/zoo.cfg
./bin/zkServer.sh status /tools/zkdata2/zoo.cfg
./bin/zkServer.sh status /tools/zkdata3/zoo.cfg
# 连接服务进行操作
./bin/zkCli.sh -server 192.168.101.128:3001
./bin/zkCli.sh -server 192.168.101.128:4001
./bin/zkCli.sh -server 192.168.101.128:5001
2.5、配置文件详解
3、ZooKeeper 指令操作
3.1 、客户端指令操作
服务启动与连接
./bin/zkServer.sh start ./conf/zoo.cfg
./bin/zkCli.sh
客户端部分指令操作
# 查看路径下的节点
ls /
# 创建节点
create /node1 yueyue # 默认 持久节点
create -s /node2 niao # 持久顺序节点
create -e /node3 yue # 临时节点
create -e -s /node3 huang # 临时顺序节点
# 查看节点状态
stat /node1
# 获取节点数据
get /node1
# 修改节点数据
set /node1 niao
# ls2 等价于 ls + stat
ls2 /node1
# 删除节点,只能删除没有字节点的节点
delete /node1
# 删除所有节点
deleteall /node1
# 清除会话,这时的临时节点也会被清除掉。
quit
4、ZooKeeper 节点监听机制
客户端可以监听znode节点的变化,znode节点的变化触发相应的事件,然后清除对该节点的监测,当监测一个znode节点的时候,zookeeper会发送通知到监测节点,一个watch事件是一个一次性的触发器,当被设置了watch的数据和目录发生了改变的时候,服务器将这个改变发送给设置了watch的客户端,用来进行通知。
# 对路径进行监听
ls -w /node
# 对数据进行监听
get -w /node
# 监听节点创建、删除
stat -w /qun
5、使用Java 操作 ZooKeeper
这里还是使用maven项目来进行操作,首先构建一个maven项目,在项目当中添加依赖。
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.10version>
dependency>
依赖添加之后就是编写java代码来对zk的节点进行操作,
import com.lzq.entity.User;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.List;
/**
* @author yueyueniao
* @version 1.0
* @date 2020-12-07 21:25
*/
public class Test {
public ZkClient zkClient;
public static void main(String[] args) throws Exception {
Test test = new Test();
test.init();
test.watchData();
}
public void init() {
// 连接到自己的zk服务
zkClient = new ZkClient("192.168.101.128:2181", 60000 * 30, 60000, new SerializableSerializer());
// 对于zk的集群操作,还是需要将所有的节点都写入进来,避免单个节点宕机导致服务无法操作集群
// zkClient = new ZkClient("192.168.101.128:3001,192.168.101.128:4001,192.168.101.128:5001",
// 60000 * 30, 60000, new SerializableSerializer());
System.out.println(zkClient);
}
public void createNode() {
// 创建节点,CreateMode.CONTAINER 节点类型(枚举类)
zkClient.create("/node0", "yueyue", CreateMode.CONTAINER);
}
public void deleteNode() {
// 删除节点
zkClient.delete("/node0");
// 删除所有节点
// zkClient.deleteRecursive("/node0");
}
public void getNode() {
// 获取节点
zkClient.getChildren("/node0");
}
public void getNodeData() {
// 获取节点数据
Object data = zkClient.readData("/node0");
}
public void getstat() {
// 获取节点状态
Stat stat = new Stat();
Object data = zkClient.readData("/node0", stat);
System.out.println(stat.getCzxid());
}
public void update() {
// 更新节点,这里的User实体类也要进行序列化,
User user = new User();
user.setId(1);
user.setName("niao");
zkClient.writeData("/node0", user);
User u = zkClient.readData("/node0");
System.out.println(u.toString());
}
private void watchData() throws IOException {
// 对节点数据进行监听,使用匿名内部类对IZkDataListener接口实现,直接重新内部两个方法
zkClient.subscribeDataChanges("/node0",new IZkDataListener(){
public void handleDataChange(String dataPath, Object data) throws Exception{
System.out.println(dataPath);
}
public void handleDataDeleted(String dataPath) throws Exception{
System.out.println(dataPath);
}
});
System.in.read();
}
private void watchChlid(){
// 对节点路径进行监听,大致与数据监听一致,
zkClient.subscribeChildChanges("/node0", new IZkChildListener() {
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println(parentPath);
}
});
}
}