com.wandoulabs.nedis.codis.RoundRobinNedisClientPool Maven / Gradle / Ivy
/**
* Copyright (c) 2015 Wandoujia Inc.
*
* 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.
*/
package com.wandoulabs.nedis.codis;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_ADDED;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_UPDATED;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.INITIALIZED;
import io.netty.channel.EventLoop;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
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.PathChildrenCache.StartMode;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wandoulabs.nedis.NedisClient;
import com.wandoulabs.nedis.NedisClientPool;
import com.wandoulabs.nedis.NedisClientPoolBuilder;
/**
* @author Apache9
*/
public class RoundRobinNedisClientPool implements NedisClientPool {
private static final Logger LOG = LoggerFactory.getLogger(RoundRobinNedisClientPool.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String CODIS_PROXY_STATE_ONLINE = "online";
private static final EnumSet RESET_TYPES = EnumSet.of(CHILD_ADDED,
CHILD_UPDATED, CHILD_REMOVED);
private static final class PooledObject {
public final String addr;
public final NedisClientPool pool;
public PooledObject(String addr, NedisClientPool pool) {
this.addr = addr;
this.pool = pool;
}
}
private volatile List pools = Collections.emptyList();
private final CuratorFramework curatorClient;
private final boolean closeCurator;
private final PathChildrenCache watcher;
private final NedisClientPoolBuilder poolBuilder;
private final AtomicInteger nextIdx = new AtomicInteger(-1);
private final Promise closePromise;
private final Promise initPromise;
private RoundRobinNedisClientPool(CuratorFramework curatorClient, boolean closeCurator,
String zkProxyDir, NedisClientPoolBuilder poolBuilder) throws Exception {
this.curatorClient = curatorClient;
this.closeCurator = closeCurator;
this.poolBuilder = poolBuilder;
EventLoop eventLoop = poolBuilder.group().next();
this.closePromise = eventLoop.newPromise();
this.initPromise = eventLoop.newPromise();
watcher = new PathChildrenCache(curatorClient, zkProxyDir, true);
watcher.getListenable().addListener(new PathChildrenCacheListener() {
private boolean initialized = false;
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
throws Exception {
StringBuilder sb = new StringBuilder("Zookeeper event received: type=")
.append(event.getType());
if (event.getData() != null) {
ChildData data = event.getData();
sb.append(", path=").append(data.getPath()).append(", stat=")
.append(data.getStat());
}
LOG.info(sb.toString());
if (!initialized) {
if (event.getType() == INITIALIZED) {
resetPools();
initPromise.trySuccess(RoundRobinNedisClientPool.this);
initialized = true;
}
} else if (RESET_TYPES.contains(event.getType())) {
resetPools();
}
}
});
watcher.start(StartMode.POST_INITIALIZED_EVENT);
}
private synchronized void resetPools() {
if (closed.get()) {
return;
}
List oldPools = this.pools;
Map addr2Pool = new HashMap<>(oldPools.size());
for (PooledObject pool: oldPools) {
addr2Pool.put(pool.addr, pool);
}
List newPools = new ArrayList<>();
for (ChildData childData: watcher.getCurrentData()) {
try {
CodisProxyInfo proxyInfo = MAPPER.readValue(childData.getData(),
CodisProxyInfo.class);
if (!CODIS_PROXY_STATE_ONLINE.equals(proxyInfo.getState())) {
continue;
}
String addr = proxyInfo.getAddr();
PooledObject pool = addr2Pool.remove(addr);
if (pool == null) {
LOG.info("Add new proxy: " + addr);
String[] hostAndPort = addr.split(":");
String host = hostAndPort[0];
int port = Integer.parseInt(hostAndPort[1]);
pool = new PooledObject(addr, poolBuilder.remoteAddress(host, port).build());
}
newPools.add(pool);
} catch (Exception e) {
LOG.warn("parse " + childData.getPath() + " failed", e);
}
}
this.pools = newPools;
for (PooledObject pool: addr2Pool.values()) {
LOG.info("Remove proxy: " + pool.addr);
pool.pool.close();
}
}
public Future initFuture() {
return initPromise;
}
@Override
public Future closeFuture() {
return closePromise;
}
private final AtomicBoolean closed = new AtomicBoolean(false);
@Override
public Future close() {
if (!closed.compareAndSet(false, true)) {
return closePromise;
}
new Thread(getClass().getSimpleName() + "-Closer") {
@Override
public void run() {
try {
watcher.close();
} catch (IOException e) {
LOG.warn("IOException should not have been thrown", e);
}
if (closeCurator) {
curatorClient.close();
}
synchronized (RoundRobinNedisClientPool.this) {
List toClose = pools;
if (toClose.isEmpty()) {
closePromise.trySuccess(null);
return;
}
final AtomicInteger remaining = new AtomicInteger(toClose.size());
FutureListener listener = new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (remaining.decrementAndGet() == 0) {
closePromise.trySuccess(null);
}
}
};
for (PooledObject pool: toClose) {
pool.pool.close().addListener(listener);
}
}
}
}.start();
return closePromise;
}
@Override
public Future acquire() {
List pools = this.pools;
if (pools.isEmpty()) {
return poolBuilder.group().next().newFailedFuture(new IOException("Proxy list empty"));
}
for (;;) {
int current = nextIdx.get();
int next = current >= pools.size() - 1 ? 0 : current + 1;
if (nextIdx.compareAndSet(current, next)) {
return pools.get(next).pool.acquire();
}
}
}
@Override
public void release(NedisClient client) {
throw new UnsupportedOperationException();
}
@Override
public boolean exclusive() {
return poolBuilder.exclusive();
}
@Override
public int numConns() {
int numConns = 0;
for (PooledObject pool: pools) {
numConns += pool.pool.numConns();
}
return numConns;
}
@Override
public int numPooledConns() {
int numPooledConns = 0;
for (PooledObject pool: pools) {
numPooledConns += pool.pool.numPooledConns();
}
return numPooledConns;
}
public static class Builder {
private static final int CURATOR_RETRY_BASE_SLEEP_MS = 100;
private static final int CURATOR_RETRY_MAX_SLEEP_MS = 30 * 1000;
private NedisClientPoolBuilder poolBuilder;
private CuratorFramework curatorClient;
private boolean closeCurator;
private String zkProxyDir;
private String zkAddr;
private int zkSessionTimeoutMs;
private Builder() {}
public Builder poolBuilder(NedisClientPoolBuilder poolBuilder) {
this.poolBuilder = poolBuilder;
return this;
}
public Builder curatorClient(CuratorFramework curatorClient, boolean closeCurator) {
this.curatorClient = curatorClient;
this.closeCurator = closeCurator;
return this;
}
public Builder zkProxyDir(String zkProxyDir) {
this.zkProxyDir = zkProxyDir;
return this;
}
public Builder curatorClient(String zkAddr, int zkSessionTimeoutMs) {
this.zkAddr = zkAddr;
this.zkSessionTimeoutMs = zkSessionTimeoutMs;
return this;
}
private void validate() {
poolBuilder.validateGroupConfig();
if (zkProxyDir == null) {
throw new IllegalArgumentException("zkProxyDir can not be null");
}
if (curatorClient == null) {
if (zkAddr == null) {
throw new IllegalArgumentException("zk client can not be null");
}
curatorClient = CuratorFrameworkFactory
.builder()
.connectString(zkAddr)
.sessionTimeoutMs(zkSessionTimeoutMs)
.retryPolicy(
new BoundedExponentialBackoffRetryUntilElapsed(
CURATOR_RETRY_BASE_SLEEP_MS, CURATOR_RETRY_MAX_SLEEP_MS,
-1L)).build();
curatorClient.start();
closeCurator = true;
}
}
public Future build() {
validate();
try {
return new RoundRobinNedisClientPool(curatorClient, closeCurator, zkProxyDir,
poolBuilder).initFuture();
} catch (Exception e) {
return poolBuilder.group().next().newFailedFuture(e);
}
}
}
public static Builder builder() {
return new Builder();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy