Zookeeper实战-使用原生api开发

zookeeper 提供了java api来进行node的创建、删除、数据的获取设置、子节点的状态的观察、权限的设置。并且zookeeper客户端提供了异步操作,并有监听机制,更为方便的提供对zookeeper数据的监听和维护,本文通过几个例子来进行java api的学习和使用。

依赖引入

java api要引入mvn依赖,这里采用3.4.8,如下所示:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.8</version>
</dependency>

创建zookeeper会话

API

Zookeeper client提供了以下几个初始化函数,初始化函数是异步的,会立即返回,大多数情况下状态为CONNECTING,当真正建立连接成功后会发送状态改变事件给客户端。client得到这个通知才算是真正建立好连接。

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
            boolean canBeReadOnly)
    
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
            long sessionId, byte[] sessionPasswd)

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
            long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)

参数含义:

  1. connectString是zookeeper server的ip:port,可以有多个,之间用,分割: “127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002”,客户端
  2. sessionTimeOut:会话超时时间,时间单位是毫秒,会话周期内进行心跳检查,如果一个会话周期内检测不到心跳,会话就失效。
  3. watcher: 事件监听器,有些api是异步的,会监听事件,当事件发生后发送给客户端,客户端通过实现改监听器来处理接收到的事件
  4. sessionId+sessionPasswd: 客户端连接后会生成一个sessionId和sessionPasswd,两个字段唯一确定一个会话,zookeeper可以根据这个sessionId+sessionPasswd来恢复一个会话 [#getSessionId] [#getSessionPasswd]
  5. canBeReadOnly: 表示当前会话是否支持read-only模式,默认情况下,zookeeper集群中一个机器如果和集群中过半的机器失去网络连接,那么这个机器不再接受客户端请求,但是某些情况下,这种情况我们仍然希望zk服务可以提供读服务,写服务肯定是无法提供的,这个就是zk的read-only模式

实战

  1. 先创建一个基本的会话实例,然后利用该实例的sessionId和sessionPasswd进行恢复会话。

    package com.example.zookeeper.javaapi;
    
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooKeeper;
    
    import java.util.concurrent.CountDownLatch;
    
    public class ZookeeperConstructorExample implements Watcher {
    
        // 利用countDownLatch进行阻塞直到连接建立
        private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
    
        public static void main(String[] args) throws Exception {
            ZooKeeper zooKeeper = new ZooKeeper("localhost:2181",
                    5000, new ZookeeperConstructorExample());
            System.out.println(zooKeeper.getState());
    
            connectedSemaphore.await();
            System.out.println("Zookeeper session establshed");
    
            // 利用sessionId和sessionPasswd恢复连接
            long sessionId = zooKeeper.getSessionId();
            byte[] sessionPasswd = zooKeeper.getSessionPasswd();
            zooKeeper = new ZooKeeper("localhost:2181",
                    5000, new ZookeeperConstructorExample(),
                    1L, "test".getBytes());
    
            zooKeeper = new ZooKeeper("localhost:2181",
                    5000, new ZookeeperConstructorExample(),
                    sessionId, sessionPasswd);
    
            Thread.sleep(Integer.MAX_VALUE);
        }
    
        @Override
        public void process(WatchedEvent watchedEvent) {
            System.out.println("Receive watched event: " + watchedEvent);
            if (Event.KeeperState.SyncConnected == watchedEvent.getState()) {
                connectedSemaphore.countDown();
            }
        }
    }
    
  2. 运行结果

    // 在客户端构造函数调用后立即返回,但是并未建立好连接
    CONNECTING
    // 三次握手结束,连接建立
    Receive watched event: WatchedEvent state:SyncConnected type:None path:null 
    Zookeeper session establshed
     // 利用不合法的sessionId和sessionPasswd会得到会话过期的事件通知
    Receive watched event: WatchedEvent state:Expired type:None path:null
    // 利用不合法的sessionId和sessionPasswd会进行会话重用
    Receive watched event: WatchedEvent state:SyncConnected type:None path:null
    // 会话心跳
    Receive watched event: WatchedEvent state:Disconnected type:None path:null
    Receive watched event: WatchedEvent state:SyncConnected type:None path:null
    

创建节点

API

提供了两个create方法,一个同步创建,一个异步创建,异步创建函数需要提供callback来对接口进行处理。

// 同步创建,等待直至创建完成
public String create(final String path, byte data[], List<ACL> acl,
            CreateMode createMode)
// 异步创建,结果回调接口中处理
public void create(final String path, byte data[], List<ACL> acl,
            CreateMode createMode,  StringCallback cb, Object ctx)

参数解析:

  1. Path: 创建的路径
  2. data: 创建完成后的初始内容
  3. createMode: 节点类型,四种选择持久(PERSISTENG),持久顺序(PERSISTENT_SEQUENTIAL),临时(EMHEMERAL),临时顺序(EMHEMERAL_SEQUENTIAL)
  4. acl: 节点的acl策略
  5. cb: 异步回调接口,需要实现process方法 public void processResult(int rc, String path, Object ctx, String name)
    1. rc: 服务端响应码,0是调用成功,-4是连接断开,-110节点已存在,-112会话过期
    2. Path: create函数中的path
    3. ctx: create函数中的ctx
    4. Name: 实际在服务端创建的节点名
  6. ctx: 可以在回调函数中使用的一个对象

实战

  1. 先创建一个基本的会话实例,然后利用该实例进行同步&异步的创建节点

    package com.example.zookeeper.javaapi;
    
    import org.apache.zookeeper.*;
    
    import java.util.concurrent.CountDownLatch;
    
    public class ZookeeperCreateNodeExample implements Watcher {
        // 利用countDownLatch进行阻塞直到连接建立
        private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
    
        public static void main(String[] args) throws Exception {
            ZooKeeper zooKeeper = new ZooKeeper("localhost:2181",
                    5000, new ZookeeperCreateNodeExample());
            System.out.println(zooKeeper.getState());
    
            connectedSemaphore.await();
            System.out.println("Zookeeper session establshed");
            // 同步创建
            syncCreate(zooKeeper);
    
            // 异步创建
            asyCreate(zooKeeper);
            Thread.sleep(Integer.MAX_VALUE);
        }
    
        public static void syncCreate(ZooKeeper zooKeeper) {
            try{
                String path1 = zooKeeper.create("/zk-test-create", "".getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                System.out.println("path1:" + path1);
    
                // 顺序临时节点
                String path2 = zooKeeper.create("/zk-test-create", "".getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
                System.out.println("path2:" + path2);
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    
        public static void asyCreate(ZooKeeper zooKeeper) {
            zooKeeper.create("/zk-test-create", "".getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL,
                    new IStringCallback(), "I am context");
    
            // 顺序临时节点
            zooKeeper.create("/zk-test-create", "".getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,
                    new IStringCallback(), "I am context");
        }
    
        @Override
        public void process(WatchedEvent watchedEvent) {
            System.out.println("Receive watched event: " + watchedEvent);
            if (Watcher.Event.KeeperState.SyncConnected == watchedEvent.getState()) {
                connectedSemaphore.countDown();
            }
        }
    }
    
    // callback实现
    package com.example.zookeeper.javaapi;
    
    import org.apache.zookeeper.AsyncCallback;
    
    public class IStringCallback implements AsyncCallback.StringCallback {
    
        @Override
        public void processResult(int rc, String path, Object ctx, String name) {
            System.out.println("Create path result: [code=" + rc + "; path=" +
                    path + ";ctx=" + ctx + ";real path name =" + name);
        }
    }
    
  2. 运行结果

    CONNECTING
    Receive watched event: WatchedEvent state:SyncConnected type:None path:null
    Zookeeper session establshed
    // 同步等待结果
    path1:/zk-test-create
    // 临时顺序节点,会自动分配一个id
    path2:/zk-test-create0000000016
    // 异常,nodeExist
    Create path result: [code=-110; path=/zk-test-create;ctx=I am context;real path name =null
    // 异步结果回调,接口调用成功
    Create path result: [code=0; path=/zk-test-create;ctx=I am context;real path name =/zk-test-create0000000017
    

删除节点

与创建节点一样,zk提供了同步以及异步的删除接口,如下所示:

public void delete(final String path, int version) throws InterruptedException, KeeperException
public void delete(final String path, int version, VoidCallback cb,  Object ctx)
  1. Path:要删除的path
  2. Version: 指定节点的数据版本
  3. cb: 异步回调接口
  4. Ctx: 传递上下文的对象

需要注意的是,zk只允许删除叶子节点

读取节点列表

API

zk提供了八个方法用来获取子节点列表,分别是四个同步,四个异步,如下所示:

public List<String> getChildren(String path, boolean watch)
public List<String> getChildren(final String path, Watcher watcher, Stat stat)
public List<String> getChildren(String path, boolean watch, Stat stat)
    
public void getChildren(final String path, Watcher watcher, ChildrenCallback cb, Object ctx)
public void getChildren(String path, boolean watch, ChildrenCallback cb, Object ctx)
public void getChildren(final String path, Watcher watcher, Children2Callback cb, Object ctx)    

参数说明:参数和create api的比较相似,

实战

  1. 程序实例

    import org.apache.zookeeper.*;
    
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    
    public class ZookeeperGetChildrenExample implements Watcher {
        // 利用countDownLatch进行阻塞直到连接建立
        private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
        private static ZooKeeper zooKeeper = null;
    
        public static void main(String[] args) throws Exception {
            // zookeeper构造
            zooKeeper = new ZooKeeper("localhost:2181",
                    5000, new ZookeeperGetChildrenExample());
            System.out.println(zooKeeper.getState());
    
            connectedSemaphore.await();
            System.out.println("Zookeeper session establshed");
    
            // 同步接口获取子节点,先创建节点,并注册NodeChildrenChanged事件监听
            String path = "/zk-book";
            zooKeeper.create(path, "".getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            zooKeeper.create(path + "/c1", "".getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            // 同步获取子节点
            List<String> childrens = zooKeeper.getChildren(path, true);
            System.out.println(childrens);
    
            // 再次创建节点,事件监听会触发,然后可以自定义结果处理,这里重新获取了节点list
            zooKeeper.create(path + "/c2", "".getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
    
            Thread.sleep(2000);
    
            // 异步接口获取
            zooKeeper.getChildren(path, true, new IChildren2Callback(), null);
            Thread.sleep(Integer.MAX_VALUE);
        }
    
        @Override
        public void process(WatchedEvent watchedEvent) {
            System.out.println("Receive watched event: " + watchedEvent);
            if (Event.KeeperState.SyncConnected == watchedEvent.getState()) {
                if (Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()) {
                    connectedSemaphore.countDown();
                } else if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged) {
                    try {
                        // NodeChildrenChanged事件是一次性的,需要重新注册,这里先不注册了
                        System.out.println("reget children:" +
                                zooKeeper.getChildren(watchedEvent.getPath(), false));
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                }
            }
        }
    }
    
    // 异步接口回调
    import org.apache.zookeeper.AsyncCallback;
    import org.apache.zookeeper.data.Stat;
    
    import java.util.List;
    
    public class IChildren2Callback implements AsyncCallback.Children2Callback {
    
        @Override
        public void processResult(int rc, String path, Object ctx,
                                  List<String> children, Stat stat) {
            System.out.println("Get children  znode result:[ rc=" + rc +
                    ";path=" + path + ";ctx=" + ctx + ";children:" + children + ";stat=" + stat);
        }
    }
    
    
  2. 运行结果

    CONNECTING
    Receive watched event: WatchedEvent state:SyncConnected type:None path:null
    Zookeeper session establshed
    [c1]
    Receive watched event: WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/zk-book
    reget children:[c1, c2]
    Get children  znode result:[ rc=0;path=/zk-book;ctx=null;children:[c1, c2];stat=573,573,1582850171405,1582850171405,0,2,0,0,0,2,575
    

读取和更新数据

api说明

  1. 获取数据getData

    zk提供了四个方法来获取节点的数据,参数path, cb, ctx都和前面的一致,watcher注册了,可以监听到后续节点变化,提供回调函数可以获取到最新的数据信息。

    public byte[] getData(final String path, Watcher watcher, Stat stat)
            throws KeeperException, InterruptedException
     public byte[] getData(String path, boolean watch, Stat stat)
                throws KeeperException, InterruptedException
        
    public void getData(final String path, Watcher watcher,
                DataCallback cb, Object ctx)
    public void getData(String path, boolean watch, DataCallback cb, Object ctx)
    
  2. 更新数据setData

    zk提供了同步和异步更新节点数据的接口,path,cb, ctx同其他api的一样,data字节数据是要更新的数据,同步数据获取到返回的状态信息,该状态信息里面包含了数据的最新版本信息。

    public Stat setData(final String path, byte data[], int version)
            throws KeeperException, InterruptedException
    public void setData(final String path, byte data[], int version,
                StatCallback cb, Object ctx)
    

     需要重点关注的是version字段,这个是要更新的节点的数据版本,但是在获取data信息时候并没有指定版本获取,这是为什么呢?这牵扯到CAS(compare and swap),这个字段可以避免分布式更新的并发问题。具体说:加入一个客户端试图进行更新操作,他会携带上次获取到的version值进行更新,而如果在获取->设置的间隔时间内,zk服务器上该节点的数据被其他客户端进行了更新,那数据版本一定得到了更新,改客户端的版本无法进行匹配,导致更新失败,从而来应对分布式更新的并发问题,利用这一点可以来构建复杂应用分布式锁服务等,但是如果更新操作没有原子性的要求,那么设置为-1就行。

实战

  1. 程序实例

    import netscape.security.UserTarget;
    import org.apache.zookeeper.*;
    import org.apache.zookeeper.data.Stat;
    
    import java.util.concurrent.CountDownLatch;
    
    public class ZookeeperGetSetDataExample implements Watcher {
        // 利用countDownLatch进行阻塞直到连接建立
        private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
        private static ZooKeeper zooKeeper = null;
        private static Stat stat = new Stat();
    
        public static void main(String[] args) throws Exception {
            zooKeeper = new ZooKeeper("localhost:2181",
                    5000, new ZookeeperGetSetDataExample());
            System.out.println(zooKeeper.getState());
            connectedSemaphore.await();
            System.out.println("Zookeeper session establshed");
    
            // 创建节点
            String path = zooKeeper.create("/zk-book", "".getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.println("path:" + path);
    
            // 设置数据
            zooKeeper.setData("/zk-book", "123".getBytes(), -1);
    
            // 获取数据
            System.out.println(new String(
                    zooKeeper.getData(path, new ZookeeperGetSetDataExample(), stat))
            );
    
            // 更新数据--更新成功,获取数据回调接收到消息
            zooKeeper.setData("/zk-book", "345".getBytes(), 1);
    
            // 更新数据--老版本更新失败
            zooKeeper.setData("/zk-book", "345".getBytes(), 1);
    
            Thread.sleep(Integer.MAX_VALUE);
        }
    
        @Override
        public void process(WatchedEvent watchedEvent) {
            System.out.println("Receive watched event: " + watchedEvent);
            if (Event.KeeperState.SyncConnected == watchedEvent.getState()) {
                if (Event.EventType.None == watchedEvent.getType()
                        && null == watchedEvent.getPath()) {
                    connectedSemaphore.countDown();
                } else if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                    try {
                        System.out.println(new String(
                                zooKeeper.getData(watchedEvent.getPath(), true, stat))
                        );
                        System.out.println(stat.getCzxid() + "," +
                                stat.getMzxid() + "," + stat.getVersion());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  2. 运行结果

    CONNECTING
    Receive watched event: WatchedEvent state:SyncConnected type:None path:null
    Zookeeper session establshed
    path:/zk-book
    123
    // 更新数据接收到watch事件通知
    Receive watched event: WatchedEvent state:SyncConnected type:NodeDataChanged path:/zk-book
    345
    594,596,2
    // 版本错误,抛出异常
    Exception in thread "main" org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for /zk-book
    	at org.apache.zookeeper.KeeperException.create(KeeperException.java:115)
    	at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
    	at org.apache.zookeeper.ZooKeeper.setData(ZooKeeper.java:1327)
    	at com.example.zookeeper.javaapi.ZookeeperGetSetDataExample.main(ZookeeperGetSetDataExample.java:39)
    
    

节点存在检测

api介绍

zk提供了四个检测节点是否存在的函数,参数含义同前面的几个api

public Stat exists(final String path, Watcher watcher)
public Stat exists(String path, boolean watch)
public void exists(final String path, Watcher watcher, StatCallback cb, Object ctx)  
public void exists(String path, boolean watch, StatCallback cb, Object ctx) 

注意:watch时候当节点创建删除或者更新时候都会接到通知。

实战

  1. 程序实例

    import org.apache.zookeeper.*;
    import java.util.concurrent.CountDownLatch;
    
    public class ZookeeperExistsExample implements Watcher {
        // 利用countDownLatch进行阻塞直到连接建立
        private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
        private static ZooKeeper zooKeeper = null;
    
        public static void main(String[] args) throws Exception {
            // zookeeper构造
            zooKeeper = new ZooKeeper("localhost:2181",
                    5000, new ZookeeperExistsExample());
            System.out.println(zooKeeper.getState());
    
            connectedSemaphore.await();
            System.out.println("Zookeeper session establshed");
    
            // 在path上检测是否存在事件
            String path = "/zk-book";
            zooKeeper.exists(path, true);
    
            // 同步接口获取子节点,先创建节点,并注册NodeChildrenChanged事件监听
            zooKeeper.create(path, "".getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    
            // 更新数据
            zooKeeper.setData(path, "123".getBytes(), -1);
    
            // 删除节点
            zooKeeper.delete(path, -1);
    
            Thread.sleep(2000);
        }
    
        @Override
        public void process(WatchedEvent watchedEvent) {
            System.out.println("Receive watched event: " + watchedEvent);
            try {
                if (Watcher.Event.KeeperState.SyncConnected == watchedEvent.getState()) {
                    if (Watcher.Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()) {
                        connectedSemaphore.countDown();
                    } else if (watchedEvent.getType() == Event.EventType.NodeCreated) {
                        System.out.println("Node: " + watchedEvent.getPath() + " Create");
                        zooKeeper.exists(watchedEvent.getPath(), true);
                    } else if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                        System.out.println("Node: " + watchedEvent.getPath() + " data change");
                        zooKeeper.exists(watchedEvent.getPath(), true);
                    } else if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
                        System.out.println("Node: " + watchedEvent.getPath() + " delete");
                        zooKeeper.exists(watchedEvent.getPath(), true);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    }
    
  2. 运行结果

    CONNECTING
    Receive watched event: WatchedEvent state:SyncConnected type:None path:null
    Zookeeper session establshed
    Receive watched event: WatchedEvent state:SyncConnected type:NodeCreated path:/zk-book
    Node: /zk-book Create
    Receive watched event: WatchedEvent state:SyncConnected type:NodeDataChanged path:/zk-book
    Node: /zk-book data change
    Receive watched event: WatchedEvent state:SyncConnected type:NodeDeleted path:/zk-book
    Node: /zk-book delete
    

参考

  1. 从Paxo到Zookeeper: 分布式一致性原理与实战
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值