zookeeper系列(三)zookeeper的使用--开源客户端

作者:leesf    掌控之中,才会成功;掌控之外,注定失败, 原创博客地址:http://www.cnblogs.com/leesf456/ 奇文共欣赏,大家共同学习进步。

一、前言

  上一篇博客已经介绍了如何使用Zookeeper提供的原生态Java API进行操作,本篇博文主要讲解如何通过开源客户端来进行操作。

二、ZkClient

  ZkClient是在Zookeeper原声API接口之上进行了包装,是一个更易用的Zookeeper客户端,其内部还实现了诸如Session超时重连、Watcher反复注册等功能。

2.1 添加依赖,使用maven管理直接添加配置文件即可

  在pom.xml文件中添加如下内容即可。

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.2</version>
</dependency>

2.2 创建会话

  使用ZkClient可以轻松的创建会话,连接到服务端

package com.hust.grid.leesf.zkClient;

import java.io.IOException;
import org.I0Itec.zkclient.ZkClient;

/**
 * 使用ZkClient创建会话,连接到服务端
 */
public class Create_Session_Sample {

	//会话超时时间
	private static final int SESSION_TIMEOUT = 5000;
	//ZkClient的实例对象
	private static ZkClient zkClient = null;
	/**
	 * 连接服务器,创建一个会话
	 * @param host 127.0.0.1:2181
	 */
	public void connect(String host){
		zkClient = new ZkClient(host, SESSION_TIMEOUT);
        System.out.println("ZooKeeper session established");
	}
	
	public static void main(String[] args) throws IOException, InterruptedException {
		Create_Session_Sample createSessionSample = new Create_Session_Sample();
		createSessionSample.connect("127.0.0.1:2181");
    }
}

运行结果:结果表明已经成功创建会话。

ZooKeeper session established.

2.3 创建节点

  ZkClient提供了递归创建节点的接口,即其帮助开发者完成父节点的创建,再创建子节点。

package com.hust.grid.leesf.zkClient;

import java.io.IOException;
import org.I0Itec.zkclient.ZkClient;
/**
 * ZkClient提供了递归创建节点的接口,即其帮助开发者完成父节点的创建,再创建子节点
 */
public class Create_Node_Sample {
	//会话超时时间
	private static final int SESSION_TIMEOUT = 5000;
	//ZkClient的实例对象
	private static ZkClient zkClient = null;
	/**
	 * 连接服务器,创建一个会话
	 * @param host 127.0.0.1:2181
	 */
	public void connect(String host){
		zkClient = new ZkClient(host, SESSION_TIMEOUT);
        System.out.println("ZooKeeper session established");
	}
	
	public static void main(String[] args) throws IOException, InterruptedException {
		Create_Session_Sample createSessionSample = new Create_Session_Sample();
		createSessionSample.connect("127.0.0.1:2181");
		String path = "/zk-book/c1";
		//创建父子节点
        zkClient.createPersistent(path, true);
        System.out.println("success create znode.");
    }
}

运行结果:

success create znode.

结果表明已经成功创建了节点,值得注意的是,在原生态接口中是无法创建成功的(父节点不存在),但是通过ZkClient可以递归的先创建父节点,再创建子节点。

2.4 删除节点

  ZkClient提供了递归删除节点的接口,即其帮助开发者先删除所有子节点(存在),再删除父节点

package com.hust.grid.leesf.zkClient;

import java.io.IOException;
import org.I0Itec.zkclient.ZkClient;
/**
 * ZkClient提供了递归删除节点的接口,即其帮助开发者先删除所有子节点(存在),再删除父节点
 */
public class Del_Data_Sample {
	//会话超时时间
	private static final int SESSION_TIMEOUT = 5000;
	//ZkClient的实例对象
	private static ZkClient zkClient = null;
	/**
	 * 连接服务器,创建一个会话
	 * @param host 127.0.0.1:2181
	 */
	public void connect(String host){
		zkClient = new ZkClient(host, SESSION_TIMEOUT);
        System.out.println("ZooKeeper session established");
	}
	
	public static void main(String[] args) throws IOException, InterruptedException {
		Del_Data_Sample delDataSample = new Del_Data_Sample();
		delDataSample.connect("127.0.0.1:2181");
		String path = "/zk-book";
        zkClient.createPersistent(path, "");
        zkClient.createPersistent(path + "/c1", "");
        System.out.println("success create znode.");
        zkClient.deleteRecursive(path);
        System.out.println("success delete znode.");
    }
	
}

  运行结果:结果表明ZkClient可直接删除带子节点的父节点,因为其底层先删除其所有子节点,然后再删除父节点。

  ZooKeeper session established
  success create znode.
  success delete znode.

2.5 获取子节点

package com.hust.grid.leesf.zkClient;

import java.util.List;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;

public class Get_Children_Sample {
	//会话超时时间
	private static final int SESSION_TIMEOUT = 5000;
	//ZkClient的实例对象
	private static ZkClient zkClient = null;
	/**
	 * 连接服务器,创建一个会话
	 * @param host 127.0.0.1:2181
	 */
	public void connect(String host){
		zkClient = new ZkClient(host, SESSION_TIMEOUT);
        System.out.println("ZooKeeper session established");
	}
	
	public static void main(String[] args) throws InterruptedException {
		Get_Children_Sample getChildrenSample = new Get_Children_Sample();
		getChildrenSample.connect("127.0.0.1:2181");
		String path = "/zk-book";
		zkClient.subscribeChildChanges(path, new IZkChildListener() {
			/**
			 * 子节点的路径被变更时回调此方法
			 */
			public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
				System.out.println(parentPath + " 's child changed, currentChilds:" + currentChilds);
		    }
		});
		zkClient.createPersistent(path);
        Thread.sleep(1000);
        zkClient.createPersistent(path + "/c1");
        Thread.sleep(1000);
        zkClient.delete(path + "/c1");
        Thread.sleep(1000);
        zkClient.delete(path);
        Thread.sleep(Integer.MAX_VALUE);
	}
}

运行结果:

/zk-book 's child changed, currentChilds:[]
/zk-book 's child changed, currentChilds:[c1]
/zk-book 's child changed, currentChilds:[]
/zk-book 's child changed, currentChilds:null

结果表明:

  客户端可以对一个不存在的节点进行子节点变更的监听;

  一旦客户端对一个节点注册了子节点列表变更监听之后,那么当该节点的子节点列表发生变更时,服务端都会通知客户端,并将最新的子节点列表发送给客户端,该节点本身的创建或删除也会通知到客户端;(给节点注册了儿子节点的监听事件,当子节点或本节点变动时都会通知客户端,冰倩返回新的节点列表)

 2.6获取节点的数据,当订阅节点数据变动时出发事件

package com.hust.grid.leesf.zkClient;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

public class Get_Data_Sample {
	//会话超时时间
	private static final int SESSION_TIMEOUT = 5000;
	//ZkClient的实例对象
	private static ZkClient zkClient = null;
	/**
	 * 连接服务器,创建一个会话
	 * @param host 127.0.0.1:2181
	 */
	public void connect(String host){
		zkClient = new ZkClient(host, SESSION_TIMEOUT);
        System.out.println("ZooKeeper session established");
	}
	
	public static void main(String[] args) throws InterruptedException {
		Get_Data_Sample getDataSample = new Get_Data_Sample();
		getDataSample.connect("127.0.0.1:2181");
		String path = "/zk-book";
		//创建一个临时节点
		zkClient.createEphemeral(path, "123");
		//节点数据变动时订阅监控
		zkClient.subscribeDataChanges(path, new IZkDataListener() {
            public void handleDataDeleted(String dataPath) throws Exception {
                System.out.println("Node " + dataPath + " deleted.");
            }
            public void handleDataChange(String dataPath, Object data) throws Exception {
                System.out.println("Node " + dataPath + " changed, new data: " + data);
            }
        });
	//获取path节点的数据
        System.out.println(zkClient.readData(path));
        //修改path节点的数据
        zkClient.writeData(path, "456");
        Thread.sleep(1000);
        //删除path节点的数据
        zkClient.delete(path);
        Thread.sleep(Integer.MAX_VALUE);
	}
}

2.7 检测节点是否存在,直接使用客户端检测节点是否存在,结果返回false不存在,true存在;

public class Exist_Node_Sample {
    public static void main(String[] args) throws Exception {
        String path = "/zk-book";
        ZkClient zkClient = new ZkClient("127.0.0.1:2181", 2000);
        System.out.println("Node " + path + " exists " + zkClient.exists(path));
    }

三、Curator客户端

  Curator解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连,反复注册Watcher和NodeExistsException异常等,现已成为Apache的顶级项目。

3.1 添加依赖

  使用maven管理时,在pom.xml文件中添加如下内容即可

<!-- https://mvnrepository.com/artifact/org.apache.curator/apache-curator -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.4.2</version>
        </dependency>

  

3.2 创建会话

  Curator除了使用一般方法创建会话外,还可以使用fluent风格进行创建;

package com.hust.grid.leesf.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
/**
 * 使用Curator客户端,创建会话
 * @author songzl
 *
 */
public class Create_Session_Sample {

	public static void main(String[] args) throws Exception {
	//重试策略:重试时间每间隔1000毫秒,最大重试次数3
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        //第一种方式新建客户端:服务端ip和端口,session超时时间,连接的超时时间,重试策略实例
        CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", 5000, 3000, retryPolicy);
        //大多数的方法在客户端启动之后才能工作
        client.start();
        System.out.println("Zookeeper session1 established. ");
        //第二种方式新建客户端:这里设置的“base”被作为根路径使用
        CuratorFramework client1 = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
                .sessionTimeoutMs(5000).retryPolicy(retryPolicy).namespace("base").build();
        client1.start();
        System.out.println("Zookeeper session2 established. ");        
    }
}

注意:值得注意的是session2会话含有隔离命名空间,即客户端对Zookeeper上数据节点的任何操作都是相对/base目录进行的,这有利于实现不同的Zookeeper的业务之间的隔离。

3.3 创建节点

  通过使用Fluent风格的接口,开发人员可以进行自由组合来完成各种类型节点的创建。  

package com.hust.grid.leesf.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;

public class Create_Node_Sample {
	//服务端ip和端口号
	private String host = "127.0.0.1:2181";
	//session超时时间
	private static final int sessionTimeOut = 5000;
	//连接的超时时间
	private static final int connectTimeOut = 3000;
	//初始化Curator客户端
	private static CuratorFramework client = null;
	//重试策略:重试时间每间隔1000毫秒,最大重试次数3
    private RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
	
    public void getCuratorFrameworkByNewClient(){
    	//第一种方式新建客户端:服务端ip和端口,session超时时间,连接的超时时间,重试策略实例
        client = CuratorFrameworkFactory.newClient(host, sessionTimeOut, connectTimeOut, retryPolicy);
        //大多数的方法在客户端启动之后才能工作
        client.start();
    }
    
    public void getCuratorFrameworkByBuilder(){
    	//第二种方式新建客户端:这里设置的“base”被作为此连接的根路径使用
//        client = CuratorFrameworkFactory.builder().connectString(host)
//                .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).namespace("base").build();
        //不设置namespace
        client = CuratorFrameworkFactory.builder().connectString(host)
                .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).build();
        client.start();
    }
    
    public static void main(String[] args) throws Exception {
    	String path = "/zk-book/c1";
    	Create_Node_Sample createNodeSample = new Create_Node_Sample();
    	createNodeSample.getCuratorFrameworkByBuilder();
    	client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path, "init".getBytes());
    	System.out.println("success create znode: " + path);
	}
    
}

3.4节点的增、删、改、查

package com.hust.grid.leesf.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

public class Create_Node_Sample {
	//服务端ip和端口号
	private String host = "127.0.0.1:2181";
	//session超时时间
	private static final int sessionTimeOut = 5000;
	//连接的超时时间
	private static final int connectTimeOut = 3000;
	//初始化Curator客户端
	private static CuratorFramework client = null;
	//重试策略:重试时间每间隔1000毫秒,最大重试次数3
    private RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
	
    public void getCuratorFrameworkByNewClient(){
    	//第一种方式新建客户端:服务端ip和端口,session超时时间,连接的超时时间,重试策略实例
        client = CuratorFrameworkFactory.newClient(host, sessionTimeOut, connectTimeOut, retryPolicy);
        //大多数的方法在客户端启动之后才能工作
        client.start();
    }
    
    public void getCuratorFrameworkByBuilder(){
    	//第二种方式新建客户端:这里设置的“base”被作为此连接的根路径使用
//        client = CuratorFrameworkFactory.builder().connectString(host)
//                .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).namespace("base").build();
        //不设置namespace
        client = CuratorFrameworkFactory.builder().connectString(host)
                .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).build();
        client.start();
    }
    /**
     * 创建节点
     * @param path
     */
    public void createNode(String path){
    	try {
    		Stat stat = client.checkExists().forPath(path);
			client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path, "init".getBytes());
			String context = new String(client.getData().storingStatIn(stat).forPath(path));
			System.out.println("success create znode: " + path + "节点内容为:"+context);
			Thread.sleep(Integer.MAX_VALUE);//创建成功后需要阻塞一下线程保持回话
		} catch (Exception e) {
			System.out.println("fail create znode: " + path);
		}
    }
    /**
     * 更新节点数据
     * @param path
     */
    public void updateNode(String path){
    	try {
    	   Stat stat = client.checkExists().forPath(path);
    	   System.out.println("初始的版本号:"+stat.getVersion());
           Stat stat1 = client.setData().withVersion(stat.getVersion()).forPath(path,"songzl".getBytes());
           System.out.println("更新后的版本号:"+stat1.getVersion());
           String context = new String(client.getData().storingStatIn(stat).forPath(path));
           System.out.println("success set node data  路径为:" +path+ "更新的内容: " + context);
        } catch (Exception e) {
            System.out.println("Fail set node data " + e.getMessage());
        }
    }
    /**
     * 删除节点数据
     * @param path
     */
    public void deleteNode(String path){
        try {
        	Stat stat = client.checkExists().forPath(path);
			client.delete().deletingChildrenIfNeeded().withVersion(stat.getVersion()).forPath(path);
			System.out.println("success delete znode " + path);
		} catch (Exception e) {
			System.out.println("fail delete znode " + path);
		}
    }
    /**
     * 获取节点的数据
     * @param path
     */
    public void getNode(String path){
		try {
			Stat stat = client.checkExists().forPath(path);  
			System.out.println(stat); 
			String context = new String(client.getData().storingStatIn(stat).forPath(path));
			System.out.println("success get node data:"+context);
		} catch (Exception e) {
			System.out.println("fail get node data");
		}
    }
    
    public static void main(String[] args) throws Exception {
    	String path = "/zk-book/c1";
    	Create_Node_Sample createNodeSample = new Create_Node_Sample();
    	createNodeSample.getCuratorFrameworkByBuilder();
    	createNodeSample.createNode(path);
	}
    
}

总结:

方法名描述
create() 开始创建操作, 可以调用额外的方法(比如方式mode 或者后台执行background) 并在最后调用forPath()指定要操作的ZNode
delete() 开始删除操作. 可以调用额外的方法(版本或者后台处理version or background)并在最后调用forPath()指定要操作的ZNode
checkExists() 开始检查ZNode是否存在的操作. 可以调用额外的方法(监控或者后台处理)并在最后调用forPath()指定要操作的ZNode
getData() 开始获得ZNode节点数据的操作. 可以调用额外的方法(监控、后台处理或者获取状态watch, background or get stat) 并在最后调用forPath()指定要操作的ZNode
setData() 开始设置ZNode节点数据的操作. 可以调用额外的方法(版本或者后台处理) 并在最后调用forPath()指定要操作的ZNode
getChildren() 开始获得ZNode的子节点列表。 以调用额外的方法(监控、后台处理或者获取状态watch, background or get stat) 并在最后调用forPath()指定要操作的ZNode
inTransaction() 开始是原子ZooKeeper事务. 可以复合create, setData, check, and/or delete 等操作然后调用commit()作为一个原子操作提交

 3.5 异步接口

  如同Zookeeper原生API提供了异步接口,Curator也提供了异步接口。在Zookeeper中,所有的异步通知事件处理都是由EventThread这个线程来处理的,EventThread线程用于串行处理所有的事件通知,其可以保证对事件处理的顺序性,但是一旦碰上复杂的处理单元,会消耗过长的处理时间,从而影响其他事件的处理,Curator允许用户传入Executor实例,这样可以将比较复杂的事件处理放到一个专门的线程池中去。

package com.hust.grid.leesf.curator;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;

public class Create_Node_Background_Sample {

	//服务端ip和端口号
	private String host = "127.0.0.1:2181";
	//session超时时间
	private static final int sessionTimeOut = 5000;
	//连接的超时时间
	private static final int connectTimeOut = 3000;
	//初始化Curator客户端
	private static CuratorFramework client = null;
	//重试策略:重试时间每间隔1000毫秒,最大重试次数3
    private RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    //对执行中的线程进行管理,等待线程完成某些操作后,再对此线程做处理(起到过河拆桥、卸磨杀驴的作用)
    static CountDownLatch semaphore = new CountDownLatch(2);
    //创建一个线程池,此线程池共享队列中的任务,直到队列中所有任务处理完(只要队列中有任务,就不停不休的执行,除非手动杀死线程)
    static ExecutorService tp = Executors.newFixedThreadPool(2);

    //第一种方式新建客户端:服务端ip和端口,session超时时间,连接的超时时间,重试策略实例
    public void getCuratorFrameworkByNewClient(){
        client = CuratorFrameworkFactory.newClient(host, sessionTimeOut, connectTimeOut, retryPolicy);
        //大多数的方法在客户端启动之后才能工作
        client.start();
    }
    
    //第二种方式新建客户端:这里设置的“base”被作为此连接的根路径使用
    public void getCuratorFrameworkByBuilder(){
//	        client = CuratorFrameworkFactory.builder().connectString(host)
//	                .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).namespace("base").build();
        //不设置namespace
        client = CuratorFrameworkFactory.builder().connectString(host)
                .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).build();
        client.start();
    }
	
    public static void main(String[] args) throws Exception {
    	Create_Node_Background_Sample createNodeBackgroundSample = new Create_Node_Background_Sample();
    	createNodeBackgroundSample.getCuratorFrameworkByBuilder();
    	System.out.println("Main thread: " + Thread.currentThread().getName());
    	String path = "/zk-book";
    	//方法一:此方法使用一个线程池当做后台执行器,可以将比较复杂的事件处理放到一个专门的线程池中去
    	client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
            public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                System.out.println("使用专门的线程池执行:event[code: " + event.getResultCode() + ", type: " + event.getType() + "]" + ", Thread of processResult: " + Thread.currentThread().getName());
                semaphore.countDown();
            }
        }, tp).forPath(path, "init".getBytes());
    	//方法二:普通的异步回调函数创建一个临时节点,EventThread线程用于串行处理所有的事件通知,其可以保证对事件处理的顺序性,但是一旦碰上复杂的处理单元,会消耗过长的处理时间
    	client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
            public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                System.out.println("不适用线程池执行:event[code: " + event.getResultCode() + ", type: " + event.getType() + "]" + ", Thread of processResult: " + Thread.currentThread().getName());
                semaphore.countDown();
            }
        }).forPath(path, "init".getBytes());
    	
        semaphore.await();
        tp.shutdown();
	}
}

3.6 Curator除了提供很便利的API,还提供了一些典型的应用场景,开发人员可以使用参考更好的理解如何使用Zookeeper客户端,所有的都在recipes包中,只需要在pom.xml中添加如下依赖即可。

<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-recipes</artifactId>
   <version>2.4.2</version>
</dependency>

3.6.1 curator-recipes对节点的监听一下是我代码测试的结论,若有误,请大家指正

 NodeCache将节点数据保存到本地缓存中,给这些数据注册一个监听器,当被新增、更新时触发监听器,(我测试时发现删除时监听器不触发)

 NodeCache节点缓存不是线程安全的,在不同步的情况下不能保持同步;当多线程更新数据时必须使用版本号,避免错误覆盖其他进程数据;

 NodeCache注册一个监听器,若session不超时、监听器没有被移除,此监听器一直有效并且可以重复使用(多次更新节点数据均被成功触发);

 NodeCache也可以同时注册多个监听器,session不超时、监听器没有被移除,所有的监听器也都是可以正常被触发;

package com.hust.grid.leesf.curator;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
/**
 * NodeCache将节点数据保存到本地缓存中,给这些数据注册一个监听器,当被增删改时触发监听器;
 * 需要注意:这个缓存不是线程安全的,不能保证同步;当更新数据时必须使用版本号,避免数据错误覆盖;
 * NodeCache的监听器可以重复添加多个,并且都会触发;也可以移除,若不移除则可一直使用;
 */
public class NodeCache_Sample {

	//服务端ip和端口号
	private String host = "127.0.0.1:2181";
	//session超时时间
	private static final int sessionTimeOut = 5000;
	//连接的超时时间
	private static final int connectTimeOut = 3000;
	//初始化Curator客户端
	private static CuratorFramework client = null;
	//重试策略:重试时间每间隔1000毫秒,最大重试次数3
    private RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    //对执行中的线程进行管理,等待线程完成某些操作后,再对此线程做处理(起到过河拆桥、卸磨杀驴的作用)
    static CountDownLatch semaphore = new CountDownLatch(1);
    //创建一个线程池,此线程池共享队列中的任务,直到队列中所有任务处理完(只要队列中有任务,就不停不休的执行,除非手动杀死线程)
    static ExecutorService tp = Executors.newFixedThreadPool(1);

    //第一种方式新建客户端:服务端ip和端口,session超时时间,连接的超时时间,重试策略实例
    public void getCuratorFrameworkByNewClient(){
        client = CuratorFrameworkFactory.newClient(host, sessionTimeOut, connectTimeOut, retryPolicy);
        //大多数的方法在客户端启动之后才能工作
        client.start();
    }
    
    //第二种方式新建客户端:这里设置的“base”被作为此连接的根路径使用
    public void getCuratorFrameworkByBuilder(){
//		        client = CuratorFrameworkFactory.builder().connectString(host)
//		                .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).namespace("base").build();
        //不设置namespace
        client = CuratorFrameworkFactory.builder().connectString(host)
                .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).build();
        client.start();
    }
    
    /**
     * path路径的节点数据放到NodeCache的本地缓存中,并且给nodeCache添加一个监听;
     * 不使用单独的线程池处理
     * @param path 
     * @throws Exception
     */
    public void nodeCacheAddListener(String path) throws Exception{
    	final NodeCache cache = new NodeCache(client, path, false);
        cache.start(true);
        cache.getListenable().addListener(new NodeCacheListener() {
        	public void nodeChanged() throws Exception {
        		System.out.println("Node data update, new data: " + new String(cache.getCurrentData().getData())+",线程名字:"+Thread.currentThread().getName());
        	}
        });
    }
    //封装nodeCacheAddListener方法
    public void warpNodeCacheAddListener(String path) throws Exception{
    	getCuratorFrameworkByBuilder();//实例化client
    	//path节点的缓存,创建一个监听器,不使用线程池,监听器可以注册多个
    	nodeCacheAddListener(path);
    	//创建时也调用监听器(之前还没有path节点,此时才创建,但是缓存节点的监听器被触发了)
    	client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path, "init".getBytes());
    	//更行path节点数据时,缓存节点的监听器被触发了
    	client.setData().forPath(path, "songzl".getBytes());
    	//删除path节点时,不触发监听(因为节点给删除了,监听也就被移除了,还调用个毛线)
    	client.delete().deletingChildrenIfNeeded().forPath(path);
    }
    /**
     * path路径的节点数据放到NodeCache的本地缓存中,并且给nodeCache添加一个监听;
     * 使用单独的线程池处理
     * @param path 
     * @throws Exception
     */
    public void nodeCacheAddListenerExecutor(String path) throws Exception{
    	final NodeCache cache = new NodeCache(client, path, false);
        cache.start(true);
    	cache.getListenable().addListener(new NodeCacheListener() {
        	public void nodeChanged() throws Exception {
                System.out.println("Node data update, new data: " + new String(cache.getCurrentData().getData())+",线程名字:"+Thread.currentThread().getName());
                semaphore.countDown();
        	}
        },tp);
    }
    //封装nodeCacheAddListenerExecutor方法
    public void warpNodeCacheAddListenerExecutor(String path) throws Exception{
    	getCuratorFrameworkByBuilder();//实例化client
    	//path节点的缓存,创建一个监听器,使用线程池,监听器可以注册多个
    	nodeCacheAddListenerExecutor(path);
    	//创建时也调用监听器(之前还没有path节点,此时才创建,但是缓存节点的监听器被触发了)
    	client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path, "init".getBytes());
    	//更行path节点数据时,缓存节点的监听器被触发了
        client.setData().forPath(path, "songzl".getBytes());
        //删除path节点时,不触发监听(因为节点给删除了,监听也就被移除了,还调用个毛线)
        client.delete().deletingChildrenIfNeeded().forPath(path);
    	semaphore.await();
        tp.shutdown();
    }
    
    public static void main(String[] args) throws Exception {
    	String path = "/zk-book/nodecache";
    	//实例化client
    	NodeCache_Sample nodeCacheSample = new NodeCache_Sample();
    	nodeCacheSample.warpNodeCacheAddListener(path);
    	Thread.sleep(Integer.MAX_VALUE);
	}
}

3.6.2  curator-recipes对 子节点监听,3.6.1的节点性质子节点均具备,还有下面的几种特性;

给子节点添加的监听器,参数event提供了增删改的类型判断,因此字节点的增删改均会触发监听器;

给子节点注册监听器使用的path是父节点的绝对路径,当父节点路径下有子节点增改删时,触发子节点的监听器,操作父节点时不会触发;

在对子节点修改过于频繁时,若不阻塞线程,会丢失监听器调用次数(我用代码测试时发现总是少调用,原来是对子节点操作过于频繁,导致监听器调用次数丢失);

 /**
     * 给子节点添加监听器
     * @param path
     * @throws Exception
     */
    public void childNodeCacheAddListener(String path) throws Exception{
        PathChildrenCache cache = new PathChildrenCache(client, path, true);
        cache.start(StartMode.POST_INITIALIZED_EVENT);
        cache.getListenable().addListener(new PathChildrenCacheListener() {
			public void childEvent(CuratorFramework client,
					PathChildrenCacheEvent event) throws Exception {
				switch (event.getType()) {
                case CHILD_ADDED:
                    System.out.println("CHILD_ADDED," + event.getData().getPath());
                    break;
                case CHILD_UPDATED:
                    System.out.println("CHILD_UPDATED," + event.getData().getPath());
                    break;
                case CHILD_REMOVED:
                    System.out.println("CHILD_REMOVED," + event.getData().getPath());
                    break;
                default:
                    break;
                }
			}
        });
    }
    public static void main(String[] args) throws Exception {
    	String path = "/zk-demo";
    	//实例化client
    	NodeCache_Sample nodeCacheSample = new NodeCache_Sample();
    	nodeCacheSample.getCuratorFrameworkByBuilder();
    	nodeCacheSample.childNodeCacheAddListener(path);
    	client.create().withMode(CreateMode.PERSISTENT).forPath(path);
    	Thread.sleep(1000);
        client.create().withMode(CreateMode.PERSISTENT).forPath(path + "/c1");
        Thread.sleep(1000);//必须阻塞下线程,避免快速操作来不及触发监听器就被下一个覆盖
        client.setData().forPath(path + "/c1", "songzl".getBytes());
     System.out.println("第一次修改子节点内容:"+ new String(client.getData().forPath(path + "/c1"))); Thread.sleep(1000);//必须阻塞下线程,避免快速操作来不及触发监听器就被下一个覆盖 client.setData().forPath(path + "/c1", "wangxn".getBytes());
     System.out.println("第二次修改子节点内容"+ new String(client.getData().forPath(path + "/c1"))); Thread.sleep(1000);//必须阻塞下线程,避免快速操作来不及触发监听器就被下一个覆盖 client.delete().forPath(path + "/c1"); client.delete().forPath(path); }

打印结果:由下图可以明确得出结论

3.7 Master选举,这个是zookeeper的核心之一;

  借助Zookeeper,开发者可以很方便地实现Master选举功能,其大体思路如下:选择一个根节点,如/master_select,多台机器同时向该节点创建一个子节点/master_select/lock,利用Zookeeper特性,最终只有一台机器能够成功创建,成功的那台机器就是Master。

package com.hust.grid.leesf.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class Recipes_MasterSelect {
	//服务端ip和端口号
	private static String host = "127.0.0.1:2181";
	//重试策略:重试时间每间隔1000毫秒,最大重试次数3
    private static RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
	//Curator客户端
    static CuratorFramework client = CuratorFrameworkFactory.builder().connectString(host).retryPolicy(retryPolicy).build();
    //主服务路径
    static String master_path = "/curator_recipes_master_path";
    
    public static void main(String[] args) throws Exception {
        client.start();
        LeaderSelector selector = new LeaderSelector(client, master_path, new LeaderSelectorListenerAdapter() {
            public void takeLeadership(CuratorFramework client) throws Exception {
                System.out.println("成为Master角色");
                Thread.sleep(3000);//模拟Master的业务
                System.out.println("完成Master操作,释放Master权利");
            }
        });
        selector.autoRequeue();
        selector.start();
        Thread.sleep(Integer.MAX_VALUE);
    }
    
}

以上结果会反复循环,并且当一个应用程序完成Master逻辑后,另外一个应用程序的相应方法才会被调用,即当一个应用实例成为Master后,其他应用实例会进入等待,直到当前Master挂了或者推出后才会开始选举Master。

3.8 分布式锁也是zookeeper的核心之一(实现数据一致性的原理)

  为了保证数据的一致性,经常在程序的某个运行点需要进行同步控制。以流水号生成场景为例,普通的后台应用通常采用时间戳方式来生成流水号,但是在用户量非常大的情况下,可能会出现并发问题。

package com.hust.grid.leesf.curator;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
/**
 * zookeeper实现分布式锁
 */
public class Recipes_Lock {
	static String lock_path = "/curator_recipes_lock_path";
    static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
            .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();

    public static void main(String[] args) throws Exception {
        client.start();
        /**
         * InterProcessMutex是跨JVM的互斥锁,该锁是由zookeeper控制;
         * 重点是它实现了分布式锁,当不同服务器的进程对同一个节点操作时是安全的受锁控制的;
         * 并且此分布式锁是绝对公平的,用户都是按照请求的顺序获取互斥锁,依次执行;
         */
        final InterProcessMutex lock = new InterProcessMutex(client, lock_path);
        final CountDownLatch down = new CountDownLatch(1);
        for (int i = 0; i < 30; i++) {
            new Thread(new BuildOrderNo(lock,down)).start();
        }
        Thread.sleep(2000);//模拟生成订单前的其他业务,当操作完后开始生成订单
        down.countDown();
    }
}
/**
 * 生成订单号类
 * @author songzl
 *
 */
class BuildOrderNo implements Runnable{
	private InterProcessMutex lock;
	private CountDownLatch down;
	public BuildOrderNo(InterProcessMutex lock,CountDownLatch down){
		this.lock = lock;
		this.down = down;
	}
	public void run() {
        try {
            down.await();
            lock.acquire();//获取互斥锁,检测当前线程是否可以执行
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
            String orderNo = sdf.format(new Date());
            System.out.println("生成的订单号是 : " + orderNo+"子线程名字:"+Thread.currentThread().getName());
            //如果调用线程是获得它的线程,那么执行一个互斥锁。如果线程已经多次调用获取,当这个方法返回时,互斥锁仍然会被保留。
            lock.release();//释放互斥锁,具备检查功能
        } catch (Exception e) {
        	e.printStackTrace();
        }
    }
}

3.9分布式计数器

  分布式计数器的典型应用是统计系统的在线人数,借助Zookeeper也可以很方便实现分布式计数器功能:指定一个Zookeeper数据节点作为计数器,多个应用实例在分布式锁的控制下,通过更新节点的内容来实现计数功能。

package com.hust.grid.leesf.curator;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.atomic.AtomicValue;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;
/**
 * 分布式计数器
 */
public class Recipes_DistAtomicInt {
	static String distatomicint_path = "/curator_recipes_distatomicint_path";
    static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
            .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();

    public static void main(String[] args) throws Exception {
        client.start();
        /**
         * 创建一个增量的计数器;它首先尝试使用乐观锁定,如果失败就选择一个可选的互斥,对于乐观和互斥,可以使用重试策略重试增量。
         */
        DistributedAtomicInteger atomicInteger = new DistributedAtomicInteger(client, distatomicint_path,
                new RetryNTimes(3, 1000));
        AtomicValue<Integer> rc = atomicInteger.add(1);
        System.out.println(rc.preValue());
        System.out.println("Result: " + rc.succeeded());
        System.out.println(rc.postValue());
    }
}

 

原文地址:https://www.cnblogs.com/aoshicangqiong/p/7912576.html