1. 基本概念
官方文档:https://zookeeper.apache.org/doc/r3.6.3/zookeeperStarted.html
官方文档上这么解释zookeeper,它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
上面的解释有点抽象,简单来说zookeeper=文件系统+监听通知机制。
Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架, 它负责存储和管理大家都关心的数据, 然后接受观察者的注册, 一旦这些数据的状态发生变化 ,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。
- Zookeeper 可以被用作注册中心。
- Zookeeper 是 Hadoop 生态系统的一员。
- 构建 Zookeeper 集群的时候,使用的服务器最好是奇数台。
ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
1.1 文件系统
Zookeeper维护一个类似文件系统的数据结构。文件树存储在内存中,所以速度很快。
ZooKeeper数据模型的结构与Unix文件系统很类似, 整体上可以看作是一棵树, 每个节点称做一个ZNode。 每一个ZNode默认能够存储1MB的数据, 每个ZNode都可以通过其路径唯一标识。
每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。
有四种类型的znode:
-
PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在
-
PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
-
EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后(会话失效,超时后失效,或者会话执行quit),该节点被删除
-
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
1.2 特点
1) Zookeeper:一个领导者(Leader) , 多个跟随者(Follower) 组成的集群。
2) 集群中只要有半数以上节点存活, Zookeeper集群就能正常服务。
3) 全局数据一致:每个Server保存一份相同的数据副本, Client无论连接到哪个Server, 数据都是一致的。
4) 更新请求顺序进行, 来自同一个Client的更新请求按其发送顺序依次执行。
5) 数据更新原子性, 一次数据更新要么成功, 要么失败。
6) 实时性, 在一定时间范围内, Client能读到最新数据。
2. 安装
2.1 源码包安装
我学习时的稳定版本为3.6.3,前置条件已安装jdk
1.下载地址:https://ftp.wayne.edu/apache/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.g
2.将安装包copy到linux的指定文件夹下。
3.解压到自定义目录。
tar -xvzf 压缩文件
4.修改配置文件
将conf文件夹下的zoo-simple.cfg文件修改为zoo.cfg
# cp conf/zoo_sample.cfg conf/zoo.cfg
5.启动
# bin/zkServer.sh start (配置文件路径)
6.jps查看是否启动
huodada@huodada:~/software/zookeeper/apache-zookeeper-3.6.3-bin$ jps
1657730 QuorumPeerMain --说明启动乘公共
2532090 ZooKeeperMain
7.启动客户端
bin/zkCli.sh #默认连接本地服务器
# 连接启动服务器使用-server参数
bin/zkCli.sh -server 127.0.0.1:2181
##
quit #关闭连接并使会话失效
8.停止
bin/zkServer.sh stop
2.2 docker安装
比较简单,下载3.6的镜像启动即可。
3. 配置文件
Zookeeper中的配置文件zoo.cfg中参数含义解读如下:
1. tickTime =2000: 通信心跳数, Zookeeper 服务器与客户端心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,
也就是每个tickTime时间就会发送一个心跳, 时间单位为毫秒。
它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。 (session的最小超
时时间是2*tickTime)
2. initLimit =10: LF 初始通信时限,也就是初始化集群时集群节点同步超时事件为20s
集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心
跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
3. syncLimit =5: LF 同步通信时限
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit *
tickTime, Leader认为Follwer死掉,从服务器列表中删除Follwer。
4. dataDir:数据文件目录+数据持久化路径
主要用于保存 Zookeeper 中的数据。
5. clientPort =2181:客户端连接端口
监听客户端连接的端口。
4. 客户端基本命令
zookeeper的api命令比较简单,在 shell 中,键入 help 以获得可以从客户端执行的命令列表,共有以下命名。
ZooKeeper -server host:port -client-configuration properties-file cmd args
addWatch [-m mode] path # optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
addauth scheme auth
close
config [-c] [-w] [-s]
connect host:port
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
## -s表示创建顺序节点,-e表示临时节点,默认为永久节点
delete [-v version] path
deleteall path [-b batch size]
delquota [-n|-b] path
get [-s] [-w] path
getAcl [-s] path
getAllChildrenNumber path
getEphemerals path
history
listquota path
ls [-s] [-w] [-R] path
printwatches on|off
quit
reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
redo cmdno
removewatches path [-c|-d|-a] [-l]
set [-s] [-v version] path data
setAcl [-s] [-v version] [-R] path acl
setquota -n|-b val path
stat [-w] path
sync path
version
说明:
ZooKeeper 的设计目标之一是提供一个非常简单的编程接口。因此,它只支持以下操作:
create : creates a node at a location in the tree 》》create /zk_test my_data
Create: 在树中的某个位置创建一个节点
delete : deletes a node 》》delete /zk_test
删除: 删除节点
exists : tests if a node exists at a location
如果一个节点在某个位置存在,则测试
get data : reads the data from a node 》》get /zk_test
获取数据: 从一个节点读取数据
set data : writes data to a node
Set data: 设置某个节点的数据 》》set /zk_test junk
get children : retrieves a list of children of a node
Get children: 检索节点的子节点列表
ls path 》》 ls /
sync : waits for dat
6. 节点监听机制
zookeeper支持监听功能,当监听的节点或者节点数据发生变化时,执行一次监听语句,只发生一次监听回调。
监听节点数据:get [-s] [-w] path 》》当一个客户端
监听节点:ls [-w] path
7. java操作ZK
可以使用java或者c来当做客户端。
详细文档参见https://zookeeper.apache.org/doc/r3.6.3/zookeeperProgrammers.html#ch_programStructureWithExample
java使用的依赖为
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.3</version>
</dependency>
下面是测试代码
package com.huo.test;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.proto.WatcherEvent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.omg.CORBA.ARG_OUT;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* package: com.huo.test
* author: Huo
* Description:
* Date: Create in 13:17 2021/4/23
*/
public class Demo02 implements Watcher {
private ZooKeeper zooKeeperClient;
private static final String IP = "192.168.1.101:2181";
private static final int SESSION_OUTTIME = 5000;
//用于阻塞程序执行,直到zk成功连接后再发送继续执行信号,因为zk建立连接是异步的,阻塞是防止程序在zk成功连接前被调用。
private static final CountDownLatch contectedSemaphore = new CountDownLatch(1);
@Override
public void process(WatchedEvent event) {
//WatcherEvent为观察者监听事件
//获取事件状态(与客户端连接状态相关)
/*
* KeeperState:Disconneced 连接失败
* KeeperState:SyncConnected 连接成功
* KeeperState:AuthFailed 认证失败
* KeeperState:Expired 会话过期
*/
Event.KeeperState state = event.getState();
//获取事件类型(与zknode相关)
/*
* EventType:NodeCreated 节点创建
* EventType:NodeDataChanged 节点的数据变更
* EventType:NodeChildrentChanged 子节点下的数据变更
* EventType:NodeDeleted 节点删除
* EventType:None 刚连接什么都没做
*/
Event.EventType type = event.getType();
System.out.println(type);
//成功建立连接(固定写法)
//发送信号让程序继续执行
//成功建立连接(固定写法)
if (Event.KeeperState.SyncConnected == state) {
if (Event.EventType.None == type) {
//发送信号让程序继续执行
contectedSemaphore.countDown();
}
}
}
//创建节点
@Test
public void testCreateNode() throws InterruptedException, KeeperException {
System.out.println(zooKeeperClient);
//阻塞程序执行
String newNode = zooKeeperClient.create("/huonode", "huodalao".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("创建节点" + newNode);
}
//获取某个路径的所有子节点
@Test
public void getAllNode() throws InterruptedException, KeeperException {
System.out.println(zooKeeperClient);
//阻塞程序执行
List<String> newNode = zooKeeperClient.getChildren("/", false);
System.out.println("该路径下所有子节点" + newNode);
}
/**
* 获取节点上面的数据
*
* @param
* @return
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void getData() throws KeeperException, InterruptedException {
byte[] data = zooKeeperClient.getData("/node", false, null);
if (data == null) {
System.out.println("null");;
}
System.out.println(new String(data));
}
/**
* 设置节点信息
*
* @return
* @throws InterruptedException
*/
@Test
public void setData() throws KeeperException, InterruptedException {
Stat stat = zooKeeperClient.setData("/node", "huodada".getBytes(), -1);
System.out.println(stat);
this.getData();
}
/**
* 删除节点
*
* @throws InterruptedException
* @throws KeeperException
*/
@Test
public void deleteNode() throws InterruptedException, KeeperException {
zooKeeperClient.delete("/testRoot", -1);
}
/**
* 获取创建时间
*
* @return
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void getCTime() throws KeeperException, InterruptedException {
Stat stat = zooKeeperClient.exists("/node", false);
System.out.println(String.valueOf(stat.getCtime()));
}
/**
* 获取某个路径下孩子的数量
*
* @return
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void getChildrenNum() throws KeeperException, InterruptedException {
int childenNum = zooKeeperClient.getChildren("/", false).size();
System.out.println(childenNum);
}
//启动某个事件之前先创建链接对象
@Before
public void getConnect() throws IOException, InterruptedException {
zooKeeperClient = new ZooKeeper(IP, 6000 * 30, this);
//保证连接创建成功
contectedSemaphore.await();
System.out.println("zookeeper connection success");
}
//完成处理后关闭连接
@After
public void closeClient() throws InterruptedException {
zooKeeperClient.close();
}
}
8. ZK的集群
在独立模式下运行 ZooKeeper 方便了评估、开发和测试。但是在生产环境中,应该以复制模式运行 ZooKeeper。同一应用程序中的复制服务器组称为仲裁,在复制模式下,仲裁中的所有服务器都具有相同配置文件的副本。
至少需要三台服务器,并且强烈建议您使用奇数个服务器。
配置文件示例
tickTime=2000 # 毫秒
dataDir=/var/lib/zookeeper #数据目录
clientPort=2181
initLimit=5 #五个tickTime
syncLimit=2 #两个tickTime
server.1=zoo1的ip:2888:3888
server.2=zoo2的ip:2888:3888
server.3=zoo3的ip:2888:3888
说明:主要是最后三行,各主机都需要包括本身所有主机的ip和两个端口。
server.X 的条目列出了组成 ZooKeeper 服务的服务器。当服务器启动时,它通过在数据目录中查找文件 myid 来知道它是哪个服务器。也就是说每个zookeeper服务器的数据目录都有一个myid文件里面有一个整形数字标识服务器号。
2888:各主机之间的同步,3888:心跳检查
然后三台服务器做同样的配置,启动后会产生一个leader两个follower。