配置信息的集中管理与共享是分布式应用的基本需求。对于此类需求,ZooKeeper可以提供两个层面的特性。一个是提供高可用存储服务,允许分布式应用的参与者更新、读取ZooKeeper中的配置信息。另一个是主动配置服务,当ZooKeeper中的配置信息发生变更时,主动通知设置了监视器的客户端,客户端收到通知后,立刻执行相应操作。
简化起见,本示例有两个前置条件。
第一,配置项的名称直接使用znode的路径表示,而znode中存储的字符串值就是配置项的值。
第二,在任何时间,只有单个客户端对配置执行更新操作。
对ZooKeeper执行读写逻辑封装在ActiveKeyValues中,代码如下:
public class ActiveKeyValueStore extends ConnectionWatcher {
private static final charset CHARSET = charset.forName("UTF-8");
public void write(String path, String value) throws InterruptedException,
KeeperException {
Stat stat = zk.exists(path, false);
if (stat == null) {
zk.create(path, value.getBytes(CHARSET), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
} else {
zk.setData(path, value.getBytes(CHARSET), -1);
}
}
public String read(String path, Watcher watcher) throws InteruptedException,
KeeperException {
byte[] data = zk.getData(path, watcher, null/*stat*/);
return new String(data, CHARSET);
}
}
以上代码中,write方法负责写操作,如果znode已经存在则直接设置其值,如果不存在先创建znode再设置其值。写操作返回znode中保存的值,最重要的是read方法中的Wacher参数,读客户端需要注册此监视器以便接收通知。ConnectionWatcher基类负责创建ZooKeeper实例、用户身份认证及与ZooKeeper服务器的连接。
ConfigUpdater类按随机时间间隔更新ZooKeeper中的配置项,充当更新配置客户端,代码如下:
public class ConfigUpdate {
public static final String PATH = "/config";
private ActiveKeyValueStore store;
private Random random = new Random();
public ConfigUpdater(String hosts) throws IOException, InterruptedException {
store = new ActiveKeyValueStore();
store.connect(hosts);
}
public void run throws InterruptedException, KeeperException {
while (ture) {
String value = random.nextInt(100) + '';
store.write(PATH,value);
System.out.printf("Set %s to %s\n", PATH, value);
TimeUnit.SECONDS.sleep(random.nextInt(10));
}
}
public static void main(String[] args) throws Exception {
ConfigUpdater configUpdater = new ConfigUpdater(args[0]);
configUpdater.run();
}
}
ConfigWatcher作为配置服务的读客户端,代码如下:
public class ConfigWatcher implements Watcher {
private ActiveKeyValueStore store;
public configWatcher(String hosts) throws IOException, InterreptedException {
store = new ActiveKeyValueStore();
store.connect(hosts);
}
public void displayConfig() throws InterruptedException, KeeperException {
String value = store.read(ConfigUpdater.PATH, this);
System.out.printf("Read %s as %s\n", ConfigUpdater.PATH, value);
}
@Override
public void process(WatcherEvent event) {
if (event.getType() == EventType.NodeDataChanged) {
try {
displayConfig();
} catch (InterruptedException e) {
System.err.println("Interrupted. Exiting.");
Thread.currentThread().interrupted();
} catch (KeeperException e) {
System.err.printf("KeeperException: %s. Exiting.\n", e);
}
}
}
public static void main(String[] args) throws Exception {
ConfigWatcher configWatcher = new ConfigWatcher(args[0]);
configWatcher.displayConfig();
Thread.sleep(Long.MAX_VALUE);
}
}
代码中的主要逻辑在process中实现,当配置发生变更时,客户端收到通知后显示新的配置内容。