zookeeper的Java API、zkClient及curator的基本用法(分布式笔记)

Zookeeper JavaAPI的使用

1、scheme权限管理方案

    scheme对应于采用哪种方案来进行权限管理,zookeeper的scheme的分类如下:

  1. ip:它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段,也可以设置为某一个具体的ip
  2. Digest:最常用的权限控制模式,也更符合我们对权限控制的认识,其类似于"username:password"形式的权限标识进行权限配置
  3. world:开放式的权限控制模式,数据节点的访问权限对所有用户开放。 world:anyone
  4. super:超级用户,可以对zookeeper上的数据节点进行操作

2、连接状态

KeeperStat.Expired:在一定时间内客户端没有收到服务器的通知,则认为当前的会话已经过期了。
KeeperStat.Disconnected:断开连接的状态
KeeperStat.SyncConnected:客户端和服务器端在某一个节点上建立连接,并且完成一次version、zxid同步
KeeperStat.authFailed:授权失败

3、事件类型

NodeCreated:节点被创建时触发
NodeChildrenChanged:子节点被创建、删除、修改时触发
NodeDataChanged:节点数据被修改时触发
NodeDeleted:节点被删除时触发
None:客户端和服务器端连接状态发生变化时的事件类型 就是None
常用操作的示例代码:

public class Practice implements Watcher{

	private final static String CONNECTSTRING = "192.168.123.38:2181,192.168.123.55:2181," +
            "192.168.123.45:2181,192.168.123.174:2181";
	
	private static CountDownLatch countDownLatch = new CountDownLatch(1);
	
	private static Stat stat = new Stat();
	
	private static ZooKeeper zooKeeper;
	
	public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
		zooKeeper = new ZooKeeper(CONNECTSTRING, 5000, new Practice());//new Practice()相当于注册的监听器
		countDownLatch.await();
		System.out.println("zookeeper客户端的连接状态:" + zooKeeper.getState());
		
		//创建临时节点CreateMode.EPHEMERAL
		String result = zooKeeper.create("/shuitu", "666".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
		zooKeeper.getData(result, true, stat);//继续watcher监听
		System.out.println("创建成功:" + result);
		
		//修改节点
		zooKeeper.setData(result, "shuitu233".getBytes(), -1);
		Thread.sleep(2000);//zookeeper的watcher是异步的
		zooKeeper.setData(result, "shuitu333".getBytes(), -1);
		Thread.sleep(2000);
		
		//删除节点
		zooKeeper.delete(result, -1);
		Thread.sleep(2000);
		
		//创建节点和子节点,然后修改子节点(临时节点下面不能挂子节点)
		String path = "/persistentNode";
		zooKeeper.create(path, "321".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		TimeUnit.SECONDS.sleep(1);//睡一秒
		zooKeeper.create(path + "/child", "IJustAChild".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		TimeUnit.SECONDS.sleep(1);
		
		zooKeeper.setData(path + "/child", "123".getBytes(), -1);
		TimeUnit.SECONDS.sleep(1);
		
		//判断所要创建的节点是否存在,不存在时才进行创建,防止NodeExists异常
		String path2 = "ManOfCloud";
		Stat stat2 = zooKeeper.exists(path2, true);
		if(stat2 == null){
			zooKeeper.create(path2, "云芷君".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
			TimeUnit.SECONDS.sleep(1);
		}
	}

	@Override
	public void process(WatchedEvent watchedEvent) {
		//如果当前的连接状态是成功的,那么通过countDownLatch.countDown()去控制闭锁countDownLatch.await()打开
		if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
			//watcher最开始是没有监听任何节点的,直到有节点注册了这个监听器
			if(Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()){
				countDownLatch.countDown();
				System.out.println("watcher监听事件的状态:" + watchedEvent.getState() + "-->watcher监听事件的类型:" + watchedEvent.getType());
			}
			else if (watchedEvent.getType() == Event.EventType.NodeDataChanged){//修改节点会触发
				try {
					System.out.println("所修改节点的路径:" + watchedEvent.getPath() + 
							"--> 改变后的值:" + zooKeeper.getData(watchedEvent.getPath(), true, stat));//true表示继续监听,false表示只监听一次,
																										//后面的都不会再进行监听
				} catch (KeeperException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} else if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged){//子节点被修改会触发
				try {
					System.out.println("修改了子节点的节点路径:" + watchedEvent.getPath() + 
							"--> 改变后的值:" + zooKeeper.getData(watchedEvent.getPath(), true, stat));//true表示继续监听,false表示只监听一次,
																										//后面的都不会再进行监听
				} catch (KeeperException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} else if (watchedEvent.getType() == Event.EventType.NodeCreated){//子节点被创建会触发
				try {
					System.out.println("所创建节点的路径" + watchedEvent.getPath() + 
							"--> 节点的值" + zooKeeper.getData(watchedEvent.getPath(), true, stat));
				} catch (KeeperException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} else if (watchedEvent.getType() == Event.EventType.NodeDeleted){//子节点删除会触发
				try {
					System.out.println("所删除节点的路径" + watchedEvent.getPath() + 
							"--> 节点的值" + zooKeeper.getData(watchedEvent.getPath(), true, stat));
				} catch (KeeperException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

权限控制的示例代码:

public class AuthControlDemo implements Watcher{
	
	private final static String CONNECTSTRING = "192.168.123.38:2181,192.168.123.55:2181," +
            "192.168.123.45:2181,192.168.123.174:2181";
	
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static ZooKeeper zookeeper;
    
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        zookeeper=new ZooKeeper(CONNECTSTRING, 5000, new AuthControlDemo());
        countDownLatch.await();

        //ACL (create/delete/write/read/admin)
        //权限模式: ip/Digest(username,pwd)/world对所有用户开放/super
        ACL acl = new ACL(ZooDefs.Perms.CREATE, new Id("digest","root:root"));
        ACL acl2 = new ACL(ZooDefs.Perms.CREATE, new Id("ip","192.168.1.1"));

        List<ACL> acls = new ArrayList<>();
        acls.add(acl);
        acls.add(acl2);
        zookeeper.create("/auth1","123".getBytes(),acls,CreateMode.PERSISTENT);
        zookeeper.addAuthInfo("digest","root:root".getBytes());
        zookeeper.create("/auth1","123".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
        zookeeper.create("/auth1/auth1-1","123".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL,CreateMode.EPHEMERAL);

        ZooKeeper zooKeeper1 = new ZooKeeper(CONNECTSTRING, 5000, new AuthControlDemo());
        countDownLatch.await();
        zooKeeper1.delete("/auth1",-1);

    }
    
    public void process(WatchedEvent watchedEvent) {
        //如果当前的连接状态是连接成功的,那么通过计数器去控制
        if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
            if(Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()){
                countDownLatch.countDown();
                System.out.println(watchedEvent.getState()+"-->"+watchedEvent.getType());
            }
        }
    }
}

Tips:TimeUnit.SECONDS.sleep()与Thread.sleep()的区别:
  TimeUnit.SECONDS.sleep()是对Thread.sleep()方法的包装,实现是一样的,只是多了时间单位转换和验证,并且TimeUnit是枚举类型的,枚举成员的方法提供了更好的可读性。

Tips:深入解析枚举类型
https://www.cnblogs.com/alter888/p/9163612.html

zkclient的使用

  zkclient在实际开发中使用较少,只进行简单演示。

public class ZkClientApiOperatorDemo {

	private final static String CONNECTSTRING = "192.168.123.38:2181,192.168.123.55:2181," +
            "192.168.123.45:2181,192.168.123.174:2181";

    private static ZkClient getInstance(){
        return new ZkClient(CONNECTSTRING, 10000);//建立连接,允许超时10秒
    }

    public static void main(String[] args) throws InterruptedException {
        ZkClient zkClient=getInstance();
        //zkclient 提供了递归创建节点的功能
        zkClient.createPersistent("/zkclient/zkclient1/zkclient1-1/zkclient1-1-1",true);
        System.out.println("success");

        //删除节点
        zkClient.delete("/shuitu");
        //递归删除节点
        zkClient.deleteRecursive("/zkclient");

        //获取子节点
        List<String> list = zkClient.getChildren("/node");
        System.out.println(list);

        //注册watcher 监听"/node"节点是否被修改或删除
        zkClient.subscribeDataChanges("/node", new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {
                System.out.println("节点名称:" + s + "->节点修改后的值" + o);
            }

            @Override
            public void handleDataDeleted(String s) throws Exception {

            }
        });
        //修改"/node"节点的值,测试监听是否正常启用
        zkClient.writeData("/node", "node");
        TimeUnit.SECONDS.sleep(2);

        //注册watcher 监听"/node"节点的子节点
        zkClient.subscribeChildChanges("/node", new IZkChildListener() {
            @Override
            public void handleChildChange(String s, List<String> list) throws Exception {

            }
        });
    }
}

curator

1、curator简介

  Curator是Netflix公司开源的一套Zookeeper客户端框架。了解过Zookeeper原生API都会清楚其复杂度。Curator帮助我们在其基础上进行封装、实现一些开发细节,包括接连重连、反复注册Watcher和NodeExistsException等。目前已经作为Apache的顶级项目出现,是最流行的Zookeeper客户端之一。从编码风格上来讲,它提供了基于Fluent的编程风格支持。
  除此之外,Curator还提供了Zookeeper的各种应用场景:Recipe、共享锁服务、Master选举机制和分布式计数器等。

2、curator项目组件

Recipes:Zookeeper典型应用场景的实现,这些实现是基于Curator Framework。
Framework:Zookeeper API的高层封装,大大简化Zookeeper客户端编程,添加了例如Zookeeper:连接管理、重试机制等。
Utilities:为Zookeeper提供的各种实用程序。
Client:Zookeeper client的封装,用于取代原生的Zookeeper客户端(ZooKeeper类),提供一些非常有用的客户端特性。
Errors:Curator如何处理错误,连接问题,可恢复的例外等。

3、curator连接的重试策略

  参数RetryPolicy提供了连接重试策略的接口,可以让用户实现自定义的重试策略。默认提供了以下实现,分别为:
ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries):衰减重试,baseSleepTimeMs间隔时间,maxRetries最大重试次数
RetryNTimes:指定最大重试次数
RetryOneTime:仅重试一次
RetryUnitilElapsed:一直重试,直到规定的时间

4、节点监听的三种watch

PatchChildrenCache :监视一个路径下子节点的创建、删除、节点数据更新
NodeCache :监视一个节点的创建、更新、删除
TreeCache :pathcache和nodecache的合体(监视路径下的创建、更新、删除事件),缓
存路径下的所有子节点的数据。
Tips:Java并发工具类CountDownLatch的使用细节参考如下博客
https://www.cnblogs.com/liun1994/p/7396026.html
  CountDownLatch能够使一个或多个线程等待其他线程完成各自的工作后再执行;CountDownLatch是JDK 5+里面闭锁的一个实现。
  闭锁(Latch)是一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。
  与CountDownLatch第一次交互是主线程等待其它的线程,主线程必须在启动其它线程后立即调用await方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
  其他的N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务,这种机制就是通过countDown()方法来完成的。每调用一次这个方法,在构造函数中初始化的count值就减1,所以当N个线程都调用了这个方法count的值等于0,然后主线程就能通过await方法,恢复自己的任务。
Curator连接的两种方式

public class CuratorCreateSessionDemo {
	private final static String CONNECTSTRING = "192.168.123.38:2181,192.168.123.55:2181," +
            "192.168.123.45:2181,192.168.123.174:2181";
	
    public static void main(String[] args) {
        //创建会话的两种方式:
    	//1、normal
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(CONNECTSTRING, 5000, 5000, 
                new ExponentialBackoffRetry(1000, 3));
        curatorFramework.start(); //用start方法启动连接

        //2、fluent风格
        CuratorFramework curatorFramework1 = CuratorFrameworkFactory
        		.builder()
        		.connectString(CONNECTSTRING)
        		.sessionTimeoutMs(5000)
        		.retryPolicy(new ExponentialBackoffRetry(1000, 3))
        		.namespace("/curator")//命名空间,表示创建连接后的所有节点操作都是在这个根节点下
        		.build();

        curatorFramework1.start();
        System.out.println("success");
    }
}

Curator的常用操作

public class CuratorOperatorDemo {

    public static void main(String[] args) throws InterruptedException {
        CuratorFramework curatorFramework = CuratorClientUtils.getInstance();
        System.out.println("连接成功.........");

        //创建节点
        try {
            String result = curatorFramework
            		.create()
            		.creatingParentsIfNeeded()
            		.withMode(CreateMode.PERSISTENT)
            		.forPath("/curator/curator1/curator11", "123".getBytes());
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //删除节点
        try {
            //默认情况下,version为-1
            curatorFramework.delete().deletingChildrenIfNeeded().forPath("/node11");
        } catch (Exception e) {
            e.printStackTrace();
        }

        //查询
        Stat stat = new Stat();
        try {
            byte[] bytes = curatorFramework.getData().storingStatIn(stat).forPath("/curator");
            System.out.println(new String(bytes) + "-->stat:" + stat);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //修改
        try {
            Stat stat1 = curatorFramework.setData().forPath("/curator", "123".getBytes());
            System.out.println(stat1);
        } catch (Exception e) {
            e.printStackTrace();
        }


        //通过线程池来实现异步操作
        ExecutorService service = Executors.newFixedThreadPool(1);
        CountDownLatch countDownLatch = new CountDownLatch(1);
        try {
            curatorFramework
            	.create()
            	.creatingParentsIfNeeded()
            	.withMode(CreateMode.EPHEMERAL)
            	.inBackground(new BackgroundCallback() {//节点创建完成后回调本方法
                    @Override
                    public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                        System.out.println(Thread.currentThread().getName() + "->resultCode:" + curatorEvent.getResultCode() + "->"
                        		+ curatorEvent.getType());
                        countDownLatch.countDown();
                    }
                },service)
            	.forPath("/mic", "123".getBytes());//创建节点的事件是由线程池处理的
        } catch (Exception e) {
            e.printStackTrace();
        }
        countDownLatch.await();//创建节点的线程完成创建后,通知主线程继续进行
        service.shutdown();

        //事务操作(curator独有的)
        try {
        	//记录事务中每个操作的结果信息
            Collection<CuratorTransactionResult> resultCollections = curatorFramework
															            		.inTransaction()
															            		.create()	//创建"/trans"节点
															            		.forPath("/trans", "111".getBytes())
															            		.and()
															            		.setData()	//并且修改"/curator"节点
															            		.forPath("/curator", "111".getBytes())
															            		.and()
															            		.commit();	//最后提交事务
            for (CuratorTransactionResult result:resultCollections){
                System.out.println(result.getForPath() + "->" + result.getType());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Curator事件监听的示例代码

public class CuratorEventDemo {

    /**
     * Curator提供了三种watcher来做节点的监听
     * PatchChildrenCache   监视一个路径下子节点的创建、删除、节点数据更新
     * NodeCache   监视一个节点的创建、更新、删除
     * TreeCache   PathCache与NodeCache的合体(监视路径下的创建、更新、删除事件),缓存路径下的所有子节点的数据
     */

    @SuppressWarnings("resource")
	public static void main(String[] args) throws Exception {
        CuratorFramework curatorFramework = CuratorClientUtils.getInstance();

        //NodeCache,监控节点"/curator"的变化
        NodeCache cache = new NodeCache(curatorFramework, "/curator", false);//false表示 缓存的数据不进行压缩
        cache.start(true);
        cache.getListenable().addListener(()-> System.out.println("节点数据发生变化,变化后的结果:"
        	+ new String(cache.getCurrentData().getData())));
        curatorFramework.setData().forPath("/curator", "艾米莉亚".getBytes());

        //PatchChildrenCache,监控"/event"下面子节点的变化
        PathChildrenCache cache1 = new PathChildrenCache(curatorFramework, "/event", true);//true表示对数据进行缓存
        cache1.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
        cache1.getListenable().addListener((curatorFramework1, pathChildrenCacheEvent)->{
            switch (pathChildrenCacheEvent.getType()){
                case CHILD_ADDED:
                    System.out.println("增加子节点");
                    break;
                case CHILD_REMOVED:
                    System.out.println("删除子节点");
                    break;
                case CHILD_UPDATED:
                    System.out.println("修改子节点");
                    break;
                default:
                	break;
            }
        });

        //创建节点
        curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/event", "event".getBytes());
        TimeUnit.SECONDS.sleep(1);
        System.out.println("1");
        //创建子节点
        curatorFramework.create().withMode(CreateMode.EPHEMERAL).forPath("/event/event1", "1".getBytes());
        TimeUnit.SECONDS.sleep(1);
        System.out.println("2");
        //修改子节点
        curatorFramework.setData().forPath("/event/event1", "222".getBytes());
        TimeUnit.SECONDS.sleep(1);
        System.out.println("3");
        //删除子节点
        curatorFramework.delete().forPath("/event/event1");
        System.out.println("4");

        System.in.read();//不让程序这么早就结束
	}
}

Tips:lambda表达式的使用,可以参考如下博客
https://www.cnblogs.com/aoeiuv/p/5911692.html

发布了51 篇原创文章 · 获赞 1 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_38038396/article/details/91359318