zookeeper 通知机制

通知机制:

客户端注册监听他关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端

Session:
客户端使用某种语言绑定创建一个服务的句柄时,就建立了一个zookeeper会话。会话建立后,句柄处于CONNECTING状态,客户端会试图连接到组成zookeeper服务的某个服务器,连接成功则进入到CONNECTED状态。通常操作中句柄将处于这两个状态之一。如果发生不可恢复的错误,如会话过期、身份鉴定失败、应用显示关闭,则句柄进入到closed状态。

Watch:
客户端可以在每个znode节点上设置一个观察,如果被观察服务端的znode节点有变更,那么watch就会被触发,这个watch所属的客户端将接受到一个通知包 告知节点已经发生变化,把相应的事件通知给设置watch的client端。
也即异步回调的触发机制

watch事件理解:

  • 一次触发(one-time-trigger):当数据有了变化时,zookeeperserver向客户端发送一个watch,他是一次性动作,即触发一次就不再有效,类似一次性纸杯。
    只监控一次
    如果想继续watch的话,需要客户端重新设置watcher。所以如果你得到一个watch事件且想在将来的变化继续得到通知,必须设置另一个watch。

  • 发往客户端:watches是异步发往客户端的,zookeeper提供了一个顺序保证,在看到watch事件之前绝不会看到变化,这样不同客户端看到的是一致性的顺序:
    在导致watch事件触发的操作成功码返回到达客户端之前,事件可能在去客户端的路上,但是可能不会到达客户端。观察事件是异步的发送给观察者(客户端)的,zookeeper会保证次序:在收到观察事件之前,客户端不会看到已经为之设置观察的节点的改动。网络延迟或者其他因素可能会让不同的客户端在不同的时间收到观察事件和操作的返回码,这里的要点是:不同客户端看到的事情都有一致的次序

  • 为数据设置watch:数据观察和子节点观察,getData()和exists()设置数据观察,getChildren()设置子节点观察。
    setData()将为znode触发数据观察,成功的create()将为新创建的节点触发数据观察,为其父节点触发子节点观察,成功的delete()将会为被删除的节点触发数据观察以及子节点观察(因为节点不能再有子节点了),为其父节点出发子节点观察
    观察维护在zookeeper服务器中,客户端连接到新的服务器时,所有的会话事件将被触发,同服务器断开连接期间不会收到观察;客户端重新连接时,如果需要,先前已经注册的观察将被重新注册和触发。有一种情况下观察事件将丢失:对还没有创建的节点设置存在观察,而在断开连接期间创建节点,然后删除

一次触发:

public class ZkWatchDemo {
    private final static Logger logger = Logger.getLogger(ZkDemo.class);

    private final static String CONNECT_STRING = "192.168.0.139:2181";
    private final static int SESSION_TIMEOUT = 50*1000;

    //获得zookeeper实例
    public ZooKeeper startZk() throws IOException {
        return new ZooKeeper(CONNECT_STRING, SESSION_TIMEOUT, event -> {
            logger.info("A " + event.toString());
        });
    }

    //关闭zookeeper
    public void stopZk(ZooKeeper zooKeeper) throws InterruptedException {
        if(zooKeeper != null) {
            zooKeeper.close();
        }
    }

    //创建节点
    public void createZnode(ZooKeeper zooKeeper, String nodePath, String nodeValue) throws KeeperException, InterruptedException {
        zooKeeper.create(nodePath, nodeValue.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    public String getZnode(ZooKeeper zooKeeper, String nodePath) throws KeeperException, InterruptedException {
        String result = null;
        //getData()方法调用一次,添加一个数据watch,在set改变数据时,会触发triggerValue()方法
        //触发一次,消耗一次
        byte[] data = zooKeeper.getData(nodePath, event -> {
            try {
                triggerValue(zooKeeper, nodePath);
            } catch (KeeperException | InterruptedException e) {
                e.printStackTrace();
            }
        }, new Stat());
        result = new String(data);
        return result;
    }

    public void triggerValue(ZooKeeper zooKeeper, String nodePath) throws KeeperException, InterruptedException {
        //这里是数据watch被触发后调用的方法
        // false表示不设置watch,如果和上面一样设置了event->{...},那么就会再添加一个数据watch
        byte[] data = zooKeeper.getData(nodePath, false, new Stat());
        logger.info("B " + new String(data));
    }

    public void setZnode(ZooKeeper zooKeeper, String nodePath, String nodeValue) throws KeeperException, InterruptedException {
        zooKeeper.setData(nodePath, nodeValue.getBytes(), 0);
    }

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        ZkWatchDemo zkDemo = new ZkWatchDemo();
        String path = "/wangteng";
        ZooKeeper zooKeeper = zkDemo.startZk();
        if(zooKeeper.exists(path, false) == null) {
            zkDemo.createZnode(zooKeeper, path, "helloworld");
            String nodeValue = zkDemo.getZnode(zooKeeper, path);
            logger.info("create znode and get value: " + nodeValue);
            //设置数据,触发watch
            zkDemo.setZnode(zooKeeper, path, "helloworld_new");
            nodeValue = zkDemo.getZnode(zooKeeper, path);
            logger.info("set znode and get value: " + nodeValue);
            //让主线程等待,在linux上zookeeper客户端执行set方法,因为上面只设置了一个watch,所以不会触发;如果上面设置了多个watch,那么这里可以触发,每set一次,消耗一次
            TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
        } else {
            logger.info("this node is already exist");
        }

        zkDemo.stopZk(zooKeeper);
    }
}

2019-09-17 04:07:54,448 INFO  (com.wang.zookeeper.ZkWatchDemo:main) - create znode and get value: helloworld
2019-09-17 04:07:54,452 INFO  (com.wang.zookeeper.ZkWatchDemo:lambda$startZk$0) - A WatchedEvent state:SyncConnected type:None path:null
2019-09-17 04:07:54,482 INFO  (com.wang.zookeeper.ZkWatchDemo:main) - set znode and get value: helloworld_new
2019-09-17 04:07:54,494 INFO  (com.wang.zookeeper.ZkWatchDemo:triggerValue) - B helloworld_new

[zk: localhost:2181(CONNECTED) 41] set /wangteng helloworld3

多次触发:

	public void triggerValue(ZooKeeper zooKeeper, String nodePath) throws KeeperException, InterruptedException {
        //这里是数据watch被触发后调用的方法
        // 触发一次 就再绑定一次
        byte[] data = zooKeeper.getData(nodePath, event -> {
            try {
                triggerValue(zooKeeper, nodePath);
            } catch (KeeperException | InterruptedException e) {
                e.printStackTrace();
            }
        }, new Stat());
        logger.info("B " + new String(data));
    }

节点变化

你可能感兴趣的:(zookeeper)