zookeeper概念介绍
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
官方文档上这么解释zookeeper,它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
上面的解释有点抽象,简单来说zookeeper=文件系统+监听通知机制。
zookeeper的基本功能和应用场景
zookeeper的整体运行机制
Zookeeper 集群:
Zookeeper 是一个由多个 server 组成的集群,一个 leader,多个 follower。(这个不同于我们常见的Master/Slave模式)leader 为客户端服务器提供读写服务,除了leader外其他的机器只能提供读服务。
每个 server 保存一份数据副本全数据一致,分布式读 follower,写由 leader 实施更新请求转发,由 leader 实施更新请求顺序进行,来自同一个 client 的更新请求按其发送顺序依次执行数据更新原子性,一次数据更新要么成功,要么失败。全局唯一数据视图,client 无论连接到哪个 server,数据视图都是一致的实时性,在一定事件范围内,client 能读到最新数据。
集群角色
Leader:是整个 Zookeeper 集群工作机制中的核心 。** **。
主要工作:
事务请求的唯一调度和处理,保障集群处理事务的顺序性。
集群内各服务器的调度者。
Leader 选举是 Zookeeper 最重要的技术之一,也是保障分布式数据一致性的关键所在。我们以三台机器为例,在服务器集群初始化阶段,当有一台服务器Server1启动时候是无法完成选举的,当第二台机器 Server2 启动后两台机器能互相通信,每台机器都试图找到一个leader,于是便进入了 leader 选举流程.
每个 server 发出一个投票
投票的最基本元素是(SID-服务器id,ZXID-事物id)
接受来自各个服务器的投票
处理投票
优先检查 ZXID(数据越新ZXID越大),ZXID比较大的作为leader,ZXID一样的情况下比较SID
统计投票
这里有个过半的概念,大于集群机器数量的一半,即大于或等于(n/2+1),我们这里的由三台,大于等于2即为达到“过半”的要求。
这里也有引申到为什么 Zookeeper 集群推荐是单数。
集群数量 至少正常运行数量 允许挂掉的数量
2 2的半数为1,半数以上最少为2 0
3 3的半数为1.5,半数以上最少为2 1
4 4的半数为2,半数以上最少为3 1
5 5的半数为2.5,半数以上最少为3 2
6 6的半数为3,半数以上最少为4 2
通过以上可以发现,3台服务器和4台服务器都最多允许1台服务器挂掉,5台服务器和6台服务器都最多允许2台服务器挂掉,明显4台服务器成本高于3台服务器成本,6台服务器成本高于5服务器成本。这是由于半数以上投票通过决定的。
改变服务器状态
一旦确定了 leader,服务器就会更改自己的状态,且一半不会再发生变化,比如新机器加入集群、非 leader 挂掉一台。
Follower :是 Zookeeper 集群状态的跟随者。他的逻辑就比较简单。除了响应本服务器上的读请求外,follower 还要处理leader 的提议,并在 leader 提交该提议时在本地也进行提交。另外需要注意的是,leader 和 follower 构成ZooKeeper 集群的法定人数,也就是说,只有他们才参与新 leader的选举、响应 leader 的提议。
Observer :服务器充当一个观察者的角色。如果 ZooKeeper 集群的读取负载很高,或者客户端多到跨机房,可以设置一些 observer 服务器,以提高读取的吞吐量。Observer 和 Follower 比较相似,只有一些小区别:首先 observer 不属于法定人数,即不参加选举也不响应提议,也不参与写操作的“过半写成功”策略;其次是 observer 不需要将事务持久化到磁盘,一旦 observer 被重启,需要从 leader 重新同步整个名字空间。
1、 文件系统
Zookeeper维护一个类似文件系统的数据结构,zookeeper中对用户的数据采用kv形式存储
只是zk有点特别:
在ZooKeeper中,这些信息被保存在一个个数据节点上,这些节点被称为znode。它采用了类似文件系统的层级树状结构进行管理。
key:是以路径的形式表示的,那就以为着,各key之间有父子关系,比如
/ 是顶层key
用户建的key只能在/ 下作为子节点,比如建一个key: /aa 这个key可以带value数据
也可以建一个key: /bb
也可以建key: /aa/xx
zookeeper中,每一个key-value称为一个znode
综上所述,zk中的数据存储形式如下:
每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。
有四种类型的znode:
- PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在 - PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 - EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除 - EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
2、 监听通知机制
客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
ZooKeeper的好处:
简单的分布式协调过程
同步 - 服务器进程之间的相互排斥和协作。此过程有助于Apache HBase进行配置管理。
有序的消息
序列化 - 根据特定规则对数据进行编码。确保应用程序运行一致。这种方法可以在MapReduce中用来协调队列以执行运行的线程。
可靠性
原子性 - 数据转移完全成功或完全失败,但没有事务是部分的。
. zookeeper的集群部署
1、上传安装包到集群服务器
2、解压
3、修改配置文件
进入zookeeper的安装目录的conf目录
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg
#The number of milliseconds of each tick
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/root/zkdata
clientPort=2181
#autopurge.purgeInterval=1
server.1=hdp20-01:2888:3888
server.2=hdp20-02:2888:3888
server.3=hdp20-03:2888:3888
对3台节点,都创建目录 mkdir /root/zkdata
对3台节点,在工作目录中生成myid文件,但内容要分别为各自的id: 1,2,3
hdp20-01上: echo 1 > /root/zkdata/myid
hdp20-02上: echo 2 > /root/zkdata/myid
hdp20-03上: echo 3 > /root/zkdata/myid
4、从hdp20-01上scp安装目录到其他两个节点
scp -r zookeeper-3.4.6/ hdp20-02PWD
5、启动zookeeper集群
zookeeper没有提供自动批量启动脚本,需要手动一台一台地起zookeeper进程
在每一台节点上,运行命令:
bin/zkServer.sh start
启动后,用jps应该能看到一个进程:QuorumPeerMain
但是,光有进程不代表zk已经正常服务,需要用命令检查状态:
bin/zkServer.sh status
能看到角色模式:为leader或follower,即正常了。
zookeeper的命令行客户端操作
zookeeper的数据存储形式:
一、zookeeper中存储数据的基本形式为: key , value
二、zookeeper中的key是用路径表示的:
/aa : 88888
/aa/bb : “xxoo”
/aa/cc : “edu360”
/tt: 9898
每一个key-value称为一个znode(zookeeper数据节点)
数据管理功能:
创建节点: create /aaa ‘ppppp’
查看节点下的子节点: ls /aaa
获取节点的value: get /aaa
修改节点的value: set /aaa ‘mmmmm’
删除节点:rmr /aaa
数据监听功能:
ls /aaa watch
查看/aaa的子节点的同时,注册了一个监听“节点的子节点变化事件”的监听器
get /aaa watch
获取/aaa的value的同时,注册了一个监听“节点value变化事件”的监听器
注意:注册的监听器在正常收到一次所监听的事件后,就失效
zookeeper编程
zookeeper客户端api
public class ZookeeperClientDemo {
ZooKeeper zk = null;
@Before
public void init() throws Exception{
// 构造一个连接zookeeper的客户端对象
// 参数1:要创建的zookeeper的 参数2:要创建的zookeeper的端口 参数三是watcher的实现类,收到通知后的操作,可在该类实现
zk = new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, null);
}
@Test
public void testCreate() throws Exception{
//创建节点:
// 参数1:要创建的节点路径 参数2:数据 参数3:访问权限 参数4:节点类型
String create = zk.create("/eclipse", "hello eclipse".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(create);
zk.close();
}
@Test
public void testUpdate() throws Exception {
//更新节点
// 参数1:节点路径 参数2:数据 参数3:所要修改的版本,-1代表任何版本
zk.setData("/eclipse", "我爱你".getBytes("UTF-8"), -1);
zk.close();
}
@Test
public void testGet() throws Exception {
//监听功能:获取该key的value,并且决定注册了一个监听“节点的子节点变化事件”的监听器
// 参数1:节点路径 参数2:是否要监听 参数3:所要获取的数据的版本,null表示最新版本
byte[] data = zk.getData("/eclipse", false, null);
System.out.println(new String(data,"UTF-8"));
zk.close();
}
@Test
public void testListChildren() throws Exception {
//监听功能:获取该key的所有value,并且决定注册了一个监听“节点的子节点变化事件”的监听器
// 参数1:节点路径 参数2:是否要监听
// 注意:返回的结果中只有子节点名字,不带全路径
List<String> children = zk.getChildren("/cc", false);
for (String child : children) {
System.out.println(child);
}
zk.close();
}
@Test
public void testRm() throws InterruptedException, KeeperException{
//更新节点删除节点
// 参数1:节点路径 参数2:所要修改的版本,-1表示所有版本,null表示最新版本
zk.delete("/eclipse", -1);
zk.close();
}
}
zookeeper监听功能实现
public class ZookeeperWatchDemo {
ZooKeeper zk = null;
@Before
public void init() throws Exception {
// 构造一个连接zookeeper的客户端对象
zk = new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, new Watcher() { // 在watcher类中重写precess方法,从而定义接到通知后的操作
@Override
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected && event.getType() == EventType.NodeDataChanged) {
System.out.println(event.getPath()); // 收到的事件所发生的节点路径
System.out.println(event.getType()); // 收到的事件的类型
System.out.println("赶紧换照片,换浴室里面的洗浴套装....."); // 收到事件后,我们的处理逻辑
try {
zk.getData("/mygirls", true, null); //在操作完成后,再次查看value,建立监听,这样就可以不断循环监听
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}else if(event.getState() == KeeperState.SyncConnected && event.getType() == EventType.NodeChildrenChanged){
System.out.println("子节点变化了......");
}
//数据监听
@Test //
public void testGetWatch() throws Exception {
byte[] data = zk.getData("/mygirls", true, null); // 监听节点数据变化
List<String> children = zk.getChildren("/mygirls", true); //监听节点的子节点变化事件
System.out.println(new String(data, "UTF-8"));
Thread.sleep(Long.MAX_VALUE); //是守护线程,如果不sleep,主线程会结束,关闭监听
}
}
zookeeper监听案例功能实现
在这里插入代码片