ZooKeeper

一、ZK简介

   (1)什么是ZK

      

   (2)zk体系架构

   

  (3)数据模型、节点

    

    

Zookeeper 这种数据结构有如下这些特点:

      1. 每个子目录项如 NameService 都被称作为 znode,这个 znode 是它所在的路径唯一标识,如 Server1 这                  个 znode 的标识为 /NameService/Server1

      2. znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL(短暂的,相对应的就是PERSISTENT持久的) 类型的目录节点不能有子节点目录

          四种类型的节点:

               PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;

               PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;

               EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;

               EPHEMERAL_SEQUENTIAL:临时自动编号节点

      3. znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据(类              式Hbase)

      4. znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除

      5. znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的,后面在典型的应用场景中会有实例介绍

   

  (4)Watches

     


二、ZK流行的应用场景(六大功能)

    1、统一命名服务(Name Service

         分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,既对人友好又不会重复。

         Name Service 已经是 Zookeeper 内置的功能,你只要调用 Zookeeper 的 API 就能实现。如调用 create 接口就可以很容易创建一个目录节点。

     2、配置管理(Configuration Management

        配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台 PC Server 运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。

       像这样的配置信息完全可以交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中

    

下面写个程序来演示zk配置管理的功能:

SetConfig.java:

package zookeeper;

import java.io.IOException;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class SetConfig {
	public static String url="192.168.8.91:2181";//连接服务器,2181是端口号,服务器会监视此端口
	private final static String root="/myConf";
	
	//数据库连接URL,username,password
	private final static String urlNode=root+"/url";
	private final static String usernameNode=root+"/username";
	private final static String passwordNode=root+"/password";
	
	//在cli界面这样访问权限:addauth digest password /myConf
	private final static String auth_type="digest";
	private final static String auth_password="password";
	
	public static void main(String[] args) throws IOException, InterruptedException, KeeperException
	{
		ZooKeeper zk=new ZooKeeper(url, 3000, new Watcher(){

			@Override
			public void process(WatchedEvent event) {
				System.out.println("触发了事件:"+event.getType());
			}			
		});
		
		while(ZooKeeper.States.CONNECTED!=zk.getState())
		{
			Thread.sleep(3000);
		}
		//加密操作,对一个session下创建的path进行加密,通常和CREATOR_ALL_ACL一起用
		zk.addAuthInfo(auth_type, auth_password.getBytes());
		
		if(zk.exists(root, true)==null)//判断某个path是否存在,并设置是否监控这个目录节点
		{
			zk.create(root, "root".getBytes(), Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
		}
		if(zk.exists(urlNode, true)==null)
		{
			zk.create(urlNode, "202.101.1.1".getBytes(),Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
		}
		if(zk.exists(usernameNode, true)==null)
		{
			zk.create(usernameNode, "wangfan".getBytes(),Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
		}
		if(zk.exists(passwordNode,true)==null)
		{
			zk.create(passwordNode, "michael".getBytes(),Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
		}
		zk.close();
	}
}


Client.java
package zookeeper;

import java.io.IOException;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

public class Client implements Watcher{
	public static String url="192.168.8.91:2181";//连接服务器,2181是端口号,服务器会监视此端口
    
	private final static String root="/myConf";
	
	//数据库连接URL,username,password
	private  String urlNode=root+"/url";
	private  String usernameNode=root+"/username";
	private  String passwordNode=root+"/password";
	
	private final static String auth_type="digest";
	private final static String auth_password="password";
	
	private String urlString;
	private String username;
	private String password;
	
	ZooKeeper zk=null;
	
	public String getUrlString() {
		return urlString;
	}
	public void setUrlString(String urlString) {
		this.urlString = urlString;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	public void initValue() throws KeeperException, InterruptedException
	{
		urlString=new String(zk.getData(urlNode, false, null));//url变化不会触发事件,getData()方法是获取这个path对应的目录节点存储的数据,同时还可以设置是否监控这个目录节点数据的状态
		username=new String(zk.getData(usernameNode, true, null));
		password=new String(zk.getData(passwordNode, true, null));
	}
	
	public ZooKeeper getZK() throws IOException, InterruptedException
	{
		zk=new ZooKeeper(url,3000,this);
		zk.addAuthInfo(auth_type, auth_password.getBytes());
		while(ZooKeeper.States.CONNECTED!=zk.getState())
		{
			Thread.sleep(3000);
		}
		return zk;
	}
	
	@Override
	public void process(WatchedEvent event) {
		// TODO 自动生成的方法存根
		if(event.getType()==Watcher.Event.EventType.None)
		{
			System.out.println("连接服务器成功!");
		}
		else if(event.getType()==Watcher.Event.EventType.NodeCreated)
		{
			System.out.println("节点创建成功!");
		}
		else if(event.getType()==Watcher.Event.EventType.NodeChildrenChanged)
		{
			System.out.println("子节点更新成功!");
			//读取新的配置
			try {
				initValue();
			} catch (KeeperException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
		else if(event.getType()==Watcher.Event.EventType.NodeDataChanged)
		{
			System.out.println("节点更新成功!");
			//读取新的配置
			try {
				initValue();
			} catch (KeeperException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
		else if(event.getType()==Watcher.Event.EventType.NodeDeleted)
		{
			System.out.println("节点删除成功!");
		}
	}
	
	
	
	
	public static void main(String[] args) throws IOException, InterruptedException, KeeperException
	{
		Client zkTest=new Client();
		ZooKeeper zk=zkTest.getZK();
		zkTest.initValue();
		int i=0;
		while(true)
		{
			System.out.println(zkTest.getUrlString());
			System.out.println(zkTest.getUsername());
			System.out.println(zkTest.getPassword());
			System.out.println("-----------------------------------------");
			Thread.sleep(10000);
			i++;
			if(i==10)
				break;
		}
		zk.close();
	}
	
}
运行:

     (1)首先运行setConfig.java,创建节点

     (2)运行Client.java

          打开cli,命令如下,改变username的值:

       
      同时发现客户端也跟着变化:

      

    当改变url的值得时候,发现并没有变化,因为getData的时候并没有监控这个目录节点数据的状态

 

  3、集群管理

      Zookeeper 能够很容易的实现集群管理的功能,如有多台 Server 组成一个服务集群,那么必须要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让“总管”知道。

      Zookeeper 不仅能够帮你维护当前的集群中机器的服务状态,而且能够帮你选出一个“总管”,让这个总管来管理集群,这就是 Zookeeper 的另一个功能 Leader Election(Leader选举)。

      它们的实现方式都是在 Zookeeper 上创建一个 EPHEMERAL 类型的目录节点,然后每个 Server 在它们创建目录节点的父目录节点上调用 getChildren(String path, boolean watch) 方法并设置 watch 为 true,由于是 EPHEMERAL 目录节点,当创建它的 Server 死去,这个目录节点也随之被删除,所以 Children 将会变化,这时 getChildren上的 Watch 将会被调用,所以其它 Server 就知道已经有某台 Server 死去了。新增 Server 也是同样的原理。

       Zookeeper 如何实现 Leader Election,也就是选出一个 Master Server。和前面的一样每台 Server 创建一个 EPHEMERAL 目录节点,不同的是它还是一个 SEQUENTIAL 目录节点,所以它是个 EPHEMERAL_SEQUENTIAL(短暂有次序) 目录节点。之所以它是 EPHEMERAL_SEQUENTIAL 目录节点,是因为我们可以给每台 Server 编号,我们可以选择当前是最小编号的 Server 为 Master,假如这个最小编号的 Server 死去,由于是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,所以当前的节点列表中又出现一个最小编号的节点,我们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。



   4、分布式锁

          

 5、队列管理

     Zookeeper 可以处理两种类型的队列:

          1. 当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。

          2. 队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型

     同步队列用 Zookeeper 实现的实现思路如下:

         创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 /synchronizing 目录的所有目录节点,也就是member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start


   FIFO 队列用 Zookeeper 实现思路如下:

        实现的思路也非常简单,就是在特定的目录下创建 PERSISTENT_SEQUENTIAL 类型的子目录 /queue_i,这样就能保证所有成员加入队列时都是有编号的,出队列时通过 getChildren( ) 方法可以返回当前所有的队列中的元素,然后消费其中最小的一个,这样就能保证 FIFO。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值