分布式架构——第2篇:ZooKeeper API实战

ZooKeeper每个节点(称为znode),除了本身拥有一部分数据外(<1M),还能拥有子节点,当子节点上数据发生变化,或者其子节点发生变化时,基于Watcher机制会发出相应的通知给订阅其状态变化的客户端。

导入jar包

通过MyEclispe->Build Path->Add External Archives…将以下两个目录中的jar包全部导入工程。

zookeeper-3.4.12/
zookeeper-3.4.12/lib

实现Watcher接口

public class ZooKeeperWatcher implements Watcher{

@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case NodeDeleted:
System.out.println("Node Deleted");
break;
case NodeChildrenChanged:
System.out.println("Node Children Changed");
break;
case NodeCreated:
System.out.println("Node Created");
break;
case NodeDataChanged:
System.out.println("Node Data Changed");
break;
default:
System.out.println("Node Watcher Event(default)");
break;
}
}
}

设置host。这里再配置一下log4j,不然console会有警告出现。

private static final String host = "127.0.0.1";
//BasicConfigurator.configure();//log4j

ZooKeeper API实战

主要包括以下几个常用API:exists, create, getData, setData, delete。

create

一般,create之前需要判断存在性,如果不存在exist返回的Stat会为null。

ZooKeeper zooKeeper = new ZooKeeper(host, 5000, new ZooKeeperWatcher());

Stat stat = zooKeeper.exists("/root", new ZooKeeperWatcher());
if (stat == null) {
zooKeeper.create("/root", "root data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}

getData

getData所获得的数据为byte[],需要转化为String才能正常显示。注意:每个znode上存储的数据不大于1M。

byte[] data = zooKeeper.getData("/root", new ZooKeeperWatcher(), stat);
String str = new String(data);
System.out.println(str);

setData

第二个参数为版本号,-1表示匹配所有版本。

zooKeeper.setData("/root", "hello".getBytes(), -1);

delete

同上。

zooKeeper.delete("/root", -1);

ZooKeeper节点

发现ZooKeeper本身会创建一些节点,同时节点会被固化到硬盘。

关于ZooKeeper自动生成的节点

主要有三个节点:/, /zookeeper, /zookeeper/quota。注意:这里的root节点是上面的代码创建的,本来是不存在的。

[zk: localhost:2181(CONNECTED) 3] ls /
[zookeeper, root]
[zk: localhost:2181(CONNECTED) 4] ls /zookeeper
[quota]

分别获取/,/zookeeper以及/zookeeper/quota中的数据,发现均为空。

data = zooKeeper.getData("/", new ZooKeeperWatcher(), stat);
str = new String(data);
System.out.println("["+str+"]");
//zookeeper
data = zooKeeper.getData("/zookeeper", new ZooKeeperWatcher(), stat);
str = new String(data);
System.out.println("["+str+"]");
//zookeeper/quota
data = zooKeeper.getData("/zookeeper/quota", new ZooKeeperWatcher(), stat);
str = new String(data);
System.out.println("["+str+"]");

控制台输出为

[]
[]
[]

znode是否被存到了硬盘?

这里使用zkServer.sh停止了ZooKeeper之后,又重新启动。

$ ./zkServer.sh stop
$ ./zkServer.sh start

然后,使用zkCli进行一番查看,发现之前注册的root节点依然存在。因此,可以初步判断,节点是被存储于硬盘中的,而非仅位于内存中。

$ ./zkCli.sh

但是,这里需要注意创建节点类型应该为CreateMode.PERSISTENT

附录

package com.soa.zookeeper;

import org.apache.log4j.BasicConfigurator;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class ZooKeeperWatcher implements Watcher{

@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case NodeDeleted:
System.out.println("Node Deleted");
break;
case NodeChildrenChanged:
System.out.println("Node Children Changed");
break;
case NodeCreated:
System.out.println("Node Created");
break;
case NodeDataChanged:
System.out.println("Node Data Changed");
break;
default:
System.out.println("Node Watcher Event(default)");
break;
}
}

private static final String host = "127.0.0.1";

public static void main(String[] args) throws Exception {
//BasicConfigurator.configure();//log4j
ZooKeeper zooKeeper = new ZooKeeper(host, 5000, new ZooKeeperWatcher());

Stat stat = zooKeeper.exists("/root", new ZooKeeperWatcher());
if (stat == null) {
zooKeeper.create("/root", "root data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
byte[] data = zooKeeper.getData("/root", new ZooKeeperWatcher(), stat);
String str = new String(data);
System.out.println(str);

zooKeeper.setData("/root", "hello".getBytes(), -1);

data = zooKeeper.getData("/root", new ZooKeeperWatcher(), stat);
str = new String(data);
System.out.println(str);

stat = zooKeeper.exists("/root/child", new ZooKeeperWatcher());
if (stat == null) {
zooKeeper.create("/root/child", "root child data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}

data = zooKeeper.getData("/root/child", new ZooKeeperWatcher(), stat);
str = new String(data);
System.out.println(str);

zooKeeper.delete("/root/child", -1);
zooKeeper.delete("/root", -1);
}

}

References: [1] log4j WARN 和 SLF4J WARN 解决办法 [2] Java中String和byte[]间的转换浅析 [3] 大型分布式网站架构设计与实践.陈康贤著