zookeeper知识总结
zookeeper
1.Zookeeper是什么
直译:动物管理员
是一个开源的分布式服务框架,为分布式应用提供协调服务,用来解决分布式应用中的数据管理问题,如:配置管理、域名服务、分布式同步、集群管理等。
zookeeper总体架构
主从架构
- 客户端:负责读写发送请求 ,两种方式:java api zkCli
- 主:leader,负责提议和投票的发起,处理写请求。
- 从:follow,具有选举权;observer,观望者,在没有投票的情况下,和follow一样,不具有投票权利,分担整个集群的读压力。
相关概念
分布式与集群的区别
分布式:将一个大型应用的不同业务部署在不同的服务器上,解决高并发的问题。
比方说,用户业务(登录,注册。。。),商品业务(商品的搜索,查找。。。)分布在不同的服务器上
集群:将同一个业务部署在多台服务器上
比方说订单业务,避免高并发问题,可以将其部署在多台服务器上。
server.serverid=host:2888:3888
1.server范围:1-255
2.serverid不能重复,因此zookeeper集群最多安装255个节点
3.2888是通信端口,3888是选主端口
2.zookeeper shell
- zkCli.sh:进入客户端命令
- create -s -e path "":创建znode
- delete path:删除znode,只能删除空节点
- rmr path:级联删除的,空节点,非空节点都可以删除
- set path(节点路径) data(修改之后的内容):修改znode内容
- get path:查看节点内容
- ls path:查看节点列表
- stat path:查看当前节点的状态信息
返回的状态信息
# Create ZXID,表示节点被创建时的事务ID。
cZxid = 0x40000000d
# Create Time,表示节点创建时间。
ctime = Tue Mar 26 10:21:03 CST 2019
# Modified ZXID,表示节点最后一次被修改时的事务ID。一旦修改节点内容,这个值就会顺序递增。
mZxid = 0x40000000d
# Modified Time,表示节点最后一次被修改的时间。
mtime = Tue Mar 26 11:16:43 CST 2019
#表示该节点的子节点列表最后一次被修改时的事务 ID。只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新。
pZxid = 0x40000000d
# 子节点的版本号
cversion = 0
# 内容版本号
dataVersion = 1
# 权限版本号
aclVersion = 0
# 表示创建该临时节点时的会话 sessionID,如果是持久性节点那么值为 0
ephemeralOwner = 0x0
# 数据长度
dataLength = 11
# 直系子节点数
numChildren = 0
# 下面3个id之间的关系
cZxid mZxid pZxid
创建一个新节点的时候 cZxid mZxid pZxid 相同的
cZxid:创建节点的事件id 代表节点创建事件的顺序的,节点一旦创建,这个值不变了。
mZxid:修改节点内容的事件提交编号,一旦修改节点内容,这个值递增。
pZxid:子节点变化事件的提交编号
集群中的一个节点的 cZxid mZxid pZxid 值越大 代表这个机器中的数据越新。这三个值是全局递增的。
ephemeralOwner = 0x0 临时节点的owner
0x0 代表的是永久节点
ephemeralOwner = 0x169b7b166210001
sessionid = 0x169b7b166210001 当前客户端的会话id,每一个客户端不同,会话id就不同,当前客户端的标识
退出客户端,临时节点删除的时候,根据ephemeralOwner值删除对应客户端下的所有临时节点
示例
# 创建/test
[zk: localhost:2181(CONNECTED) 1] create /test ""
Created /test
# 查看/test节点的状态信息
[zk: localhost:2181(CONNECTED) 2] stat /test
cZxid = 0x80000006c
ctime = Sat Jan 16 10:22:24 CST 2021
mZxid = 0x80000006c
mtime = Sat Jan 16 10:22:24 CST 2021
pZxid = 0x80000006c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 0
# 修改/test节点的值
[zk: localhost:2181(CONNECTED) 3] set /test "haha"
cZxid = 0x80000006c
ctime = Sat Jan 16 10:22:24 CST 2021
# mZxid的值发生改变
mZxid = 0x80000006d
mtime = Sat Jan 16 10:23:02 CST 2021
pZxid = 0x80000006c
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
# 再次修改/test节点的值
[zk: localhost:2181(CONNECTED) 4] set /test "hahahehe"
cZxid = 0x80000006c
ctime = Sat Jan 16 10:22:24 CST 2021
# mZxid的值又发生改变
mZxid = 0x80000006e
mtime = Sat Jan 16 10:28:38 CST 2021
pZxid = 0x80000006c
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0
# 创建子节点
[zk: localhost:2181(CONNECTED) 5] create /test/01 ""
Created /test/01
# 再次查看节点信息
[zk: localhost:2181(CONNECTED) 6] stat /test
cZxid = 0x80000006c
ctime = Sat Jan 16 10:22:24 CST 2021
mZxid = 0x80000006e
mtime = Sat Jan 16 10:28:38 CST 2021
# 该值发生改变
pZxid = 0x80000006f
cversion = 1
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
# 子节点个数为1
numChildren = 1
# 修改子节点内容
[zk: localhost:2181(CONNECTED) 7] set /test/01 "xixi"
cZxid = 0x80000006f
ctime = Sat Jan 16 10:31:13 CST 2021
mZxid = 0x800000070
mtime = Sat Jan 16 10:34:04 CST 2021
pZxid = 0x80000006f
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
# 再次查看节点信息
[zk: localhost:2181(CONNECTED) 8] stat /test
cZxid = 0x80000006c
ctime = Sat Jan 16 10:22:24 CST 2021
mZxid = 0x80000006e
mtime = Sat Jan 16 10:28:38 CST 2021
# 该值没有发生改变
pZxid = 0x80000006f
cversion = 1
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 1
3.zookeeper 开源客户端
ZkClient
ZkClient是Github上一个开源的zookeeper客户端,在Zookeeper原生API接口之上进行了包装,是一个更易用的Zookeeper客户端,同时,zkClient在内部还实现了诸如Session超时重连、Watcher反复注册等功能。
开发步骤
-
添加依赖
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.2</version> </dependency>
-
创建会话
使用ZkClient可以轻松的创建会话,连接到服务端。
import java.io.IOException; import org.I0Itec.zkclient.ZkClient; public class CreateSession { /* 创建一个zkClient实例来进行连接 */ public static void main(String[] args) { ZkClient zkClient = new ZkClient("hadoop01:2181"); System.out.println("ZooKeeper session created."); } }
-
创建节点
import org.I0Itec.zkclient.ZkClient; public class Create_Node_Sample { public static void main(String[] args) { ZkClient zkClient = new ZkClient("hadoop01:2181"); System.out.println("ZooKeeper session established."); //createParents的值设置为true,可以递归创建节点 zkClient.createPersistent("/lg-zkClient/lg-c1",true); System.out.println("success create znode."); } }
-
删除节点
import org.I0Itec.zkclient.ZkClient; public class Del_Data_Sample { public static void main(String[] args) throws Exception { String path = "/lg-zkClient/lg-c1"; ZkClient zkClient = new ZkClient("hadoop:2181", 5000); zkClient.deleteRecursive(path); System.out.println("success delete znode."); } }
-
监听节点变化
import org.I0Itec.zkclient.IZkChildListener; import org.I0Itec.zkclient.ZkClient; import org.apache.zookeeper.client.ZooKeeperSaslClient; import java.util.List; /* 演示zkClient如何使用监听器 */ public class Get_Child_Change { public static void main(String[] args) throws InterruptedException { //获取到zkClient final ZkClient zkClient = new ZkClient("hadoop01:2181"); //zkClient对指定目录进行监听(不存在目录:/lg-client),指定收到通知之后的逻辑 //对/lag-client注册了监听器,监听器是一直监听 zkClient.subscribeChildChanges("/lg-client", new IZkChildListener() { //该方法是接收到通知之后的执行逻辑定义 public void handleChildChange(String path, List<String> childs) throws Exception { //打印节点信息 System.out.println(path + " childs changes ,current childs " + childs); } }); //使用zkClient创建节点,删除节点,验证监听器是否运行 zkClient.createPersistent("/lg-client"); Thread.sleep(1000); //只是为了方便观察结果数据 zkClient.createPersistent("/lg-client/c1"); Thread.sleep(1000); zkClient.delete("/lg-client/c1"); Thread.sleep(1000); zkClient.delete("/lg-client"); Thread.sleep(Integer.MAX_VALUE); /* 1 监听器可以对不存在的目录进行监听 2 监听目录下子节点发生改变,可以接收到通知,携带数据有子节点列表 3 监听目录创建和删除本身也会被监听到 */ } }
-
监听节点数据变化
import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; public class GetDataChange { public static void main(String[] args) throws InterruptedException { final ZkClient zkClient = new ZkClient("hadoop01:2181"); final boolean exists = zkClient.exists("/lg-client"); if (!exists) { zkClient.createEphemeral("/lg-client", "123"); } zkClient.subscribeDataChanges("/lg-client", new IZkDataListener() { public void handleDataChange(String path, Object data) throws Exception { System.out.println(path + "data changed, new data is " + data); } public void handleDataDeleted(String path) throws Exception { System.out.println(path + "is deleted!!!"); } }); zkClient.writeData("/lg-client", "new data"); Thread.sleep(1000); zkClient.delete("/lg-client"); Thread.sleep(Integer.MAX_VALUE); } } // /lg-clientdata changed, new data is new data // /lg-clientis deleted!!! // 可以监听节点的数据变化和节点的删除
4.zookeeper的组成
主要包括两部分:文件系统、通知机制
文件系统
1.Zookeeper维护一个类似Linux系统的数据结构,用于存储数据。所有的寻址都只能通过绝对路径。
2.数据模型结构是一种树形结构,由许多节点组成
3.每个节点叫做ZNode (Zookeeper Node),既不是文件,也不是目录,但是有他们的功能。
4.每个节点对应一个唯一路径,通过该路径来标识节点,如:/app1/p_2
5.每个节点只能存储大概1M的数据(储存配置文件)
6.zookeeper存储数据量越大,一致性越难维护。
节点类型
1.持久化目录节点:persistent
创建方法:create path data 必须指定data,没有的话给空。
客户端与服务器断开连接,该节点仍然存在
2.持久化顺序编号目录节点:persistent_sequential
创建方法:create -s path data
客户端与服务器断开连接,该节点仍然存在,此时节点会被顺序编号,如:000001,000002......
3.临时目录节点:ephemeral
创建方法:create -e path data
客户端与服务器断开连接,该节点会被删除
4.临时顺序编号目录节点:ephemeral_sequential
创建方法:create -e -s path data
客户端与服务器断开连接,该节点会被删除,此时节点会被顺序编号,如:000001,000002......
注意
1.有编号节点可以反复创建同名节点,无编号节点不可以。
2.临时节点下面不能创建子节点,永久节点可以。
3.有几个zookeeper节点,数据就保存几份。
4.每个节点只能存储大概1M的数据(储存配置文件)
监听机制
监控存储内容的变化情况,并及时做反应
监听事件
- nodecreated:节点是否创建了
- nodedeleted:节点是否删除了
- nodedatachanged:节点内容是否修改了
- nodechildrenchanged:节点下是否创建了子节点
如何监听
添加监听(shell)
- ls:查看某一个节点的子节点
- get:查看某一个节点的内容变化
触发监听(shell)
- create|delete|rmr
- set|delete
注意
- 一次监听只生效一次,要想重复生效需要重新添加
- 那个客户端添加的监听,反应给哪一个客户端
- zookeeper就是运用监听机制监听文件系统,当监听触发,通知客户端
5.zookeeper应用场景
命名服务
多个节点对同一个文件进行统一命名,原始的多个节点对同一个文件统一命名的时候。
将命名放在zk的一个znode上,其他的节点对这个名字感兴趣 ,就添加监听,一旦名字修改,就会触发监听。
集群管理
集群选主
依赖于zk内部的选主机制
集群节点的上下线服务
原本namenode知道datanode宕机需要10x3+2x5min=630s的时间,通过将需要上下线的节点存储在zk中,存储为临时节点,一旦下线,namenode可以添加监听,获取监听触发事件。
锁管理
写锁
- 进行写操作的锁
- 排他锁:只能允许一个线程获取锁 其他的线程都得等
- 临时无编号
读锁
- 进行读操作的锁
- 共享锁:所有线程通用一把锁
- 临时有编号
时序锁
队列管理
先进先出
配置文件管理
目标:监控某一配置文件目录下的子节点的个数和内容是否发生变化。实现代码如下:
监听端
public class ConfigServer {
static ZooKeeper zk = null;
static List<String> children = null;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//创建zookeeper对象
zk = new ZooKeeper("cdh01:2181,cdh02:2181,cdh03:2181", 6000, new Watcher() {
public void process(WatchedEvent event) {
//获取事件类型和发生的路径
String ePath = event.getPath();
EventType eType = event.getType();
//监听nodedatachanged
/*
* None (-1),
NodeCreated (1),
NodeDeleted (2),
NodeDataChanged (3),
NodeChildrenChanged (4);
*/
//监听节点数据发生改变
if (EventType.NodeDataChanged.equals(eType)) {
byte[] data;
//循环监听
try {
data = zk.getData(ePath, true, null);
System.out.println(ePath + "配置文件发生了修改,修改之后的内容为" + new String(data));
}catch (Exception e) {
e.printStackTrace();
}
}
//监听节点被删除
else if (EventType.NodeDeleted.equals(eType)) {
System.out.println(ePath + "配置文件被删除了!!!");
String pPath=ePath.substring(0, ePath.lastIndexOf("/"));
try {
//删除之后,需要重新获取到现在还有哪些子节点,并且添加监听
children = zk.getChildren(pPath, true);
System.out.println(children);
} catch (KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//监听节点个数增加,该事件返回的path是父节点,比如 /config
else if (EventType.NodeChildrenChanged.equals(eType)) {
try {
System.out.println(ePath);
//参数1表示的是父节点,新增节点之后也是要更新节点
List<String> newChildren = zk.getChildren(ePath, true);
//如果现在的子节点个数大于之前的子节点个数
if (newChildren.size()>children.size()) {
//调用方法求得新增节点名字
String node = getNewNode(newChildren,children);
//获取新增节点内容,并添加监听
byte[] newdata = zk.getData(ePath+"/"+node, true, null);
System.out.println("添加了一个配置文件,配置文件的名为"+node+",内容为"+new String(newdata));
//将变化之后的子节点更新
children = newChildren;
}
} catch (KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
//添加监听,需要添加监听的路径
String pPtath = "/config";
//得到该节点下所有的子节点
children = zk.getChildren(pPtath, true);
//循环遍历每一个子节点,添加监听
for (String string : children) {
System.out.println(string);
String cPath = pPtath+"/"+string;
//添加监听
zk.getData(cPath, true, null);
}
Thread.sleep(1000000);
}
//该方法用于获取新增节点的名字,参数1:新子节点 参数2:旧子节点
public static String getNewNode(List<String> newchildren, List<String> children) {
String resNode="";
for (String cd : newchildren) {
if (!children.contains(cd)) {
resNode = cd;
break;
}
}
return resNode;
}
}
触发端
public class ConfigClient {
public static ZooKeeper zk;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//创建zookeeper对象
zk = new ZooKeeper("cdh01:2181,cdh02:2181,cdh03:2181", 6000, null);
//触发监听1:修改配置文件内容
//zk.setData("/config/map.xml", "hello".getBytes(), -1);
//触发监听2:删除一个配置文件
//zk.delete("/config/map.xml", -1);
//触发监听3:新增一个配置文件
zk.create("/config/hadoop-site.xml", "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
6.zookeeper非全新集群选主
先统一逻辑时钟
逻辑时钟不统一,就重新投票
按照数据版本投票
数据版本(每个节点的zxid)大的获胜。如果zxid相同,再按照id投票。
按照id投票
myid大的获胜
最终获胜的节点肯定是数据版本高,id大的节点。
7.zookeeper数据同步
选主结束之后,非主节点就要开始同步主节点的数据
follower在更新数据的过程中,不对外提供服务。
数据同步流程:
- leader 等待 follower 连接
- follower 连接 leader,将最大的 zxid 发送给 leader
- leader将follower发送的zxid和自己最大的zxid对比,小于自己的则会通知follower更新。
- follower开始更新数据,不对外提供读写服务。
- follower更新完数据,会将状态修改为updated,又可以重新接受 client 的请求进行服务了。
8.zookeeper角色
leader:
- 集群的老大,负责提议的发起,有选举权和被选举权
- 可以接受客户端的读请求和写请求
follower:
- 集群的从节点,有选举权和被选举权
- 功能:只能接受读请求,接受到客户端的写请求,则将这个请求转发给leader,由leader进行写操作。
observer:
- 配置方法:
server.id=主机名:2888:3888:observer
- 没有选举权和被选举权,被剥夺政治权利的,不参与选举
- 处理读请求,不能处理写请求;如果接收到写请求,会将请求转发给leader;集群中的读的压力很大的时候,通常会配置几台 observer分担集群的读数据压力。
9.zookeeper的作用
解决分布式应用程序数据一致性问题。
完全分布式的hadoop存在主节点单点故障的问题,可以通过高可用的方式解决。但是这个时候又会出现两个问题。
第一个问题:两个NameNode的元数据如何同步?
zookeeper来解决多个节点数据一致性问题。active节点将原数据写到zookeeper,standby及时读取zookeeper的元数据。
第二个问题:active和standby节点的状态信息如何保持一致?即standby如何知道active节点的状态?
同样是zookeeper来解决