Zookeeper 从设计模式角度来看,是一个基于观察者模式设计的分布
式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应,从而实现集群中类似 Master/Slave 管理模式
配置文件通常有如下几种常用的保存方式:
1.将配置信息保存在程序代码中
这种方案简单,但每次修改配置都要重新编译、部署应用程序。
2.将配置信息保存在xml文件或者属性文件中
在参数信息保存在xml或者属性文件中,当需要修改参数时,直接修改 xml 文件。这样无需重新编译,只需重新部署修改的文件即可。
3.将配置信息保存在数据库中
当需要修改配置信息时,直接修改数据库中的数据,然后重启应用程序,或者刷新应用的缓存。
小结:上面的几种方法比较适用于普通的单服务项目;但是, 配置的管理在分布式应用环境中很常见,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。
像这样的配置信息完全可以交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中。
原生zookeeper的事件监听采用Watcher实现,不过Watcher监听是一次性的,如果需要继续监听该事件,必须重新注册。
Curator中采用cache来封装了对事件的监听,在包org.apache.curator.framework.recipes.cache封装了3种类型的事件监听。
cache 分为三种(其实就是从不同的维度去解析cache):
1.PathChildrenCache cache 监听某一个path,一个znode的子节点
2.node cache 监听某一个node
3.tree cache 监听节点以及子节点的状态,监控整个树中的节点
通过addListener添加事件监听,监听变化,提供接口以方便实现者做出相关改变。
下面的代码是apache的curator包里面的api(还有org.I0Itec.zkclient包中的subscribeDataChanges方法可以监听节点的变化,这里不具体描述)。
代码功能图
package app.curator;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.eclipse.jetty.util.ConcurrentHashSet;
import java.lang.reflect.Constructor;
import java.nio.charset.Charset;
import java.util.*;
/**
* Created by lili19289 on 2016/8/18.
*/
public class ZooKeeperClient {
private static final Logger LOG = Logger.getLogger(ZooKeeperClient.class);
public static final char PATH_SEPARATOR_CHAR = '/';
public static final int DEFAULT_BASE_SLEEP_TIME_MS = 1000;
public static final int DEFAULT_MAX_RETRIES = 0;
private ConnectionState connectionState;//连接状态
//连接状态监听
private Set connectionStateListeners = new ConcurrentHashSet();
private Map nodeCacheHandlerMap = new HashMap();
private CuratorFramework client;
/**
* 创建ZooKeeper客户端。
* @param connectString 服务器连接串。
* @param retryPolicy 重试策略。
*/
public ZooKeeperClient(String connectString, RetryPolicy retryPolicy) {
client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
}
/**
* 创建ZooKeeper客户端。
* @param connectString 服务器连接串。
* @param baseSleepTimeMs 重试间隔时间。
* @param maxRetries 最大重试次数。
*/
public ZooKeeperClient(String connectString, int baseSleepTimeMs, int maxRetries) {
client = CuratorFrameworkFactory.newClient(connectString, new ExponentialBackoffRetry(baseSleepTimeMs,
maxRetries));
}
/**
* 创建ZooKeeper客户端。
* @param connectString 服务器连接串。
*/
public ZooKeeperClient(String connectString) {
try {
client = CuratorFrameworkFactory.newClient(connectString, new ExponentialBackoffRetry(
DEFAULT_BASE_SLEEP_TIME_MS, DEFAULT_MAX_RETRIES));
}catch (Exception e){
LOG.error("zookeeperclient connect failed ,the connectString is "+connectString,e);
}
}
/**
* 启动该ZooKeeper客户端。
*/
public void start() {
try {
// 先确认状态,如果已经启动或关闭时调用start会抛异常
if (CuratorFrameworkState.LATENT == client.getState()) {
client.getConnectionStateListenable().addListener(new ClientConnectionStateListener());
client.start();
}
}catch (Exception e){
LOG.error("zookeeperclient start failed ",e);
}
}
/**
* 关闭该ZooKeeper客户端。
*/
public void close() {
client.close();
}
/**
* 创建指定路径,永久路径
* @param path 路径。
*/
public void createPersistent(String path) {
create(path, CreateMode.PERSISTENT);
}
/**
* 创建指定路径,临时路径
* @param path 路径。
*/
public void createEphemeral(String path) {
create(path, CreateMode.EPHEMERAL);
}
/**
* 创建指定路径,如果父路径不存在则抛出异常。
* @param path 路径。
* @param createMode 要创建的路径的类型。
*/
public void create(String path, CreateMode createMode) {
// 先检查路径是否存在,如果已存在则不做操作
if (!exists(path)) {
try {
client.create().creatingParentsIfNeeded()//如果指定的节点的父节点不存在,递归创建父节点
.withMode(createMode)//存储类型(临时的还是持久的)
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//访问权限
.forPath(path);
} catch (Exception e) {
String message = "create path failed, path=" + path;
LOG.error(message, e);
throw new RuntimeException(message, e);
}
}
}
/**
* 删除指定路径,会递归删除其下子路径,如果路径不存在则不操作。
* @param path 路径。
*/
public void delete(String path) {
// 先检查路径是否存在,如果不存在则不做操作
if (exists(path)) {
// 递归删除子路径
delete(path, true);
}
}
/**
* 删除指定路径,如果路径不存在,则抛出异常。
* @param path 路径。
* @param clearChildren 如果为true,则递归删除子路径,否则只删除指定路径,如果路径非空,则抛出异常。
*/
public void delete(String path, boolean clearChildren) {
try {
if (clearChildren) {
// 列出所有子路径
List children = client.getChildren().forPath(path);
if (!children.isEmpty()) {
// 遍历子路径操作
for (String childPath : children) {
// 递归调用删除子路径
delete(path + PATH_SEPARATOR_CHAR + childPath, true);
}
}
}
// 删除本路径
client.delete().forPath(path);
} catch (Exception e) {
String message = "delete path failed, path=" + path;
LOG.error(message, e);
throw new RuntimeException(message, e);
}
}
/**
* 检查指定路径是否存在。
* @param path 路径。
* @return 如果存在则返回true,否则返回false。
*/
public boolean exists(String path) {
try {
// 如果返回的Stat不为null则表示该路径存在
return null != client.checkExists().forPath(path);
} catch (Exception e) {
String message = "check exists failed, path=" + path;
LOG.error(message, e);
throw new RuntimeException(message, e);
}
}
/**
* 获取指定路径中的子路径名。
* @param path 要获取子路径的父路径。
* @return 子路径名,如果父路径不存在,则返回空白列表。
*/
public List getChildren(String path) {
try {
// 先检查路径是否存在,如果存在则获取子路径名
if (exists(path)) {
return client.getChildren().forPath(path);
} else {// 如果该路径不存在则返回空列表
return Collections.emptyList();
}
} catch (Exception e) {
String message = "get children failed, path=" + path;
LOG.error(message, e);
throw new RuntimeException(message, e);
}
}
public void mkdirs(String path) {
mkdirs(path, CreateMode.PERSISTENT);
}
/**
* 创建路径,如果父路径不存在,会自动创建。
* @param path 路径。
* @param createMode 要创建的路径的类型。
*/
public void mkdirs(String path, CreateMode createMode) {
// 先检查路径是否存在,如果已存在则不做操作
if (!exists(path)) {
try {
client.create().creatingParentsIfNeeded().withMode(createMode).forPath(path);
} catch (Exception e) {
String message = "create path failed, path=" + path;
LOG.error(message, e);
throw new RuntimeException(message, e);
}
}
}
public void setData( String path, String data) {
try{
if(!exists(path)){
mkdirs(path);
}
if(StringUtils.isBlank(data)) client.setData().forPath(path);
// set data for the given node
client.setData().forPath(path,data.getBytes());
}catch(Exception e){
String message = "set data failed, path=" + path;
LOG.error(message, e);
}
}
/**
* 获取指定路径上的数据。
* @param path 路径。
* @return 路径上的数据,如果路径不存在,则返回null。
*/
public String getStringData(String path) {
byte[] bytes = getData(path);
return null == bytes ? null : new String(bytes, Charset.forName("UTF-8"));
}
/**
* 获取指定路径上的数据。
* @param path 路径。
* @return 路径上的数据,如果路径不存在,则返回null。
*/
public byte[] getData(String path) {
try {
// 先检查路径是否存在,如果存在则获取节点数据
if (exists(path)) {
byte[] bytes = client.getData().forPath(path);
if (null == bytes) {
return null;
}
return bytes;
} else {// 如果路径不存在则返回null
return null;
}
} catch (Exception e) {
String message = "get data failed, path=" + path;
LOG.error(message, e);
throw new RuntimeException(message, e);
}
}
public CuratorFramework getCuratorFramework(){
return client;
}
/**
* node监听,一个监听方法
* 该方法是直接实现NodeCacheListener来实现监听
* @param path
* @param listenerClass
*/
public void addNodeCacheListener(String path,Class extends ZooKeeperNodeCacheListener> listenerClass){
final NodeCache nodeCache = new NodeCache(client, path);
Class[] paramTypes = {NodeCache.class};
Object[] params = {nodeCache};
Constructor con=null;
ZooKeeperNodeCacheListener listener=null;
try {
con = listenerClass.getConstructor(paramTypes);
listener =(ZooKeeperNodeCacheListener) con.newInstance(params);
nodeCache.getListenable().addListener(listener);
nodeCache.start(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* node监听,一个或以上监听方法
* 该方法是通过实现NodeCacheListener,然后对自己定义的NodeCahceChangeListener的接口进行调用来实现的监听
* @param path
* @param listeners
*/
public void addNodeCacheListeners(String path, ZooKeeperNodeCacheHandler.NodeCahceChangeListener...listeners){
ZooKeeperNodeCacheHandler nodeCacheListeners = getZooKeeperNodeCacheListeners(path);
nodeCacheListeners.addListeners(listeners);
}
private ZooKeeperNodeCacheHandler getZooKeeperNodeCacheListeners(String path){
ZooKeeperNodeCacheHandler nodeCacheListeners =nodeCacheHandlerMap.get(path);
if(nodeCacheListeners==null){
NodeCache nodeCache = new NodeCache(client,path);
nodeCacheListeners = new ZooKeeperNodeCacheHandler(path,nodeCache);
nodeCacheListeners.startWatch();
nodeCacheHandlerMap.put(path,nodeCacheListeners);
}
return nodeCacheListeners;
}
/**
* Path Cache:
* 监视一个路径下
* 1)孩子结点的创建、2)孩子结点的删除,3)以及孩子结点数据的更新。
* 产生的事件会传递给注册的PathChildrenCacheListener。
* @param path
* @param listener
*/
public void addPathChildrenListener(String path,PathChildrenCacheListener listener){
if(!exists(path))mkdirs(path);
try {
PathChildrenCache childrenCache = new PathChildrenCache(client, path,true);
childrenCache.getListenable().addListener(listener);
childrenCache.start();
// childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
} catch (Exception e) {
e.printStackTrace();
LOG.error("PathChildrenCache listen fail,path = "+path,e);
}
}
/**
* 监控 指定节点和节点下的所有的节点的变化--无限监听 可以进行本节点的删除(不在创建)
* @param path
* @param listener
*/
public void addTreeCacheListener(String path, TreeCacheListener listener){
if(!exists(path))mkdirs(path);
try {
TreeCache treeCache = new TreeCache(client,path);
treeCache.getListenable().addListener(listener);
treeCache.start();
}catch (Exception e){
LOG.error(" zookeeper treeCache listener failed ,the path is "+path,e);
}
}
/*
********************************************************************************************************8
*/
/**
* 连接状态监听实现
*/
private class ClientConnectionStateListener implements ConnectionStateListener {
public void stateChanged(CuratorFramework client, ConnectionState newState) {
connectionState = newState;
notifyConnectionStateListener(newState);
}
}
private void notifyConnectionStateListener(ConnectionState state) {
if (state.isConnected()) {
for (ZooKeeperClientConnectionStateListener listener : connectionStateListeners) {
listener.notifyConnected();
}
} else {
for (ZooKeeperClientConnectionStateListener listener : connectionStateListeners) {
listener.notifyDisconnected();
}
}
}
private void notifyConnectionStateListener(ConnectionState state,
ZooKeeperClientConnectionStateListener listener) {
if (state.isConnected()) {
listener.notifyConnected();
} else {
listener.notifyDisconnected();
}
}
/**
* 添加客户端连接状态监听
* @param listener 客户端连接状态监听器
*/
public void addClientConnectionStateListener(ZooKeeperClientConnectionStateListener listener) {
// 任何情况下都添加,启动后才通知
if (connectionStateListeners.add(listener) && CuratorFrameworkState.STARTED == client.getState()) {
notifyConnectionStateListener(connectionState, listener);
}
}
}
package app.curator;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
/**
* Created by lili19289 on 2016/8/19.
* NodeCache主要用来监听节点本身的变化,当节点的状态发生变更后,会回调NodeCachaListener
* 可以继承这个类进行具体操作
*/
public class ZooKeeperNodeCacheListener implements NodeCacheListener {
protected NodeCache nodeCache;
public ZooKeeperNodeCacheListener(NodeCache nodeCache){
this.nodeCache = nodeCache;
}
public void nodeChanged() throws Exception {
System.out.println("NodeCache changed, data is: " + new String(nodeCache.getCurrentData().getData()));
}
}
package app.curator;
import app.utils.IoUtil;
import org.apache.commons.io.IOUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.log4j.Logger;
/**
* Created by lili19289 on 2016/8/20.
* 监视一个路径下
* 1)孩子结点的创建、2)孩子结点的删除,3)以及孩子结点数据的更新。
* 不监听自己节点的操作
* 产生的事件会传递给注册的PathChildrenCacheListener。
*/
public class ZooKeeperPathChildrenCacheListener implements PathChildrenCacheListener {
private static final Logger LOG = Logger.getLogger(ZooKeeperPathChildrenCacheListener.class);
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) {
ChildData data = pathChildrenCacheEvent.getData();
try{
PathChildrenCacheEvent.Type eventType = pathChildrenCacheEvent.getType();
switch (eventType) {
case CHILD_ADDED:
System.out.println("CHILD_ADDED : "+ data.getPath() +" 数据:"+ new String(data.getData(), IoUtil.CHARSET_UTF8));
break;
case CHILD_REMOVED:
System.out.println("CHILD_REMOVED : "+ data.getPath() +" 数据:"+ new String(data.getData(), IoUtil.CHARSET_UTF8));
break;
case CHILD_UPDATED:
System.out.println("CHILD_UPDATED : "+ data.getPath() +" 数据:"+ new String(data.getData(), IoUtil.CHARSET_UTF8));
break;
default:
break;
}
}catch (Exception e){
LOG.error("ZooKeeperPathChildrenCacheListener listen failed,the path is "+data.getPath() ,e);
}
}
}
package app.curator;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
import org.apache.log4j.Logger;
/**
* Created by lili19289 on 2016/8/20.
*/
public class ZooKeeperTreeCacheListener implements TreeCacheListener {
private static final Logger LOG = Logger.getLogger(ZooKeeperTreeCacheListener.class);
public void childEvent(CuratorFramework client, TreeCacheEvent event) {
ChildData data = event.getData();
try {
if (data != null) {
switch (event.getType()) {
case NODE_ADDED:
System.out.println("treeCache ----- NODE_ADDED : " + data.getPath() + " 数据:" + new String(data.getData()));
break;
case NODE_REMOVED:
System.out.println("treeCache ----- NODE_REMOVED : " + data.getPath() + " 数据:" + new String(data.getData()));
break;
case NODE_UPDATED:
System.out.println("treeCache ----- NODE_UPDATED : " + data.getPath() + " 数据:" + new String(data.getData()));
break;
default:
break;
}
} else {
System.out.println("data is null : " + event.getType());
}
}
catch(Exception e) {
LOG.error("ZooKeeperTreeCacheListener listen failed,the path is "+data.getPath() ,e);
}
}
}
package app.curator;
import app.context.RuntimeContext;
import org.apache.log4j.Logger;
/**
* Created by lili19289 on 2016/8/19.
*/
public class ZooKeeperLoader {
private static final Logger LOG = Logger.getLogger(ZooKeeperLoader.class);
ZooKeeperClient zooKeeperClient;
public void init(){
try {
zooKeeperClient = RuntimeContext.getBean(ZooKeeperClient.class);
zooKeeperClient.addNodeCacheListener("/learning",ZooKeeperNodeCacheListener.class);
// zooKeeperClient.addPathChildrenListener("/learning",new ZooKeeperPathChildrenCacheListener());
// zooKeeperClient.addTreeCacheListener("/learning",new ZooKeeperTreeCacheListener());
// zooKeeperClient.addNodeCacheListener("/learning");
}catch (Exception e){
e.printStackTrace();
LOG.error("ZooKeeperLoader connect zookeeper failed ",e);
}
}
}