转自:http://blog.csdn.net/lzy_lizhiyang/article/details/48518731
使用Java操作zookeeper时,一般有两种方式:使用zkclient或者curator,相比较来说,curator的使用较为简便。今天就来看看如何使用curator来操作zookeeper。
需要的依赖如下:
org.apache.curator curator-framework 2.8.0 org.apache.curator curator-client 2.8.0 org.apache.zookeeper zookeeper 3.4.6 com.google.guava guava 16.0.1
首先,我们创建一个客户端类,来真正的操作zookeeper,包括创建连接、创建节点,写入值、读取值等。
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import org.apache.commons.lang.StringUtils; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.api.CuratorWatcher; import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.RetryNTimes; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CuratorZookeeperClient { private final int CONNECT_TIMEOUT = 15000; private final int RETRY_TIME = Integer.MAX_VALUE; private final int RETRY_INTERVAL = 1000; private static final Logger logger = LoggerFactory.getLogger(CuratorZookeeperClient.class); private CuratorFramework curator; private volatile static CuratorZookeeperClient instance; /** * key:父路径,如/jobcenter/client/goodscenter * value:Map-->key:子路径,如/jobcenter/client/goodscenter/goodscenter00000001 * value:路径中的值 */ private static ConcurrentHashMap> zkCacheMap = new ConcurrentHashMap >(); public static Map > getZkCacheMap() { return zkCacheMap; } private CuratorFramework newCurator(String zkServers) { return CuratorFrameworkFactory.builder().connectString(zkServers) .retryPolicy(new RetryNTimes(RETRY_TIME, RETRY_INTERVAL)) .connectionTimeoutMs(CONNECT_TIMEOUT).build(); } private CuratorZookeeperClient(String zkServers) { if(curator == null) { curator = newCurator(zkServers); curator.getConnectionStateListenable().addListener(new ConnectionStateListener() { public void stateChanged(CuratorFramework client, ConnectionState state) { if (state == ConnectionState.LOST) { //连接丢失 logger.info("lost session with zookeeper"); } else if (state == ConnectionState.CONNECTED) { //连接新建 logger.info("connected with zookeeper"); } else if (state == ConnectionState.RECONNECTED) { logger.info("reconnected with zookeeper"); //连接重连 for(ZkStateListener s:stateListeners){ s.reconnected(); } } } }); curator.start(); } } public static CuratorZookeeperClient getInstance(String zkServers) { if(instance == null) { synchronized(CuratorZookeeperClient.class) { if(instance == null) { logger.info("initial CuratorZookeeperClient instance"); instance = new CuratorZookeeperClient(zkServers); } } } return instance; } /** * 写数据:/docker/jobcenter/client/app/app0..../app1...../app2 * @param path * @param content * * @return 返回真正写到的路径 * @throws Exception */ public String write(String path,String content) throws Exception { StringBuilder sb = new StringBuilder(path); String writePath = curator.create().creatingParentsIfNeeded() .withMode(CreateMode.EPHEMERAL_SEQUENTIAL) .forPath(sb.toString(), content.getBytes("utf-8")); return writePath; } /** * 随机读取一个path子路径 * 先从cache中读取,如果没有,再从zookeeper中查询 * @param path * @return * @throws Exception */ public String readRandom(String path) throws Exception { String parentPath = path; Map cacheMap = zkCacheMap.get(path); if(cacheMap != null && cacheMap.size() > 0) { logger.debug("get random value from cache,path="+path); return getRandomValue4Map(cacheMap); } if(curator.checkExists().forPath(path) == null) { logger.debug("path [{}] is not exists,return null",path); return null; } else { logger.debug("read random from zookeeper,path="+path); cacheMap = new HashMap (); List list = curator.getChildren().usingWatcher(new ZKWatcher(parentPath,path)).forPath(path); if(list == null || list.size() == 0) { logger.debug("path [{}] has no children return null",path); return null; } Random rand = new Random(); String child = list.get(rand.nextInt(list.size())); path = path + "/" + child; byte[] b = curator.getData().usingWatcher(new ZKWatcher(parentPath,path)) .forPath(path); String value = new String(b,"utf-8"); if(StringUtils.isNotBlank(value)) { cacheMap.put(path, value); zkCacheMap.put(parentPath, cacheMap); } return value; } } /** * 读取path下所有子路径下的内容 * 先从map中读取,如果不存在,再从zookeeper中查询 * @param path * @return * @throws Exception */ public List readAll(String path) throws Exception { String parentPath = path; Map cacheMap = zkCacheMap.get(path); List list = new ArrayList (); if(cacheMap != null) { logger.debug("read all from cache,path="+path); list.addAll(cacheMap.values()); return list; } if(curator.checkExists().forPath(path) == null) { logger.debug("path [{}] is not exists,return null",path); return null; } else { cacheMap = new HashMap (); List children = curator.getChildren().usingWatcher(new ZKWatcher(parentPath,path)).forPath(path); if(children == null || children.size() == 0) { logger.debug("path [{}] has no children,return null",path); return null; } else { logger.debug("read all from zookeeper,path="+path); String basePath = path; for(String child : children) { path = basePath + "/" + child; byte[] b = curator.getData().usingWatcher(new ZKWatcher(parentPath,path)) .forPath(path); String value = new String(b,"utf-8"); if(StringUtils.isNotBlank(value)) { list.add(value); cacheMap.put(path, value); } } } zkCacheMap.put(parentPath, cacheMap); return list; } } /** * 随机获取Map中的一个值 * @param map * @return */ private String getRandomValue4Map(Map map) { Object[] values = map.values().toArray(); Random rand = new Random(); return values[rand.nextInt(values.length)].toString(); } public void delete(String path) throws Exception { if(curator.checkExists().forPath(path) != null) { curator.delete().inBackground().forPath(path); zkCacheMap.remove(path); } } /** * 获取路径下的所有子路径 * @param path * @return */ public List getChildren(String path) throws Exception { if(curator.checkExists().forPath(path) == null) { logger.debug("path [{}] is not exists,return null",path); return null; } else { List children = curator.getChildren().forPath(path); return children; } } public void close() { if(curator != null) { curator.close(); curator = null; } zkCacheMap.clear(); } /** * zookeeper监听节点数据变化 * @author lizhiyang * */ private class ZKWatcher implements CuratorWatcher { private String parentPath; private String path; public ZKWatcher(String parentPath,String path) { this.parentPath = parentPath; this.path = path; } public void process(WatchedEvent event) throws Exception { Map cacheMap = zkCacheMap.get(parentPath); if(cacheMap == null) { cacheMap = new HashMap (); } if(event.getType() == Event.EventType.NodeDataChanged || event.getType() == Event.EventType.NodeCreated){ byte[] data = curator.getData(). usingWatcher(this).forPath(path); cacheMap.put(path, new String(data,"utf-8")); logger.info("add cache={}",new String(data,"utf-8")); } else if(event.getType() == Event.EventType.NodeDeleted) { cacheMap.remove(path); logger.info("remove cache path={}",path); } else if(event.getType() == Event.EventType.NodeChildrenChanged) { //子节点发生变化,重新进行缓存 cacheMap.clear(); List children = curator.getChildren().usingWatcher(new ZKWatcher(parentPath,path)).forPath(path); if(children != null && children.size() > 0) { for(String child : children) { String childPath = parentPath + "/" + child; byte[] b = curator.getData().usingWatcher(new ZKWatcher(parentPath,childPath)) .forPath(childPath); String value = new String(b,"utf-8"); if(StringUtils.isNotBlank(value)) { cacheMap.put(childPath, value); } } } logger.info("node children changed,recaching path={}",path); } zkCacheMap.put(parentPath, cacheMap); } } private final Set stateListeners = new CopyOnWriteArraySet (); public void addStateListener(ZkStateListener listener) { stateListeners.add(listener); }
其中,我们对节点和值进行了缓存,避免频繁的访问zookeeper。在对zookeeper操作时,对连接丢失、连接新建、重连等事件进行了监听,使用到了类ZkStateListener
public interface ZkStateListener { void reconnected(); }
下面,我们就使用ZkDockerService类来封住客户端的操作。
import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * jobclient docker改造 * 注册应用信息至zookeeper * @author lizhiyang * */ public class ZkDockerService { private static final Logger logger = LoggerFactory.getLogger(ZkDockerService.class); private CuratorZookeeperClient zkClient; private SetzkPathList = new HashSet (); // 失败重试定时器,定时检查是否有请求失败,如有,无限次重试 private ScheduledFuture> retryFuture; // 定时任务执行器 private final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("RegistryFailedRetryTimer", true)); //需要重新注册的数据 private Set retrySet = new HashSet (); /** * init-method,初始化执行 * 将本机docker的IP地址 端口都注册到zookeeper中 */ public void register2Zookeeper() { try { zkClient = CuratorZookeeperClient.getInstance(ZOOKEEPER_ADDRESS); ClientData client = findClientData(); registerClientData(client); zkClient.addStateListener(new ZkStateListener(){ @Override public void reconnected() { ClientData client = findClientData(); //将服务添加到重试列表 retrySet.add(client); } }); //启动线程进行重试,1秒执行一次,因为jobcenter的定时触发时间最短的是1秒 this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() { public void run() { // 检测并连接注册中心 try { retryRegister(); } catch (Throwable t) { // 防御性容错 logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t); } } }, 1, 1, TimeUnit.SECONDS); } catch (Exception e) { logger.error("zookeeper write exception",e); } } /** * destrory-method,销毁时执行 */ public void destroy4Zookeeper() { logger.info("zkDockerService destrory4Zookeeper path="+zkPathList); try { if(retryFuture != null){ retryFuture.cancel(true); } } catch (Throwable t) { logger.warn(t.getMessage(), t); } if(zkPathList != null && zkPathList.size() > 0) { for(String path : zkPathList) { try { zkClient.delete(path); } catch (Exception e) { logger.error("zkDockerService destrory4Zookeeper exception",e); } } } zkClient.close(); } /** 构造要存储的对象 **/ private ClientData findClientData() { ClientData client = new ClientData(); client.setIpAddress(ip); client.setPort(port); client.setSource(1); return client; } /** 将值写入zookeeper中 **/ private void registerClientData(ClientData client) throws Exception{ String centerPath = "/server"; String content = ""; String strServer = zkClient.write(centerPath, content); if(!StringUtils.isBlank(strServer)) { zkPathList.add(strServer); } } /** * 重连到zookeeper时,自动重试 */ protected synchronized void retryRegister() { if(!retrySet.isEmpty()){ logger.info("jobclient begin retry register client to zookeeper"); Set retryClients = new HashSet (retrySet); for(ClientData data :retryClients){ logger.info("retry register="+data); try { registerJobcenterClient(data); retrySet.remove(data); } catch (Exception e) { logger.error("registerJobcenterClient failed",e); } } } } }
其中在使用定时任务时,使用到了NamedThreadFactory,如下:
import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; public class NamedThreadFactory implements ThreadFactory { private static final AtomicInteger POOL_SEQ = new AtomicInteger(1); private final AtomicInteger mThreadNum = new AtomicInteger(1); private final String mPrefix; private final boolean mDaemo; private final ThreadGroup mGroup; public NamedThreadFactory() { this("pool-" + POOL_SEQ.getAndIncrement(),false); } public NamedThreadFactory(String prefix) { this(prefix,false); } public NamedThreadFactory(String prefix,boolean daemo) { mPrefix = prefix + "-thread-"; mDaemo = daemo; SecurityManager s = System.getSecurityManager(); mGroup = ( s == null ) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup(); } public Thread newThread(Runnable runnable) { String name = mPrefix + mThreadNum.getAndIncrement(); Thread ret = new Thread(mGroup,runnable,name,0); ret.setDaemon(mDaemo); return ret; } public ThreadGroup getThreadGroup() { return mGroup; } }