文章目录
Zk介绍
特点
数据结构
ZooKeeper 数据模型的结构与 Unix 文件系统很类似
,整体上可以看作是一棵树,每个
节点称做一个 ZNode。每一个 ZNode 默认能够存储 1MB 的数据,每个 ZNode 都可以通过 其路径唯一标识
应用场景
提供的服务包括:统一命名服务
、统一配置管理
、统一集群管理
、服务器节点动态上下线
、软负载均衡
等
统一命名服务
需要对应用 / 服务进行统一命名
,便于识别。
例如:IP不容易记住,而域名容易记住。
统一配置管理
统一集群管理
服务器动态上下线
软负载均衡
在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去 处理最新的客户端请求
Zk安装、集群
下载、启动
https://zookeeper.apache.org/
解压、配置zk
tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz -C /opt/module/ # 解压
cd /opt/module/ # 进入 该目录
mv apache-zookeeper-3.7.1-bin/ zookeeper-3.7.1 # 改名
cd /opt/module/zookeeper-3.7.1 # 进入 改名后的目录
mkdir /opt/module/zookeeper-3.7.1/zkdata # 创建存放zk数据的目录
cd conf/
cp zoo_sample.cfg zoo.cfg # 拷贝 一份配置文件,重命名为 zoo.cfg
vim zoo.cfg # 编辑配置文件
修改
dataDir
的目录
操作zk
cd /opt/module/zookeeper-3.7.1/bin
./zkServer.sh start # 启动zk
./zkServer.sh status # 查看zk状态
./zkCli.sh # 连接zk服务端
配置参数解读
Zookeeper中的配置文件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
:客户端连接端口,通常不做修改
Zookeeper 集群操作
第一台机器
解压、配置zk
tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz -C /opt/module/ # 解压
cd /opt/module/ # 进入 该目录
mv apache-zookeeper-3.7.1-bin/ zookeeper-3.7.1 # 改名
cd /opt/module/zookeeper-3.7.1 # 进入 改名后的目录
mkdir /opt/module/zookeeper-3.7.1/zkdata # 创建存放zk数据的目录
cd conf/
cp zoo_sample.cfg zoo.cfg # 拷贝 一份配置文件,重命名为 zoo.cfg
vim zoo.cfg # 编辑配置文件
修改
dataDir
的目录
cd /opt/module/zookeeper-3.7.1/zkdata/ # 进入数据目录
vim myid
在文件中添加与 server 对应的编号
(注意:上下不要有空行,左右不要有空格)
2
注意:
添加 myid 文件
,一定要在 Linux 里面创建,在 notepad++里面很可能乱码
其他机器:
其他不变,修改一下
myid
文件中的 值、修改数据存储路径
配置
在每台机器增加一下配置
#######################cluster##########################
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:3888
server.4=hadoop104:2888:3888
增加的 配置参数解读
server.A=B:C:D
A
是一个数字,表示这个是第几号
服务器;
集群模式下配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面有一个数据就是A
的值
Zookeeper 启动时读取此文件
,拿到里面的数据与 zoo.cfg
里面的配置信息比较从而判断到底是哪个 server。
B
是这个服务器的地址;C
是这个服务器 Follower 与集群中的 Leader 服务器交换信息的端口
;D
是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口
分别启动Zk即可
选举机制(面试重点)
第一次启动
非
第一次启动
启动选举规则:
- ①EPOCH 大 的直接胜出
- ②EPOCH 相同,
事务 id
大的胜出 - ③事务 id 相同,
服务器 id
大的胜出
生产集群安装多少 zk 合适?
安装奇数台
。
生产经验:
⚫ 10 台服务器:3 台 zk;
⚫ 20 台服务器:5 台 zk;
⚫ 100 台服务器:11 台 zk;
⚫ 200 台服务器:11 台 zk
服务器台数多:
好处,提高可靠性
;坏处:提高通信延时
客户端命令行操作
help # 显示所有操作命令
ls path #使用 ls 命令来查看当前 znode 的子节点 [可监听]
# -w 监听子节点变化
# -s 附加次级信息
create # 普通创建
#-s 含有序列
#-e 临时(重启或者超时消失)
get path # 获得节点的值 [可监听]
# -w 监听节点内容变化
# -s 附加次级信息
set # 设置节点的具体值
stat # 查看节点状态
delete # 删除节点
deleteall # 递归删除节点
客户端界面
可以下载 prettyZoo-win.msi
进行操作,更直观
节点类型(持久 / 短暂 / 有序号 / 无序号)
1)分别创建 2个 普通节点(永久节点 + 不带序号)
create /sanguo "liubei"
注意:
创建节点时,要赋值
2)获得节点的值
get /sanguo
get -s /sanguo
3)创建带序号的节点(永久节点 + 带序号)
先创建一个
普通
的根节点/sanguo/weiguo
,默认是持久化,不带序号
的节点
create /sanguo/weiguo "caocao"
创建
带序号的节点
create -s /sanguo/weiguo "caocao"
如果原来没有序号节点,序号从 0 开始依次递增
。如果原节点下已有 2 个节点,则再排序时从 2 开始
,以此类推
4)创建短暂节点( 短暂节点 + 不带序号 or 带序号)
创建 短暂的 不带序号
的 节点
create -e /sanguo/weiguo "x1"
创建短暂的带序号
的节点
create -es /sanguo/weiguo "x2"
5)修改 节点数据值
set /sanguo/weiguo0000000006 xiaowang
监听器
1)节点的值变化监听
注册监听
/sanguo
节点数据变化
get -w /sanguo
修改 /sanguo
节点的数据
type:NodeDataChanged
代表 节点数据变化
2)节点的子节点变化监听(路径变化)
监听/sanguo
节点的子节点变化
ls -w /sanguo
type:NodeChildrenChanged
子节点变化
节点删除与查看
# 创建节点
create /sanguo/jin
# 删除节点
delete /sanguo/jin
# 递归 删除节点
deleteall /sanguo/shuguo
查看
节点状态
stat /sanguo
客户端 API 操作
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.7.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
创建 ZooKeeper 客户端
import lombok.SneakyThrows;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
public class DemoApplicationTests {
// 注意:多个 zk服务器 用逗号分隔
// 逗号前后 不能有空格
private static String connectString =
"192.168.111.101:2181";
private static int sessionTimeout = 2000;
private ZooKeeper zkClient;
@Before
public void init() throws Exception {
zkClient = new ZooKeeper (connectString, sessionTimeout, new Watcher () {
@SneakyThrows
@Override
public void process(WatchedEvent watchedEvent) {
// 收到 事件通知后的 回调函数(用户的业务逻辑)
System.out.println (watchedEvent.getType () + "--"
+ watchedEvent.getPath ());
// 再次启动监听
List<String> children = zkClient.getChildren ("/", true);
for (String child : children) {
System.out.println (child);
}
}
});
}
// 创建子节点
@Test
public void create() throws Exception {
/**
* 参数 1:要创建的节点的路径;
* 参数 2:节点数据 ;
* 参数 3:节点权限 ;
* 参数 4:节点的类型
*
*/
String nodeCreated = zkClient.create ("/xiag",
"shuaige".getBytes (), ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
Thread.sleep (Long.MAX_VALUE);
}
// 获取子节点并监听节点变化
@Test
public void getChildren() throws Exception {
List<String> children = zkClient.getChildren ("/", true);
for (String child : children) {
System.out.println (child);
}
// 延时阻塞
Thread.sleep (Long.MAX_VALUE);
}
// 判断 znode 是否存在
@Test
public void exist() throws Exception {
Stat stat = zkClient.exists ("/atguigu", false);
System.out.println (stat == null ? "not exist" : "exist");
}
}
案例
服务器动态上下线
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class DistributeClient {
private static String connectString =
"192.168.111.101:2181";
private static int sessionTimeout = 2000;
private ZooKeeper zk = new ZooKeeper (connectString, sessionTimeout, new Watcher () {
@Override
public void process(WatchedEvent event) {
}
});
private String parentNode = "/servers";
public DistributeClient() throws IOException {
}
// 获取服务器列表信息
// 获取 servers 的子节点信息,从中获取服务器信息列表
public void getServerList() throws Exception {
// 1 获取服务器子节点信息
// 第一次 调用该方法时 注册监听事件、添加 回调函数
// 然后 获取节点数据 运行下面的逻辑
List<String> children = zk.getChildren (parentNode, event -> {
// 监听的事件 发生时, 第一步 注册监听事件、添加 回调函数
// 然后 再执行下面的业务!!必须 要每次都要注册!形成循环~
try {
getServerList ();
} catch (Exception e) {
throw new RuntimeException (e);
}
});
// 2 存储服务器信息列表
ArrayList<String> servers = new ArrayList<> ();
// 3 遍历 所有节点,获取 节点中的主机名称信息
for (String child : children) {
byte[] data = zk.getData (parentNode + "/" + child,
false, null);
if (data == null || data.length == 0) continue;
servers.add (new String (data));
}
// 4 打印服务器列表信息
System.out.println (servers);
}
// 业务功能
public void business() throws Exception {
System.out.println ("client is working ...");
Thread.sleep (Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 1 获取 zk 连接
DistributeClient client = new DistributeClient ();
//注册监听事件
client.getServerList ();
// 2 业务进程启动
client.business ();
}
}
# 创建节点 /servers
create /servers
删除一个节点
动态监听是否有流量
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
public class DistributeClient {
private static String connectString =
"192.168.111.101:2181";
private static int sessionTimeout = 2000;
private ZooKeeper zk = new ZooKeeper (connectString, sessionTimeout, new Watcher () {
@Override
public void process(WatchedEvent event) {
}
});
;
public DistributeClient() throws IOException {
}
public void hasFlow() throws Exception {
// 第一次 调用该方法时 注册监听事件、添加 回调函数
// 然后 获取节点数据 运行下面的逻辑
byte[] data = zk.getData ("/hasFlow", event -> {
// 监听的事件 发生时, 第一步 注册监听事件、添加 回调函数
// 然后 再执行下面的业务!!必须 要每次都要注册!形成循环~
try {
hasFlow ();
} catch (Exception e) {
throw new RuntimeException (e);
}
}, null);
String dataStr = new String (data);
if ("false".equals (dataStr))
System.out.println ("No Flow!");
else if ("true".equals (dataStr))
System.out.println ("Flow is ok ");
}
// 业务功能
public void business() throws Exception {
System.out.println ("client is working ...");
Thread.sleep (Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 1 获取 zk 连接
DistributeClient client = new DistributeClient ();
//注册监听事件
client.hasFlow ();
client.business ();
}
}
# 先创建节点 /hasFlow
create /hasFlow ""