目录
2>PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
4>EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
一、简介
1.1 官网解释
Zookeeper(动物管理员),它是拿来管大象(Hadoop)、蜜蜂(Hive)、小猪(Pig)的管理员, Apache Hbase和Apache Solr以及阿里的Dubbo等项目中都采用到了Zookeeper。
一句话:ZooKeeper是一个分布式协调技术、高性能的,开源的分布式系统的协调(Coordination)服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用程序一致性和分布式协调技术服务的软件。它是拿来管大象(Hadoop)、蜜蜂(Hive)、小猪(Pig)的管理员, Apache Hbase和Apache Solr以及阿里的Dubbo等项目中都采用到了Zookeeper。
1.2 设计模式
基于观察者模式设计的分布式服务管理框架。
负责存储和管理大家都关心的数据,接受观察者注册,一旦数据的状态发生变化,Zookeeper就负责通知已经在Zookeeper上注册的那些观察者做出响应的反应,从而实现集群中类似Master/Slave管理模式。
1.3 使用
1.3.1 在Dubbo中实现
服务提供者在启动的时候,向ZK上的指定节点/dubbo/${serviceName}/providers目录下写入自己的URL地址。 这个操作就完成了服务的发布。
服务消费者启动的时候,订阅/dubbo/${serviceName}/providers目录下的提供者URL地址, 并向/dubbo/${serviceName} /consumers目录下写入自己的URL地址。
注意,所有向ZK上注册的地址都是临时节点,这样就能够保证服务提供者和消费者能够自动感应资源的变化。 另外,Dubbo还有针对服务粒度的监控,方法是订阅/dubbo/${serviceName}目录下所有提供者和消费者的信息。
1.3.2 配置管理
在大型的分布式系统中,为了服务海量的请求,同一个应用常常需要多个实例。如果存在配置更新的需求,常常需要逐台更新,给运维增加了很大的负担同时带来一定的风险(配置会存在不一致的窗口期,或者个别节点忘记更新)。zookeeper可以用来做集中的配置管理,存储在zookeeper集群中的配置,如果发生变更会主动推送到连接配置中心的应用节点,实现一处更新处处更新的效果
现在把这些配置全部放到zookeeper上去,保存在 Zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中就好。
二、安装配置
2.1 Linux下安装
官网下载地址:Apache ZooKeeper
前提:必须安装java环境
默认端口号:2181
1>创建安装目录(自己的软件安装方式)
mkdir /zookeeper
2>将tar包拷贝进创建的文件中并解压
x.x.x:代表版本
mv zookeeper-x.x.x.tar.gz /zookeeper
tar -zxvf zookeeper-x.x.x.tar.gz
3>进入conf文件夹,拷贝zoo_sample.cfg改为zoo.cfg
cp zoo_sample.cfg zoo.cfg
zoo.cfg文件解释:
tickTime:通信心跳数,Zookeeper服务器心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间 或 服务器与客户端 之间维持心跳的间隔时间,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。即:session>=2*tickTimeinitLimit
这个配置项是用来配置Zookeeper接收Follower客户端(这里所说的客户端不是用户链接Zookeeper服务器的客户端.
而是Zookeeper服务器集群中连接到leader的Follower服务器,Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。
Leader允许Follower在 initLimit 时间内完成这个工作)初始化连接是最长能忍受多少个心跳的时间间隔数。syncLimit:LF同步通信时限
集群中Leader与Follower之间的最大响应时间单位。
在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态,
假如响应超过syncLimit * tickTime(假设syncLimit=5 ,请求和应答时间长度,最长不能超过多少个tickTime的时间长度,总的时间长度就是5*2000=10秒。),Leader认为Follwer死掉,从服务器列表中删除Follwer。在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。
如果L发出心跳包在syncLimit之后,还没有从F那收到响应,那么就认为这个F已经不在线了。dataDir:数据文件目录 + 数据持久化路径
保存内存数据库快照信息的位置,如果没有其他说明,更新的事务日志也保存到数据库。clientPort:客户端连接端口
监听客户端连接的端口
4>进入bin目录,启动zookeeper
./zkServer.sh start
#关闭命令
./zkServer.sh stop
#查看状态
./zkServer.sh status
#查看是否真正启动成功,查看进程
ps -ef | grep zookeeper
成功启动截图:
2.2 链接客户端
1>进入bin目录下:./zkCli.sh
2>退出:quit命令
2.3 客户端基本命令使用
1> ls / 查看+获取zookeeper服务器上根节点
初始只有一个节点
2> get /zookeeper 获取节点存储数据
无数据:第一行不显示
三、数据模型/znode 节点
3.1 文件系统
所使用的数据模型风格很像文件系统的目录树结构,简单来说,有点类似windows中注册表的结构,
有名称,有树节点,有Key(键)/Value(值)对的关系,
可以看做一个树形结构的数据库,分布在不同的机器上做名称管理。
znode节点:
3.2 znode数据模型
1>是什么
Znode维护了一个stat结构,这个stat包含数据变化的版本号、访问控制列表变化、还有时间戳。版本号和时间戳一起,可让Zookeeper验证缓存和协调更新。每次znode的数据发生了变化,版本号就增加。
例如,无论何时客户端检索数据,它也一起检索数据的版本号。并且当客户端执行更新或删除时,客户端必须提供他正在改变的znode的版本号。如果它提供的版本号和真实的数据版本号不一致,更新将会失败。
2>Zookeeper的Stat结构体
czxid- 引起这个znode创建的zxid,创建节点的事务的zxid(ZooKeeper Transaction Id)
每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
ctime - znode被创建的毫秒数(从1970年开始)
mzxid - znode最后更新的zxid
mtime - znode最后修改的毫秒数(从1970年开始)
pZxid-znode最后更新的子节点zxid
cversion - znode子节点变化号,znode子节点修改次数
dataversion - znode数据变化号
aclVersion - znode访问控制列表的变化号
ephemeralOwner- 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0x0。
dataLength- znode的数据长度
numChildren - znode子节点数量
znode的集合又是一个树形结构,
每一个znode又有很多属性进行描述。 Znode = path + data + Stat
Path:路径
Data:数据
Stat:版本,时间戳等信息
案例:
3.3 znode中存在类型
1>PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在
# create /demo1 java
2>PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
# create -s /demo2 spring
3>EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除
# create -e /demo3 redis
4>EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
# create -s -e /demo3 dubbo
四、基础命令和Java客户端操作
4.1 基础命令
①help
②ls /path使用ls命令来查看当前znode中所包含的内容
③ls2 /path 查看当前节点数据并能看到更新次数等数据
④stat /path查看节点状态
⑤set /path data 设置节点的具体值
⑥get /path 获取节点的值
⑦create [-参数] /path data
-s 含序列的节点
-e 临时节点
-s -e 含序列的临时节点
⑧delete /path 删除无子节点的节点
⑨rmr 递归删除:删除含子节点的节点
4.2 四字命令
zookeeper支持某些特定的四字命令,他们大多是用来查询ZK服务的当前状态及相关信息的。
通过telnet或nc向zookeeper提交相应命令,如:echo ruok | nc 127.0.0.1 2181
命令格式: echo 四字命令 | nc 主机IP zookeeper端口
常用命令:
ruok:测试服务是否处于正确状态。如果确实如此,那么服务返回“imok ”,否则不做任何相应
stat:输出关于性能和连接的客户端的列表
conf:输出相关服务配置的详细信息
cons:列出所有连接到服务器的客户端的完全的连接 /会话的详细信息。包括“接受 / 发送”的包数量、会话id 、操作延迟、最后的操作执行等等信息
dump:列出未经处理的会话和临时节点
envi:输出关于服务环境的详细信息(区别于conf命令)
reqs:列出未经处理的请求
wchs:列出服务器watch的详细信息
wchc:通过session列出服务器watch的详细信息,它的输出是一个与watch相关的会话的列表
wchp:通过路径列出服务器 watch的详细信息。它输出一个与 session相关的路径
案例:
在终端中执行,不在cli客户端中
4.3 Java客户端操作
1>创建Maven项目
2>添加依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
3>代码
public class ZkDemo {
private static final String CONNECTSTRING = "192.168.200.129:2181";
private static final String PATH = "/demo"; //新节点
private static final int SESSION_TIMEOUT = 50*1000;
// 连接zk 方法
public ZooKeeper startZk() throws Exception{
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
// 监听方法
@Override
public void process(WatchedEvent event) {
}
});
}
// 关闭zk 方法
public void stopZk(ZooKeeper zooKeeper) throws Exception{
if (zooKeeper!=null){
zooKeeper.close();
}
}
/**
* 创建节点
* @param zk zk 对象
* @param path 节点名称
* @param nodeValue 节点数据
* @throws Exception
*/
public void createZNode(ZooKeeper zk,String path,String nodeValue) throws Exception{
// acl 表示权限 OPEN_ACL_UNSAFE 公开的权限
zk.create(path,nodeValue.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 获取节点
public String getZNode(ZooKeeper zk,String path)throws Exception{
byte[] byteArray = zk.getData(path, false, new Stat());
String data = new String(byteArray);
System.out.println(data);
return data;
}
// 调用
public static void main(String[] args) throws Exception {
ZkDemo zkDemo = new ZkDemo();
// 获取连接
ZooKeeper zk = zkDemo.startZk();
String zNode = null;
Stat stat = zk.exists(PATH, false);
if (stat==null){
// 创建节点
zkDemo.createZNode(zk,PATH,"java");
}else {
System.out.println("***********znode has already ok***********");
}
// 获取节点数据
zNode = zkDemo.getZNode(zk, PATH);
System.out.println("**********result:"+zNode);
// 关闭连接
zkDemo.stopZk(zk);
}
}
五、通知机制
5.1 概述
客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
观察者功能:
ZooKeeper 支持watch(观察)的概念。客户端可以在每个znode结点上设置一个观察。如果被观察服务端的znode结点有变更,那么watch就会被触发,这个watch所属的客户端将接收到一个通知包被告知结点已经发生变化,把相应的事件通知给设置过Watcher的Client端。
Zookeeper里的所有读取操作:getData(),getChildren()和exists()都有设置watch的选项。
异步回调的触发机制!
5.2 watch事件
5.3.1 一次触发
当数据有了变化时zkserver向客户端发送一个watch,它是一次性的动作,即触发一次就不再有效,类似一次性纸杯。
只监控一次。
如果想继续Watch的话,需要客户端重新设置Watcher。因此如果你得到一个watch事件且想在将来的变化得到通知,必须新设置另一个watch。
5.3.2 发往客户端
Watches是异步发往客户端的,Zookeeper提供一个顺序保证:在看到watch事件之前绝不会看到变化,这样不同客户端看到的是一致性的顺序。
例如:java调用zk,则会将信息发送给java客户端
在(导致观察事件被触发的)修改操作的成功返回码到达客户端之前,事件可能在去往客户端的路上,但是可能不会到达客户端。观察事件是异步地发送给观察者(客户端)的。ZooKeeper会保证次序:在收到观察事件之前,客户端不会看到已经为之设置观察的节点的改动。网络延迟或者其他因素可能会让不同的客户端在不同的时间收到观察事件和更新操作的返回码。这里的要点是:不同客户端看到的事情都有一致的次序。
5.3.3 为数据设置watch
节点有不同的改动方式。可以认为ZooKeeper维护两个观察列表:数据观察和子节点观察。getData()和exists()设置数据观察。getChildren()设置子节点观察。此外,还可以认为不同的返回数据有不同的观察。getData()和exists()返回节点的数据,而getChildren()返回子节点列表。所以,setData()将为znode触发数据观察。成功的create()将为新创建的节点触发数据观察,为其父节点触发子节点观察。成功的delete()将会为被删除的节点触发数据观察以及子节点观察(因为节点不能再有子节点了),为其父节点触发子节点观察。
观察维护在客户端连接到的ZooKeeper服 务器中。这让观察的设置、维护和分发是轻量级的。客户端连接到新的服务器时,所有会话事件将被触发。同服务器断开连接期间不会收到观察。客户端重新连接 时,如果需要,先前已经注册的观察将被重新注册和触发。通常这都是透明的。有一种情况下观察事件将丢失:对还没有创建的节点设置存在观察,而在断开连接期 间创建节点,然后删除。
5.3.4 时序性和一致性
Watches是在client连接到Zookeeper服务端的本地维护,这可让watches成为轻量的,可维护的和派发的。当一个client连接到新server,watch将会触发任何session事件,断开连接后不能接收到。当客户端重连,先前注册的watches将会被重新注册并触发。
关于watches,Zookeeper维护这些保证:
(1)Watches和其他事件、watches和异步恢复都是有序的。Zookeeper客户端保证每件事都是有序派发
(2)客户端在看到新数据之前先看到watch事件
(3)对应更新顺序的watches事件顺序由Zookeeper服务所见
5.3 代码
5.3.1一次监听
public class WatchOne {
private static final String CONNECTSTRING = "192.168.200.129:2181";
private static final String PATH = "/atguigu";
private static final int SESSION_TIMEOUT = 50*1000;
// 声明zk变量
private ZooKeeper zk = null;
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
// 连接zk 方法
public ZooKeeper startZk() throws Exception{
//
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
}
});
}
/**
* 创建节点
* @param path 节点名称
* @param nodeValue 节点数据
* @throws Exception
*/
public void createZNode(String path,String nodeValue) throws Exception{
// acl 表示权限 OPEN_ACL_UNSAFE 公开的权限
zk.create(path,nodeValue.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 获取节点
public String getZNode(String path)throws Exception{
byte[] byteArray = zk.getData(path, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 监听事件
try {
triggerValue(path);
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Stat());
String data = new String(byteArray);
System.out.println(data);
return data;
}
// 监听
public String triggerValue(String path) throws Exception{
byte[] bytes = zk.getData(path, false, new Stat());
String resvalue = new String(bytes);
System.out.println("监听方法:"+resvalue);
return resvalue;
}
public static void main(String[] args) throws Exception {
// 创建对象
WatchOne watchOne = new WatchOne();
watchOne.setZk(watchOne.startZk());
// 判断节点 是否存在
if (watchOne.getZk().exists(PATH,false)==null){
// 创建节点
watchOne.createZNode(PATH,"html");
// 获取节点
String zNode = watchOne.getZNode(PATH);
System.out.println("数据:"+zNode);
Thread.sleep(Long.MAX_VALUE);
}else{
System.out.println("has exists.....");
}
}
}
5.3.2多次监听
public class WatchMore {
private static final String CONNECTSTRING = "192.168.200.129:2181";
private static final String PATH = "/admin";
private static final int SESSION_TIMEOUT = 50*1000;
// 声明zk变量
private ZooKeeper zk = null;
// 记录节点的内容
private String lastValue = "";
public String getLastValue() {
return lastValue;
}
public void setLastValue(String lastValue) {
this.lastValue = lastValue;
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
// 连接zk 方法
public ZooKeeper startZk() throws Exception{
//
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
}
});
}
/**
* 创建节点
* @param path 节点名称
* @param nodeValue 节点数据
* @throws Exception
*/
public void createZNode(String path,String nodeValue) throws Exception{
// acl 表示权限 OPEN_ACL_UNSAFE 公开的权限
zk.create(path,nodeValue.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 获取节点
public String getZNode(String path)throws Exception{
byte[] byteArray = zk.getData(path, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 监听事件
try {
triggerValue(path);
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Stat());
String data = new String(byteArray);
System.out.println(data);
return data;
}
// 监听
public boolean triggerValue(String path) throws Exception{
byte[] bytes = zk.getData(path, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 监听事件
try {
triggerValue(path);
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Stat());
String resvalue = new String(bytes);
if (lastValue.equals(resvalue)){
System.out.println("there is no change~~~~~~~~");
return false;
}else {
System.out.println("lastValue: "+lastValue+"\t"+"resvalue: "+resvalue);
this.lastValue = resvalue;
return true;
}
}
public static void main(String[] args) throws Exception {
// 创建对象
WatchMore watchMore = new WatchMore();
watchMore.setZk(watchMore.startZk());
// 判断节点 是否存在
if (watchMore.getZk().exists(PATH,false)==null){
// 初始化数据
String initValue = "0000";
watchMore.setLastValue(initValue);
// 创建节点
watchMore.createZNode(PATH,initValue);
// 获取节点
String zNode = watchMore.getZNode(PATH);
System.out.println("数据:"+zNode);
Thread.sleep(Long.MAX_VALUE);
}else{
System.out.println("has exists.....");
}
}
}
六、zookeeper集群配置
(伪集群配置)
6.1概述
initLimit 是Zookeeper用它来限定集群中的Zookeeper服务器连接到Leader的时限
syncLimit 限制了follower服务器与leader服务器之间请求和应答之间的时限
服务器名称与地址:集群信息(服务器编号,服务器地址,LF通信端口,选举端口)
这个配置项的书写格式比较特殊,规则如下:server.N=YYY:A:B 其中,
N表示服务器编号,
YYY表示服务器的IP地址,
A为LF通信端口,表示该服务器与集群中的leader交换的信息的端口。
B为选举端口,表示选举新leader时服务器间相互通信的端口(当leader挂掉时,其余服务器会相互通信,选择出新的leader)
一般来说,集群中每个服务器的A端口都是一样,每个服务器的B端口也是一样。
下面是一个集群的例子:
server.0=233.34.9.144:2008:6008
server.1=233.34.9.145:2008:6008
server.2=233.34.9.146:2008:6008
server.3=233.34.9.147:2008:6008
但是当所采用的为伪集群时,IP地址都一样,只能是A端口和B端口不一样。
下面是一个伪集群的例子:
server.0=127.0.0.1:2008:6008
server.1=127.0.0.1:2007:6007
server.2=127.0.0.1:2006:6006
6.2 搭建集群
1>创建文件夹 zk01,zk02,zk03
将zookeeper-x.x.x.tar.gz解压并拷贝进创建的文件夹中
tar -zxvf zookeeper-x.x.x.tar.gz
mv zookeeper-x.x.x zk01
mv zookeeper-x.x.x zk02
mv zookeeper-x.x.x zk03
2>在zk01(zk02|zk03)中创建文件夹 mydata、mylog
cd zk01
mkdir mydata
mkdir mylog
3>进入conf文件夹创建zoo.cfg文件
cp zoo_sample.cfg zoo.cfg
4>修改zoo.cfg配置文件
添加数据存储目录和数据日志目录
#dataLogDir=/opt/zk01/mylog 可以省略不用写也没用问题!
修改zookeeper端口号为2191(zk02:2192 | zk03:2193)
最后一行添加所有zookeeper的ip和端口
server.1=127.0.0.1:2991:3991
server.2=127.0.0.1:2992:3992
server.3=127.0.0.1:2993:3993
5>在mydata目录下创建myId文件
在文件中写入的数字,表示几号服务器
6>启动集群
# vim zkstartup.sh
/zk01/bin/zkServer.sh start
/zk02/bin/zkServer.sh start
/zk03/bin/zkServer.sh start
:wq
# chmod +x startup.sh
./startup.sh
查看进程
# ps –ef |grep zookeeper
---------------------------
# vim zkstop.sh
/zk01/bin/zkServer.sh stop
/zk02/bin/zkServer.sh stop
/zk03/bin/zkServer.sh stop
:wq
# chmod +x zkstop.sh
./zkstop.sh
---------------------------
# vim zkstatus.sh
/zk01/bin/zkServer.sh status
/zk02/bin/zkServer.sh status
/zk03/bin/zkServer.sh status
:wq
# chmod +x zkstatus.sh
./zkstatus.sh
7>使用客户端链接集群
./zk01/bin/zkCli.sh -server 127.0.0.1:2191
8>查看节点状态
./zk01/bin/zkServer.sh status