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来解决

10.hadoop HA原理

posted @ 2021-01-16 14:04  凯尔哥  阅读(384)  评论(0编辑  收藏  举报