学习目标:
- 什么是zookeeper
- 应用场景
- 基本的操作1. 安装部署2. shell客户端3. java端
- 基本原理 1. 选举机制2. 数据一致性3. 数据的读写流程
1 zookeeper简介
zookeeper是一个底层的集群协调工具,(比如:NN和DN之间的状态感应;监控 通知)!
具备基本的功能有 ,记录用户的状态数据 (写), 返回用户的数据(读) ,监控和通知。zookeeper为了高可用和安全性是一个集群! 3台(每个节点的数据完全一致)!
2 应用场景
- 写数据
- 读取数据
- 监控通知
2.1 服务状态感知
2.2 分布式锁
2.3 分布式配置同步
2.4 统一域名
分布式中统一CMD指令
分布式中服务器的状态感知
3 操作
3.1 安装
zookeeper在部署的时候选择奇数台!要求zk集群半数以上的机器在线才能正常工作!
**上传解压 **
配置
- 在zk安装目录下 创建文件夹 zkData mkdir zkData /opt/apps/zookeeper-3.4.6/zkData
- 修改conf/下的配置模板配置文件名 mv zoo_sample.cfg zoo.cfg
- vi zoo.cfg
dataDir=/opt/apps/zookeeper-3.4.6/zkData# Set to "0" to disable auto purge feature#autopurge.purgeInterval=1server.1=linux01:2888:3888server.2=linux02:2888:3888server.3=linux03:2888:3888
- 在zkData文件夹下创建myid文件 echo 1 > zkData/myid
**同步安装包 **
scp -r zookeeper-3.4.6/ linux02:$PWD
scp -r zookeeper-3.4.6/ linux03:$PWD
修改各个节点myid的值 linux02【2】 linux03【3】
**启动集群 **
在各个节点上启动ZK服务
/opt/apps/zookeeper-3.4.6/bin/zkServer.sh start
查看进程 jps
[root@linux01 bin]# jps
1697 QuorumPeerMain
1720 Jps
查看集群状态 在每个节点执行
/opt/apps/zookeeper-3.4.6/bin/zkServer.sh status
-------------------------------------------------------
Mode: follower
Mode: leader
Mode: follower
3.2 编写一键启动脚本
#!/bin/bash
# zk启停脚本
for hostname in linux01 linux02 linux03
do
echo "连接${hostname}... ...正在执行 $1"
ssh ${hostname} "source /etc/profile ;/opt/apps/zookeeper-3.4.6/bin/zkServer.sh $1 ;exit"
done
3.3 shell操作
zookeeper中记录数据以Tree节点的形式存储数据的/类似于目录树!
目录叫znode : 节点的组织数据是 K V
基本操作指令
# 客户端连接
bin/zkCli.sh 连接到本地zk服务
bin/zkCli.sh -server linux02:2181 连接到执行节点的服务
quit 退出客户端 ctrl+c
-------------------------------------------
help 帮助命令 查看系统支持的所有的命令
ZooKeeper -server host:port cmd args
stat path [watch]
set path data [version]
ls path [watch] *
delquota [-n|-b] path
ls2 path [watch] *
setAcl path acl
setquota -n|-b val path
history
redo cmdno
printwatches on|off
delete path [version] *
sync path
listquota path
rmr path *
get path [watch]
create [-s] [-e] path data acl *
addauth scheme auth
quit *
getAcl path
close *
connect host:port
-------------------------------------------------------------------
创建节点 节点必须以/开头 (路径必须是绝对路径)
-- create [-s] [-e] path data acl
create /a 1
create /b 2
create /a/a1 11
create /a/a2 22
create /a/a3 33
[-s] 在节点后面添加一个自增的id 防止节点名重复而创建失败
[-e] 创建的节点是临时节点 , 只在当前连接中有效
------节点分类-----
--临时节点
临时有序 -e
临时无序 -e -s
--永久节点
永久有序 -s
永久无序 默认
注意: 节点的创建不能层级创建
查看节点列表
ls /a
ls2 /a 显示数据版本 , 事务id 创建时间等信息
修改数据
set /a 123
获取数据
get /a
删除节点
delete /a 只能删除空节点
rmr /a 可以删除任意节点
监听和通知
事件 : 触发了某种事件 通知
- 节点数据的变化
- 子节点个数的变化
- ls path [watch] 监控指定路径下数据的变化
- get path [watch] 监控指定路径数据的变化
注意: 监控和通知只能1次
打开两个zk客户端
1) 一个监控 2) 一个修改数据
# 子节点个数变化
窗口01 : ls / watch
->(WatchedEvent state:SyncConnected type:NodeChildrenChangedpath:/)
窗口02 : rmr /a000000004
---------------------------------------------------------------------------
# 节点数据变化
窗口01 : get /b watch
->(WatchedEvent state:SyncConnected type:NodeDataChanged path:/b)
窗口02 : set /b 321
3.4 java操作
- 添加依赖
- 获取zk客户端
- 调用API
- 释放资源
添加依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
入门示例-[获取连接\创建节点]
package com.doitedu.zk.cli;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
/**
* @Date 2022/2/23
* @Created by HANGGE
* @Description TODO
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
// 获取zk的客户端
/**
* 参数一 zk的地址 host:port,host2:port
* 参数二 连接的超时时间 毫秒
* 参数三 连接成功后的监听
*/
ZooKeeper zk = new ZooKeeper("linux01:2181,linux02:2181,linux03:2181", 3000, null);
// 调用API
//1 创建节点
/**
* 参数1 路径
* 参数2 数据 值 字节数组
* 参数3 权限
* 参数4 节点类型 1 2 3 4
*/
zk.create("/doitedu" , "JINGYINGDOIT30_".getBytes() , ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.EPHEMERAL_SEQUENTIAL);
// System.out.println(zk);
//释放资源
Thread.sleep(8000);
zk.close();
}
}
zookeeper-Java-API使用
package com.doitedu.zk.cli;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.util.List;
/**
* @Date 2022/2/23
* @Created by HANGGE
* @Description
* 创建节点
* 删除节点
* 获取值
* 修改值
* 遍历子节点列表
* 判断节点是否存在
*/
public class ClientApiDemo {
public static void main(String[] args) throws Exception {
// 获取客户端对象
ZooKeeper zk = ZookeeperUtil.getZookeeper();
// testGetData(zk);
//修改数据
//testSetData(zk);
//获取子节点列表
//testls(zk);
// 只能删除空节点 递归
// zk.delete("/b" , -1);
// 递归删除节点
ZookeeperUtil.rmr(zk,"/b") ;
zk.close();
}
// 查看路径下的子节点列表
public static void testls(ZooKeeper zk) throws KeeperException, InterruptedException {
List<String> ls = zk.getChildren("/", null);
if(ls!=null && ls.size()>0){
for (String name : ls) {
System.out.println("节点的名字是:" + name);
byte[] data = zk.getData("/" + name, null, null);
System.out.println(new String(data));
}
}
}
// 更新数据
public static void testSetData(ZooKeeper zk) throws KeeperException, InterruptedException {
Stat stat = zk.setData("/b", "liulan".getBytes(), -1);
// 如果更新成功Stat 不为null
if(stat != null){
testGetData(zk);
}
}
// 获取数据
public static void testGetData(ZooKeeper zk) throws KeeperException, InterruptedException {
byte[] data = zk.getData("/b", null, null);
String str = new String(data);
System.out.println(str);
}
}
封装工具类
package com.doitedu.zk.cli;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.List;
/**
* @Date 2022/2/23
* @Created by HANGGE
* @Description TODO
*/
public class ZookeeperUtil {
/**
* 获取指定zk集群的客户端对象
* linux01 02 03
* @return
* @throws IOException
*/
public static ZooKeeper getZookeeper() throws IOException {
return new ZooKeeper("linux01:2181,linux02:2181,linux03:2181", 3000, null);
}
/**
* 删除任意节点
* @param zk
* @param path
* @throws KeeperException
* @throws InterruptedException
*/
public static void rmr(ZooKeeper zk , String path) throws KeeperException, InterruptedException {
// 遍历是否有子节点
List<String> ls = zk.getChildren(path, null);
// 有子节点
if(ls!=null && ls.size() >0){
// 删除子节点
for (String name : ls) {
//---递归
rmr(zk , path+"/"+name);
}
}
//没有子节点
zk.delete(path , -1);
}
}
监控和通知
- 连接成功 监听通知
- 数据变化
- 节点变化
例子
package com.doitedu.zk.cli;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
/**
* @Date 2022/2/23
* @Created by HANGGE
* @Description 连接对象获取成功 事件通知
*/
public class TestWatcher01 {
public static void main(String[] args) throws Exception {
/**
* 参数三 事件通知 监听器
* 在获取对象的时候使用 当连接成功以后会 回调一次process方法
*/
ZooKeeper zk = new ZooKeeper("linux11:2181", 2000, new Watcher() {
public void process(WatchedEvent event) {
System.out.println("获取连接成功......");
}
});
System.out.println(zk);
zk.close();
}
}
----------------------------------------------------------------------
package com.doitedu.zk.cli;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
/**
* @Date 2022/2/23
* @Created by HANGGE
* @Description
* 监听节点数据的变化
* 当监听的节点的数据发生变化 通知客户端 回调process
*/
public class TestWatcher02 {
public static void main(String[] args) throws Exception {
/**
* 参数三 事件通知 监听器
* 在获取对象的时候使用 当连接成功以后会 回调一次process方法
*/
final ZooKeeper zk = new ZooKeeper("linux01:2181", 2000, null);
// 获取指定节点数据的时候 绑定监听器 监听此节点数据的变化
byte[] data = zk.getData("/b", new Watcher() {
// /b节点数据变化就会执行这个方法 1次
public void process(WatchedEvent event) {
try {
System.out.println("/b的数据发生了变化....");
System.out.println(event.getType());
// 改变后的数据是
byte[] data1 = zk.getData("/b", this, null);
System.out.println("变化后的的数据是: "+new String(data1));
} catch (Exception e) {
e.printStackTrace();
}
}
}, null);
System.out.println("变化以前的数据是: "+new String(data));
Thread.sleep(Integer.MAX_VALUE);
zk.close();
}
}
-----------------------------------------------------------------------
package com.doitedu.zk.cli;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.util.List;
/**
* @Date 2022/2/23
* @Created by HANGGE
* @Description
* 监听节点数据的变化
* 当监听的节点的数据发生变化 通知客户端 回调process
*/
public class TestWatcher03 {
public static void main(String[] args) throws Exception {
/**
* 参数三 事件通知 监听器
* 在获取对象的时候使用 当连接成功以后会 回调一次process方法
*/
final ZooKeeper zk = new ZooKeeper("linux01:2181", 2000, null);
List<String> ls = zk.getChildren("/", new Watcher() {
// 当 / 节点的子节点 被删除 添加 执行这个方法
public void process(WatchedEvent event) {
try {
System.out.println(event.getType());
List<String> ls2 = zk.getChildren("/", this);
System.out.println("变化后的节点有:"+ls2);
} catch (Exception e) {
e.printStackTrace();
}
}
});
System.out.println("当前的节点有: "+ls);
Thread.sleep(Integer.MAX_VALUE);
zk.close();
}
}
4 原理基础
4.1 选举机制
在ZK中有三种状态 ;
Leader: 优先负责写数据
follower 存储数据 参与选举
observe 观察者 不参与选举 存储数据
每个服务器都有一个唯一的myid标记
初始启动选举
- ZK服务启动[linux01(myid=1)] , 发现集群中没有Leader .进入到选举状态 ; 投自己一票
- myid=1的机器获取1票 , 票数没有过半, 不能当选leader
- linux01, 广播投票结果
- linux02(myid=2)启动 ,发现集群中没有leader , 进入到选举状态
- 收到linux01的1票信息 , 发现linux02的myid > linux01的myid ;投自己一票 ,广播
- linux01收到广播 ,发现自己的myid小 ; 改投lnux02 1票 , 广播
- linux02 收到linux01的广播 ,自己的票数为2 , 过半 ,切换leader状态
- linux01 切换follower状态
- linux03启动 ,发现集群中有leader ;切换follower状态
运行过程中选举
注意: 要求整个集群中的数据是一致的! 有事务的保证! zxid事务id
1 先比较zxid的大小 , zxid大的优先当选
2 如果事务id一致 , 再比较myid
整个集群中有机器宕机 ,属于不正常的状态! 修复.........
数据一致性
zk中存储数据以KV的形式 ; K是以节点的组织的 以/开头
整个集群中所有的节点存储一样的数据
记录的数据一般为状态数据 ,配置数据 ,命令....数据量不大
版权归原作者 白眼黑刺猬 所有, 如有侵权,请联系我们删除。