package com.dianping.pigeon.registry.zookeeper; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.RandomUtils; import org.apache.curator.CuratorZookeeperClient; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; 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.KeeperException; import org.apache.zookeeper.data.Stat; import com.dianping.pigeon.config.ConfigChangeListener; import com.dianping.pigeon.config.ConfigManager; import com.dianping.pigeon.config.ConfigManagerLoader; import com.dianping.pigeon.log.Logger; import com.dianping.pigeon.log.LoggerLoader; import com.dianping.pigeon.monitor.Monitor; import com.dianping.pigeon.monitor.MonitorLoader; import com.dianping.pigeon.registry.listener.RegistryEventListener; import com.dianping.pigeon.threadpool.DefaultThreadFactory; public class CuratorClient { private static final String CHARSET = "UTF-8"; private static Logger logger = LoggerLoader.getLogger(CuratorClient.class); private ConfigManager configManager = ConfigManagerLoader.getConfigManager(); private CuratorFramework client; private volatile int retries = configManager.getIntValue("pigeon.registry.curator.retries", Integer.MAX_VALUE); private volatile int retryInterval = configManager.getIntValue("pigeon.registry.curator.retryinterval", 3000); private volatile int retryLimit = configManager.getIntValue("pigeon.registry.curator.retrylimit", 50); private int sessionTimeout = configManager.getIntValue("pigeon.registry.curator.sessiontimeout", 30 * 1000); private int connectionTimeout = configManager.getIntValue("pigeon.registry.curator.connectiontimeout", 15 * 1000); private static ExecutorService curatorStateListenerThreadPool = Executors .newCachedThreadPool(new DefaultThreadFactory("Pigeon-Curator-State-Listener")); private static ExecutorService curatorEventListenerThreadPool = Executors .newCachedThreadPool(new DefaultThreadFactory("Pigeon-Curator-Event-Listener")); private static Monitor monitor = MonitorLoader.getMonitor(); private static final String KEY_REGISTRY_ADDRESS = "pigeon.registry.address"; private volatile String address; private final String EVENT_NAME = "Pigeon.registry"; public CuratorClient(String zkAddress) throws Exception { newCuratorClient(zkAddress); } public CuratorClient() throws Exception { String zkAddress = configManager.getStringValue(KEY_REGISTRY_ADDRESS); newCuratorClient(zkAddress); } private void newCuratorClient(String zkAddress) throws Exception { if (StringUtils.isBlank(zkAddress)) { throw new IllegalArgumentException("zookeeper address is required"); } logger.info("start to initialize zookeeper client:" + zkAddress); this.address = zkAddress; newCuratorClient(); curatorStateListenerThreadPool.execute(new CuratorStateListener()); configManager.registerConfigChangeListener(new InnerConfigChangeListener()); logger.info("succeed to initialize zookeeper client:" + zkAddress); } private boolean newCuratorClient() throws InterruptedException { logger.info("begin to create zookeeper client:" + address); // CuratorFramework client = CuratorFrameworkFactory.newClient(address, // sessionTimeout, connectionTimeout, // new MyRetryPolicy(retries, retryInterval)); CuratorFramework client = CuratorFrameworkFactory.builder().connectString(address) .sessionTimeoutMs(sessionTimeout).connectionTimeoutMs(connectionTimeout) .retryPolicy(new MyRetryPolicy(retries, retryInterval)).build(); client.getConnectionStateListenable().addListener(new ConnectionStateListener() { @Override public void stateChanged(CuratorFramework client, ConnectionState newState) { logger.info("zookeeper state changed to " + newState); if (newState == ConnectionState.RECONNECTED) { RegistryEventListener.connectionReconnected(); } monitor.logEvent(EVENT_NAME, "zookeeper:" + newState.name().toLowerCase(), ""); } }); client.getCuratorListenable().addListener(new CuratorEventListener(this), curatorEventListenerThreadPool); client.start(); boolean isConnected = client.getZookeeperClient().blockUntilConnectedOrTimedOut(); CuratorFramework oldClient = this.client; this.client = client; close(oldClient); if (isConnected) { logger.info("succeed to connect to zookeeper:" + address); monitor.logEvent(EVENT_NAME, "zookeeper:rebuild_success", ""); } else { logger.warn("unable to connect to zookeeper:" + address); monitor.logEvent(EVENT_NAME, "zookeeper:rebuild_failure", ""); } return isConnected; } public CuratorFramework getClient() { return client; } public boolean isConnected() { final CuratorFramework cf = getClient(); if (cf != null) { try { return cf.getZookeeperClient().getZooKeeper().getState().isConnected() && cf.getZookeeperClient().isConnected(); } catch (Exception e) { return false; } } return false; } private class CuratorStateListener implements Runnable { private final Logger logger = LoggerLoader.getLogger(CuratorStateListener.class); public void run() { long sleepTime = retryInterval; int failCount = 0; boolean isSuccess = true; while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(sleepTime * (1 + RandomUtils.nextInt(20))); final CuratorFramework cf = getClient(); if (cf != null) { int retryCount = ((MyRetryPolicy) cf.getZookeeperClient().getRetryPolicy()).getRetryCount(); boolean isConnected = false; try { isConnected = isConnected(); } catch (Exception e) { logger.info("error with zookeeper client's connection:" + e.toString()); } if (isConnected) { if (!isSuccess) { logger.info("begin to rebuild zookeeper client after reconnected"); isSuccess = rebuildCuratorClient(); logger.info("succeed to rebuild zookeeper client"); } failCount = 0; } else { failCount++; if (retryCount > 0) { logger.info("zookeeper client's retries:" + retryCount + ", fails:" + failCount + ", limit:" + retryLimit); } } if (failCount > retryLimit) { logger.info("begin to rebuild zookeeper client after " + retryCount + "/" + failCount + " retries"); isSuccess = rebuildCuratorClient(); logger.info("succeed to rebuild zookeeper client"); failCount = 0; } } } catch (Throwable e) { logger.warn("[curator-state] task failed:", e); } } } } private boolean rebuildCuratorClient() throws InterruptedException { boolean isSuccess = newCuratorClient(); if (isSuccess) { RegistryEventListener.connectionReconnected(); } return isSuccess; } private static class MyRetryPolicy extends RetryNTimes { private final int sleepMsBetweenRetries; private int retryCount; public MyRetryPolicy(int n, int sleepMsBetweenRetries) { super(n, sleepMsBetweenRetries); this.sleepMsBetweenRetries = sleepMsBetweenRetries; } @Override protected int getSleepTimeMs(int retryCount, long elapsedTimeMs) { this.retryCount = retryCount; return sleepMsBetweenRetries * (1 + RandomUtils.nextInt(20)); } public int getRetryCount() { return retryCount; } } public String get(String path) throws Exception { return get(path, true); } public String getWithNodeExistsEx(String path, Stat stat) throws Exception { if (exists(path, false)) { byte[] bytes = client.getData().storingStatIn(stat).forPath(path); String value = new String(bytes, CHARSET); if (logger.isDebugEnabled()) { logger.debug("get value of node " + path + ", value " + value); } return value; } else { if (logger.isDebugEnabled()) { logger.debug("node " + path + " does not exist"); } throw new KeeperException.NodeExistsException("node " + path + " does not exist"); } } public String get(String path, Stat stat) throws Exception { if (exists(path, false)) { byte[] bytes = client.getData().storingStatIn(stat).forPath(path); String value = new String(bytes, CHARSET); if (logger.isDebugEnabled()) { logger.debug("get value of node " + path + ", value " + value); } return value; } else { if (logger.isDebugEnabled()) { logger.debug("node " + path + " does not exist"); } return null; } } public String get(String path, boolean watch) throws Exception { if (exists(path, watch)) { byte[] bytes = client.getData().forPath(path); String value = new String(bytes, CHARSET); if (logger.isDebugEnabled()) { logger.debug("get value of node " + path + ", value " + value); } return value; } else { if (logger.isDebugEnabled()) { logger.debug("node " + path + " does not exist"); } return null; } } public void set(String path, Object value, int version) throws Exception { byte[] bytes = (value == null ? new byte[0] : value.toString().getBytes(CHARSET)); if (exists(path, false)) { client.setData().withVersion(version).forPath(path, bytes); if (logger.isDebugEnabled()) { logger.debug("set value of node " + path + " to " + value); } } else { client.create().creatingParentsIfNeeded().forPath(path, bytes); if (logger.isDebugEnabled()) { logger.debug("create node " + path + " value " + value); } } } public void set(String path, Object value) throws Exception { byte[] bytes = (value == null ? new byte[0] : value.toString().getBytes(CHARSET)); if (exists(path, false)) { client.setData().forPath(path, bytes); if (logger.isDebugEnabled()) { logger.debug("set value of node " + path + " to " + value); } } else { client.create().creatingParentsIfNeeded().forPath(path, bytes); if (logger.isDebugEnabled()) { logger.debug("create node " + path + " value " + value); } } } public void create(String path) throws Exception { create(path, null); } public void create(String path, Object value, int version) throws Exception { byte[] bytes = (value == null ? new byte[0] : value.toString().getBytes(CHARSET)); client.create().creatingParentsIfNeeded().withProtection().forPath(path, bytes); if (logger.isInfoEnabled()) { logger.info("create node " + path + " value " + value); } } public void create(String path, Object value) throws Exception { byte[] bytes = (value == null ? new byte[0] : value.toString().getBytes(CHARSET)); client.create().creatingParentsIfNeeded().forPath(path, bytes); if (logger.isInfoEnabled()) { logger.info("create node " + path + " value " + value); } } public void createEphemeral(String path, String value) throws Exception { byte[] bytes = (value == null ? new byte[0] : value.toString().getBytes(CHARSET)); client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path, bytes); if (logger.isInfoEnabled()) { logger.info("create ephemeral node " + path + " value " + value); } } public void createEphemeral(String path) throws Exception { createEphemeral(path, null); } public boolean exists(String path) throws Exception { Stat stat = client.checkExists().watched().forPath(path); return stat != null; } public boolean exists(String path, boolean watch) throws Exception { Stat stat = watch ? client.checkExists().watched().forPath(path) : client.checkExists().forPath(path); return stat != null; } public List<String> getChildren(String path) throws Exception { return getChildren(path, true); } public List<String> getChildren(String path, boolean watch) throws Exception { try { List<String> children = watch ? client.getChildren().watched().forPath(path) : client.getChildren().forPath(path); if (logger.isDebugEnabled()) { logger.debug("get children of node " + path + ": " + StringUtils.join(children.iterator(), ',')); } return children; } catch (KeeperException.NoNodeException e) { logger.debug("node " + path + " does not exist"); return new ArrayList<>(); } } public void deleteIfExists(String path) throws Exception { if (exists(path, false)) { delete(path); } else { logger.warn("node " + path + " not exists!"); } } public void delete(String path) throws Exception { client.delete().forPath(path); if (logger.isInfoEnabled()) { logger.info("delete node " + path); } } public void watch(String path) throws Exception { client.checkExists().watched().forPath(path); } public void watchChildren(String path) throws Exception { if (exists(path)) client.getChildren().watched().forPath(path); } public void close() { this.close(this.client); } private void close(CuratorFramework client) { if (client != null) { logger.info("begin to close zookeeper client"); try { client.close(); logger.info("succeed to close zookeeper client"); } catch (Exception e) { } } } private class InnerConfigChangeListener implements ConfigChangeListener { @Override public void onKeyUpdated(String key, String value) { if (key.endsWith("pigeon.registry.curator.retries")) { try { retries = Integer.valueOf(value); MyRetryPolicy retryPolicy = new MyRetryPolicy(retries, retryInterval); client.getZookeeperClient().setRetryPolicy(retryPolicy); } catch (RuntimeException e) { } } else if (key.endsWith("pigeon.registry.curator.retryinterval")) { try { retryInterval = Integer.valueOf(value); MyRetryPolicy retryPolicy = new MyRetryPolicy(retries, retryInterval); client.getZookeeperClient().setRetryPolicy(retryPolicy); } catch (RuntimeException e) { } } else if (key.endsWith("pigeon.registry.curator.retrylimit")) { try { retryLimit = Integer.valueOf(value); } catch (RuntimeException e) { } } else if (key.endsWith(KEY_REGISTRY_ADDRESS)) { address = value; logger.info("registry address changed:" + address); try { Thread.sleep(RandomUtils.nextInt(180) * 1000); rebuildCuratorClient(); } catch (Exception e) { logger.warn("rebuild curator client failed:", e); } } } @Override public void onKeyAdded(String key, String value) { } @Override public void onKeyRemoved(String key) { } } public String getStatistics() { CuratorZookeeperClient client = getClient().getZookeeperClient(); return new StringBuilder().append("address:").append(client.getCurrentConnectionString()).append(", connected:").append(isConnected()).append(", retries:") .append(((MyRetryPolicy) client.getRetryPolicy()).getRetryCount()).toString(); } }