zookeeper一文精通(中)

zookeeper一文精通(中)


​ 上一篇文章带着大家学习了一下zk的基本概念、存储结构、原始API等,本文主要是给大家介绍一下操作zk的2个框架,一个是Curator另一个是ZkClient。在dubbo的老版本中使用的是zkclient,在新版本中使用的是Curator。

想查看更多的文章请关注公众号:IT巡游屋
在这里插入图片描述

一、为什么不使用原始的API来操作zk
  1. ZooKeeper的Watcher是一次性的,用过了需要再注册
  2. session的超时后没有自动重连,生产环境中如果网络出现不稳定情况,那么这种情况出现的更加明显
  3. 没有领导选举机制,集群情况下可能需要实现stand by,一个服务挂了,另一个需要接替的效果
  4. 客户端只提供了存储byte数组的接口,而项目中一般都会使用对象
  5. 客户端接口需要处理的异常太多,并且通常,我们也不知道如何处理这些异常
二、zkclient 框架的基本使用
  • 导入jar包

    <!-- zookeeper本身的包 -->
    	 <dependency>
    	    <groupId>org.apache.zookeeper</groupId>
    	    <artifactId>zookeeper</artifactId>
    	    <version>3.4.13</version>
    	</dependency>
    	<!-- zkclient -->
    	<dependency>
    	    <groupId>com.101tec</groupId>
    	    <artifactId>zkclient</artifactId>
    	    <version>0.10</version>
    	</dependency>
    
  • 获取连接

    	@Test
        public void getZkClient(){
            ZkClient zkClient = new ZkClient(CONNECTSTRING,sessionTimeout);
            System.out.println(zkClient);
        }
    
  • 创建节点

     	@Test
        public void createNode(){
            ZkClient zkClient = new ZkClient(CONNECTSTRING,sessionTimeout);
            zkClient.createPersistent("/abc","abc");
        }
    
  • 获取节点

     	@Test
        public void getNode(){
            ZkClient zkClient = new ZkClient(CONNECTSTRING,sessionTimeout);
            String value = zkClient.readData("/abc");
            System.out.println(value);
        }
    
    
  • 创建子节点

    	@Test
        public void createChildNode(){
            ZkClient zkClient = new ZkClient(CONNECTSTRING,sessionTimeout);
            //递归创建
            zkClient.createPersistent("/abc/cba/aaa/bb",true);
        }
    
  • 获取子节点

    	@Test
        public void getChild(){
            ZkClient zkClient = new ZkClient(CONNECTSTRING,sessionTimeout);
            List<String> list = zkClient.getChildren("/abc");
            System.out.println(Arrays.toString(list.toArray()));
        }
    
  • 删除子节点

        @Test
        public void deleteChild(){
            ZkClient zkClient = new ZkClient(CONNECTSTRING,sessionTimeout);
           zkClient.deleteRecursive("/abc");
        }
    
  • 添加监听事件

        @Test
        public void addListener(){
            ZkClient zkClient = new ZkClient(CONNECTSTRING,sessionTimeout);
            zkClient.subscribeChildChanges("/abc", new IZkChildListener() {
                @Override
                public void handleChildChange(String s, List<String> list) throws Exception {
                    System.out.println(s+" : " + list);
                }
            });
            zkClient.writeData("/abc","ccc");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
三、Curator框架的基本操作
  • 创建连接

        @Test
        public void getConnection(){
            /**
             * 第一种创建方式
             *  ExponentialBackoffRetry: 表示超时策略,每隔1000毫秒执行一次,总共执行三次
             *  RetryNTimes: 重试指定次数,每隔1000毫秒执行一次
             */
            CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(CONNECTSTRING,sessionTimeout,Connectiontime,
                    new ExponentialBackoffRetry(1000,3));
            curatorFramework.start(); //启动连接
            System.out.println(curatorFramework.getState());//打印状态
    
            //第二种创建方式
            CuratorFramework curatorFramework1 = CuratorFrameworkFactory.builder()
                    .sessionTimeoutMs(sessionTimeout)
                    .connectionTimeoutMs(Connectiontime)
                    .connectString(CONNECTSTRING)
                    .retryPolicy(new RetryNTimes(3,1000))
                    .build();
            curatorFramework1.start();
            System.out.println(curatorFramework1);
    
        }
    
  • 其他操作参考zkclient的api

四、zookeeper实现分布式共享锁
  1. 创建公共类

    //获取zk的连接
    public class ZookeeperUtills {
        private static final String CONNECTSTRING = "192.168.11.129:2181,192.168.11.134:2181,92.168.11.135:2181,192.168.11.136:2181";
        private static final Integer sessionTimeout = 10000;
        private static CountDownLatch countDownLatch = new CountDownLatch(1);
    
        public static ZooKeeper getInstance(){
            try {
                ZooKeeper zooKeeper = new ZooKeeper(CONNECTSTRING, sessionTimeout, new Watcher() {
                    @Override
                    public void process(WatchedEvent watchedEvent) {
                        if(watchedEvent.getState().equals(Event.KeeperState.SyncConnected)){
                            System.out.println("连接成功!!!!");
                            countDownLatch.countDown();
                        }
                    }
                });
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return zooKeeper;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static Integer getSessionTimeout(){
            return ZookeeperUtills.sessionTimeout;
        }
    }
    
  2. 创建核心类

    public class DistributeLock {
        private String lockId;
        private ZooKeeper zooKeeper;
        private String MASTPATH = "/master";
        private Integer sessionTimeout;
        private byte[] data = new byte[]{1,2};
        private CountDownLatch countDownLatch = new CountDownLatch(1);
    
        public DistributeLock(){
            this.zooKeeper = ZookeeperUtills.getInstance();
            this.sessionTimeout = ZookeeperUtills.getSessionTimeout();
        }
    
        public void lock(){
            try {
            //1. 创建一个临时有序的节点
            lockId = zooKeeper.create(MASTPATH +"/",data, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            //2. 获取所有的子节点
            List<String> childs = zooKeeper.getChildren(MASTPATH,false);
                if(!childs.isEmpty()){
                    //3. 将所有的子节点进行排序
                    SortedSet<String> sorted = new TreeSet<>();
                    for(int i=0;i<childs.size();i++){
                        sorted.add(MASTPATH+"/"+childs.get(i));
                    }
                    //4. 取出第一个,然后比较第一个是不是自己,如果是则表示获取到了锁
                    String first = sorted.first();
                    if(first.equals(lockId)){
                        System.out.println("获取锁成功,获取到的lockId是:" + lockId);
                    }else{
                        //5. 如果第一个不是自己,则获取自己的前面一个节点,然后监听前一个节点的删除事件
                        SortedSet<String> heads = ((TreeSet<String>) sorted).headSet(lockId);
                        String prevHead = heads.last();
                        if(!prevHead.isEmpty()){
                            zooKeeper.exists(prevHead, new Watcher() {
                                @Override
                                public void process(WatchedEvent watchedEvent) {
                                    if(watchedEvent.getState().equals(Event.EventType.NodeDeleted)){
                                        DistributeLock.this.countDownLatch.countDown();
                                    }
                                }
                            });
                        }
    
                    }
                }
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public void unlock(){
            try {
                zooKeeper.delete(lockId,-1);
                System.out.println("节点"+lockId + "成功删除");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
        }
    }
    
  3. 测试类

    public class DistributeMain {
        public static void main(String[] args) {
            for (int i=0; i<10; i++){
                new Thread(new Runnable() {
                    DistributeLock lock = null;
                    Random random = new Random();
                    @Override
                    public void run() {
                       lock = new DistributeLock();
                       lock.lock();
                        try {
                            Thread.sleep(random.nextInt(500));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }finally {
                            lock.unlock();
                        }
                    }
                }).start();
            }
        }
    }
    
    
五、zkclient实现leader选举
  1. 封装一个选举对象

    public class User implements Serializable {
        private int id;
        private String name;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
  2. 封装一个工具类

    public class ZkClientUtils {
        private static final String CONNECTSTRING = "192.168.11.129:2181,192.168.11.134:2181,92.168.11.135:2181,192.168.11.136:2181";
        private static final Integer sessionTimeout = 10000;
    
        public static ZkClient getInstance(){
            return new ZkClient(CONNECTSTRING,sessionTimeout);
        }
    
        public static Integer getSessionTimeout(){
            return ZkClientUtils.sessionTimeout;
        }
    }
    
  3. 封装核心选举类

    public class MasterSelector {
        private User server;
        private User master;
        private ZkClient zkClient;
        private String Master = "/master";
        private boolean isRunning ;
        private IZkDataListener dataListener;
        private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    
        public MasterSelector(ZkClient zkClient,User server){
            this.isRunning = true;
            this.zkClient = zkClient;
            this.server = server;
            this.dataListener = new IZkDataListener() {
                @Override
                public void handleDataChange(String s, Object o) throws Exception {
    
                }
    
                @Override
                public void handleDataDeleted(String path) throws Exception {
                    System.out.println("删除的数据路径:" + path);
                    selector();
                }
            };
        }
    
        //选举的方法
        public void selector(){
            try{
                //1. 开始创建节点,如果节点创建成功,则表示竞争到了锁,屏把server复制给master
                zkClient.create(Master,server,CreateMode.EPHEMERAL);
                this.master = server;
                System.out.println(Thread.currentThread().getName() + "成功获取到连锁 master is " + server.toString());
    
                //模拟释放锁
                executorService.schedule(new Runnable() {
                    @Override
                    public void run() {
                        releaseMaster();
                    }
                },5,TimeUnit.SECONDS);
            }catch (Exception e){
                //2. 如果抛出异常则表示竞争锁失败,可以再次获取锁节点,如果锁节点为空则可以重新竞争
                User user = zkClient.readData(Master);
                if(null == user){
                    selector();
                }else{
                    this.master = user;
                }
            }
    
        }
    
        //释放锁
        public void releaseMaster(){
            User user = zkClient.readData(Master);
            if(user.getId() == server.getId()){
                zkClient.delete(Master);
            }
        }
        
        //开启
        public void start(){
            if(isRunning){
                zkClient.subscribeDataChanges(Master,dataListener);
                selector();
                System.out.println("start 方法执行成功,开始竞争master节点");
            }
        }
        
        //结束
        public void stop(){
            if(isRunning){
                this.isRunning = false;
                executorService.shutdown();
                zkClient.unsubscribeDataChanges(Master,dataListener);
                releaseMaster();
                System.out.println("节点删除成功");
            }
        }
    
  4. 测试选举

    public class SelectorClient {
        public static int j = 1;
    
        public static void main(String[] args) {
            for (int i=0; i<10;i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        User user = new User();
                        user.setId(j);
                        user.setName("zhangsan"+j++);
                        MasterSelector selector = new MasterSelector(ZkClientUtils.getInstance(),user);
                        selector.start();
    
                        try {
                            TimeUnit.SECONDS.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
    

zookeeper一文精通(下)预告:下一篇文件我们讲重点给大家讲解一下zk的zab协议和zk的选举机制,这2个内容也是面试中问的最多的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值