package com.anduo.filesync.zk;
import com.google.common.util.concurrent.MoreExecutors;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.DebugUtils;
import org.apache.curator.utils.ThreadUtils;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
*/
class ZKClientImpl implements ZKClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ZKClientImpl.class);
public static final int MAX_RETRIES = 3000;
public static final int BASE_SLEEP_TIMEMS = 3000;
private final CuratorFramework client;
private final ExecutorService EVENT_THREAD_POOL = Executors
.newFixedThreadPool(1, ThreadUtils.newThreadFactory("PathChildrenCache"));
private final ExecutorService SAME_EXECUTOR = MoreExecutors.sameThreadExecutor();
private final AtomicInteger REFERENCE_COUNT = new AtomicInteger(0);
private Lock _lock = new ReentrantLock(true);
public ZKClientImpl(String adds) {
System.setProperty(DebugUtils.PROPERTY_DONT_LOG_CONNECTION_ISSUES, "false");
RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIMEMS, MAX_RETRIES);
this.client = CuratorFrameworkFactory.builder().connectString(adds).retryPolicy(retryPolicy)
.connectionTimeoutMs(5000).build();
waitUntilZkStart();
}
private void waitUntilZkStart() {
CountDownLatch latch = new CountDownLatch(1);
addConnectionChangeListenter(new ConnectionWatcher(latch));
client.start();
try {
latch.await();
} catch (InterruptedException e) {
LOGGER.error("start zk latch.await() error", e);
Thread.currentThread().interrupt();
}
}
/**
* 使用分布式锁执行任务
*
* @param path
* @param getLockTimeout 获取锁超时时间(单位ms)
* @param task
* @auth anduo 2015年5月8日
*/
public void distributeLock(String path, int getLockTimeout, Runnable task) {
InterProcessMutex lock = new InterProcessMutex(client, path);
try {
LOGGER.debug("尝试获取锁。。。");
if (lock.acquire(getLockTimeout, TimeUnit.MILLISECONDS)) {
try {
LOGGER.debug("获得锁,开始执行任务。。。");
task.run();
} finally {
lock.release();
LOGGER.debug("释放锁,path:" + path);
}
} else {
LOGGER.info("任务执行失败,在时间:" + getLockTimeout + "ms内,未获得分布式锁!");
}
} catch (Exception e) {
LOGGER.error("执行分布式锁任务异常。", e);
}
}
@Override
public List<String> getChildren(String path)
throws Exception {
return client.getChildren().forPath(path);
}
@Override
public List<String> listenChildrenPath(final String parent, final NodeListener listener, final boolean sync)
throws Exception {
PathChildrenCache cache = new PathChildrenCache(client, parent, false, false, EVENT_THREAD_POOL);
cache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework c, PathChildrenCacheEvent e)
throws Exception {
if (e.getData() == null) { return; }
switch (e.getType()) {
case CHILD_ADDED:
listener.nodeChanged(ZKClientImpl.this,
new ChangedEvent(e.getData().getPath(), ChangedEvent.Type.CHILD_ADDED));
break;
case CHILD_REMOVED:
listener.nodeChanged(ZKClientImpl.this,
new ChangedEvent(e.getData().getPath(), ChangedEvent.Type.CHILD_REMOVED));
break;
case CHILD_UPDATED:
listener.nodeChanged(ZKClientImpl.this,
new ChangedEvent(e.getData().getPath(), ChangedEvent.Type.CHILD_UPDATED));
break;
}
}
}, SAME_EXECUTOR);
PathChildrenCache.StartMode mode = sync ? PathChildrenCache.StartMode.BUILD_INITIAL_CACHE : PathChildrenCache.StartMode.NORMAL;
cache.start(mode);
List<ChildData> children = cache.getCurrentData();
List<String> result = new ArrayList<String>();
for (ChildData child : children) {
result.add(child.getPath());
}
return result;
}
@Override
public String addEphemeralNode(String parent, String node)
throws Exception {
return addEphemeralNode(ZKPaths.makePath(parent, node));
}
@Override
public String addEphemeralNode(String path)
throws Exception {
return client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
}
@Override
public void addPersistentNode(String path)
throws Exception {
try {
client.newNamespaceAwareEnsurePath(path).ensure(client.getZookeeperClient());
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path);
} catch (KeeperException.NodeExistsException e) {
LOGGER.warn("Node already exists: {}", path);
} catch (Exception e) {
throw new Exception("addPersistentNode error", e);
}
}
@Override
public void addEphemeralNodeData(String path, String data)
throws Exception {
try {
client.newNamespaceAwareEnsurePath(path).ensure(client.getZookeeperClient());
client.create().withMode(CreateMode.EPHEMERAL).forPath(path, data.getBytes());
} catch (KeeperException.NodeExistsException e) {
LOGGER.warn("Node already exists: {}", path);
} catch (Exception e) {
throw new Exception("addEphemeralNodeData error", e);
}
}
/**
* 创建 节点
*
* @param path
* @param data
* @auth anduo 2015年5月8日
*/
@Override
public void createPath(String path, String data) {
try {
client.newNamespaceAwareEnsurePath(path).ensure(client.getZookeeperClient());
client.setData().forPath(path, data.getBytes());
} catch (Exception ex) {
LOGGER.error("创建节点异常,path:" + path + " , data:" + data, ex);
}
}
/**
* 设置节点值
*
* @param path
* @param data
* @auth anduo 2015年5月8日
*/
@Override
public void updatePathValue(String path, String data) {
try {
LOGGER.debug("设置结点值,path:" + path + ",data:" + data);
this.client.setData().forPath(path, data.getBytes("UTF-8"));
} catch (Exception e) {
LOGGER.error("设置zookeeper节点值异常,path:" + path + ",data" + data, e);
}
}
/**
* 获取节点值
*
* @param path
* @return
* @throws Exception
* @auth anduo 2015年5月7日
*/
@Override
public String getPathValue(String path)
throws Exception {
if (!checkExist(path)) {
throw new RuntimeException("Path " + path + " does not exists.");
}
return new String(client.getData().forPath(path), "UTF-8");
}
@PreDestroy
@Override
public void close() {
LOGGER.info("Call close of ZKClient, reference count is: {}", REFERENCE_COUNT.get());
if (REFERENCE_COUNT.decrementAndGet() == 0) {
client.close();
LOGGER.info("ZKClient is closed");
}
}
@Override
public boolean isConnected() {
return client.getZookeeperClient().isConnected();
}
/**
* @param listener
*/
@Override
public void addConnectionChangeListenter(final ConnectionStateListener listener) {
if (listener != null) {
client.getConnectionStateListenable()
.addListener((sender, state) -> listener.stateChanged(ZKClientImpl.this, convertTo(state)));
}
}
private ConnectionState convertTo(org.apache.curator.framework.state.ConnectionState state) {
switch (state) {
case CONNECTED:
return ConnectionState.CONNECTED;
case SUSPENDED:
return ConnectionState.SUSPENDED;
case RECONNECTED:
return ConnectionState.RECONNECTED;
case LOST:
return ConnectionState.LOST;
default:
return ConnectionState.READ_ONLY;
}
}
/**
* 删除一个znode,如果该znode下面有子节点则会抛异常
*
* @param path
* @throws Exception
*/
@Override
public void deletePath(String path)
throws Exception {
client.delete().forPath(path);
}
@Override
public void deletAllPath(String path)
throws Exception {
List<String> children = client.getChildren().forPath(path);
if (children == null || children.size() == 0) {
deletePath(path);
return;
}
for (String child : children) {
deletAllPath(ZKPaths.makePath(path, child));
}
}
private class ConnectionWatcher implements ConnectionStateListener {
CountDownLatch latch;
ConnectionWatcher(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void stateChanged(ZKClient client, ConnectionState newState) {
if (newState == newState.CONNECTED) {
latch.countDown();
}
}
}
@Override
public boolean checkExist(String path) {
try {
Stat stat = client.checkExists().forPath(path);
return stat != null;
} catch (Exception e) {
LOGGER.error("check exist error", e);
return false;
}
}
protected void incrementReference() {
REFERENCE_COUNT.incrementAndGet();
}
}