/*
* (C) Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* ohun@live.cn (夜色)
*/
package com.mpush.zk;
import com.mpush.api.Constants;
import com.mpush.api.service.BaseService;
import com.mpush.api.service.Listener;
import com.mpush.tools.log.Logs;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.CuratorFrameworkFactory.Builder;
import org.apache.curator.framework.api.ACLProvider;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class ZKClient extends BaseService {
public static final ZKClient I = I();
private ZKConfig zkConfig;
private CuratorFramework client;
private TreeCache cache;
private Map<String, String> ephemeralNodes = new LinkedHashMap<>(4);
private Map<String, String> ephemeralSequentialNodes = new LinkedHashMap<>(1);
private synchronized static ZKClient I() {
return I == null ? new ZKClient() : I;
}
private ZKClient() {
}
@Override
public void start(Listener listener) {
if (isRunning()) {
listener.onSuccess();
} else {
super.start(listener);
}
}
@Override
protected void doStart(Listener listener) throws Throwable {
client.start();
Logs.RSD.info("init zk client waiting for connected...");
if (!client.blockUntilConnected(1, TimeUnit.MINUTES)) {
throw new ZKException("init zk error, config=" + zkConfig);
}
initLocalCache(zkConfig.getWatchPath());
addConnectionStateListener();
Logs.RSD.info("zk client start success, server lists is:{}", zkConfig.getHosts());
listener.onSuccess(zkConfig.getHosts());
}
@Override
protected void doStop(Listener listener) throws Throwable {
if (cache != null) cache.close();
TimeUnit.MILLISECONDS.sleep(600);
client.close();
Logs.RSD.info("zk client closed...");
listener.onSuccess();
}
/**
* 初始化
*/
@Override
public void init() {
if (client != null) return;
if (zkConfig == null) {
zkConfig = ZKConfig.build();
}
Builder builder = CuratorFrameworkFactory
.builder()
.connectString(zkConfig.getHosts())
.retryPolicy(new ExponentialBackoffRetry(zkConfig.getBaseSleepTimeMs(), zkConfig.getMaxRetries(), zkConfig.getMaxSleepMs()))
.namespace(zkConfig.getNamespace());
if (zkConfig.getConnectionTimeout() > 0) {
builder.connectionTimeoutMs(zkConfig.getConnectionTimeout());
}
if (zkConfig.getSessionTimeout() > 0) {
builder.sessionTimeoutMs(zkConfig.getSessionTimeout());
}
if (zkConfig.getDigest() != null) {
/*
* scheme对应于采用哪种方案来进行权限管理,zookeeper实现了一个pluggable的ACL方案,可以通过扩展scheme,来扩展ACL的机制。
* zookeeper缺省支持下面几种scheme:
*
* world: 默认方式,相当于全世界都能访问; 它下面只有一个id, 叫anyone, world:anyone代表任何人,zookeeper中对所有人有权限的结点就是属于world:anyone的
* auth: 代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户); 它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication)
* digest: 即用户名:密码这种方式认证,这也是业务系统中最常用的;它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的authentication
* ip: 使用Ip地址认证;它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段
* super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa)
*/
builder.authorization("digest", zkConfig.getDigest().getBytes(Constants.UTF_8));
builder.aclProvider(new ACLProvider() {
@Override
public List<ACL> getDefaultAcl() {
return ZooDefs.Ids.CREATOR_ALL_ACL;
}
@Override
public List<ACL> getAclForPath(final String path) {
return ZooDefs.Ids.CREATOR_ALL_ACL;
}
});
}
client = builder.build();
Logs.RSD.info("init zk client, config={}", zkConfig.toString());
}
// 注册连接状态监听器
private void addConnectionStateListener() {
client.getConnectionStateListenable().addListener((cli, newState) -> {
if (newState == ConnectionState.RECONNECTED) {
ephemeralNodes.forEach(this::reRegisterEphemeral);
ephemeralSequentialNodes.forEach(this::reRegisterEphemeralSequential);
}
Logs.RSD.warn("zk connection state changed new state={}, isConnected={}", newState, newState.isConnected());
});
}
// 本地缓存
private void initLocalCache(String watchRootPath) throws Exception {
cache = new TreeCache(client, watchRootPath);
cache.start();
}
/**
* 获取数据,先从本地获取,本地找不到,从远程获取
*
* @param key
* @return
*/
public String get(final String key) {
if (null == cache) {
return null;
}
ChildData data = cache.getCurrentData(key);
if (null != data) {
return null == data.getData() ? null : new String(data.getData(), Constants.UTF_8);
}
return getFromRemote(key);
}
/**
* 从远程获取数据
*
* @param key
* @return
*/
public String getFromRemote(final String key) {
if (isExisted(key)) {
try {
return new String(client.getData().forPath(key), Constants.UTF_8);
} catch (Exception ex) {
Logs.RSD.error("getFromRemote:{}", key, ex);
}
}
return null;
}
/**
* 获取子节点
*
* @param key
* @return
*/
public List<String> getChildrenKeys(final String key) {
try {
if (!isExisted(key)) return Collections.emptyList();
List<String> result = client.getChildren().forPath(key);
result.sort(Comparator.reverseOrder());
return result;
} catch (Exception ex) {
Logs.RSD.error("getChildrenKeys:{}", key, ex);
return Collections.emptyList();
}
}
/**
* 判断路径是否存在
*
* @param key
* @return
*/
public boolean isExisted(final String key) {
try {
return null != client.checkExists().forPath(key);
} catch (Exception ex) {
Logs.RSD.error("isExisted:{}", key, ex);
return false;
}
}
/**
* 持久化数据
*
* @param key
* @param value
*/
public void registerPersist(final String key, final String value) {
try {
if (isExisted(key)) {
update(key, value);
} else {
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(key, value.getBytes());
}
} catch (Exception ex) {
Logs.RSD.error("persist:{},{}", key, value, ex);
throw new ZKException(ex);
}
}
/**
* 更新数据
*
* @param key
* @param value
*/
public void update(final String key, final String value) {
try {
/*TransactionOp op = client.transactionOp();
client.transaction().forOperations(
op.check().forPath(key),
op.setData().forPath(key, value.getBytes(Constants.UTF_8))
);*/
client.inTransaction().check().forPath(key).and().setData().forPath(key, value.getBytes(Constants.UTF_8)).and().commit();
} catch (Exception ex) {
Logs.RSD.error("update:{},{}", key, value, ex);
throw new ZKException(ex);
}
}
/**
* 注册临时数据
*
* @param key
* @param value
*/
public void registerEphemeral(final String key, final String value, boolean cacheNode) {
try {
if (isExisted(key)) {
client.delete().deletingChildrenIfNeeded().forPath(key);
}
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(key, value.getBytes(Constants.UTF_8));
if (cacheNode) ephemeralNodes.put(key, value);
} catch (Exception ex) {
Logs.RSD.error("persistEphemeral:{},{}", key, value, ex);
throw new ZKException(ex);
}
}
/**
* 注册临时数据
*
* @param key
* @param value
*/
public void reRegisterEphemeral(final String key, final String value) {
registerEphemeral(key, value, false);
}
/**
* 注册临时数据
*
* @param key
* @param value
*/
public void registerEphemeral(final String key, final String value) {
registerEphemeral(key, value, true);
}
/**
* 注册临时顺序数据
*
* @param key
* @param value
* @param cacheNode 第一次注册时设置为true, 连接断开重新注册时设置为false
*/
private void registerEphemeralSequential(final String key, final String value, boolean cacheNode) {
try {
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(key, value.getBytes());
if (cacheNode) ephemeralSequentialNodes.put(key, value);
} catch (Exception ex) {
Logs.RSD.error("persistEphemeralSequential:{},{}", key, value, ex);
throw new ZKException(ex);
}
}
private void reRegisterEphemeralSequential(final String key, final String value) {
registerEphemeralSequential(key, value, false);
}
public void registerEphemeralSequential(final String key, final String value) {
registerEphemeralSequential(key, value, true);
}
/**
* 注册临时顺序数据
*
* @param key
*/
public void registerEphemeralSequential(final String key) {
try {
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(key);
} catch (Exception ex) {
Logs.RSD.error("persistEphemeralSequential:{}", key, ex);
throw new ZKException(ex);
}
}
/**
* 删除数据
*
* @param key
*/
public void remove(final String key) {
try {
client.delete().deletingChildrenIfNeeded().forPath(key);
} catch (Exception ex) {
Logs.RSD.error("removeAndClose:{}", key, ex);
throw new ZKException(ex);
}
}
public void registerListener(TreeCacheListener listener) {
cache.getListenable().addListener(listener);
}
public ZKConfig getZKConfig() {
return zkConfig;
}
public ZKClient setZKConfig(ZKConfig zkConfig) {
this.zkConfig = zkConfig;
return this;
}
public CuratorFramework getClient() {
return client;
}
}