作者: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());
    }
}
                    
                
                
            
        
浙公网安备 33010602011771号