zookeeper一文精通(中)
上一篇文章带着大家学习了一下zk的基本概念、存储结构、原始API等,本文主要是给大家介绍一下操作zk的2个框架,一个是Curator另一个是ZkClient。在dubbo的老版本中使用的是zkclient,在新版本中使用的是Curator。
想查看更多的文章请关注公众号:IT巡游屋
一、为什么不使用原始的API来操作zk
- ZooKeeper的Watcher是一次性的,用过了需要再注册
- session的超时后没有自动重连,生产环境中如果网络出现不稳定情况,那么这种情况出现的更加明显
- 没有领导选举机制,集群情况下可能需要实现stand by,一个服务挂了,另一个需要接替的效果
- 客户端只提供了存储byte数组的接口,而项目中一般都会使用对象
- 客户端接口需要处理的异常太多,并且通常,我们也不知道如何处理这些异常
二、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实现分布式共享锁
-
创建公共类
//获取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; } }
-
创建核心类
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(); } } }
-
测试类
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选举
-
封装一个选举对象
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 + '\'' + '}'; } }
-
封装一个工具类
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; } }
-
封装核心选举类
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("节点删除成功"); } }
-
测试选举
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个内容也是面试中问的最多的内容。