Repository: CodisLabs/jodis
Branch: master
Commit: 6fd0b153e408
Files: 14
Total size: 50.9 KB
Directory structure:
gitextract_z0l835z5/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
├── src/
│ ├── main/
│ │ └── java/
│ │ └── io/
│ │ └── codis/
│ │ └── jodis/
│ │ ├── BoundedExponentialBackoffRetryUntilElapsed.java
│ │ ├── CodisProxyInfo.java
│ │ ├── JedisPoolAdaptor.java
│ │ ├── JedisResourcePool.java
│ │ └── RoundRobinJedisPool.java
│ └── test/
│ └── java/
│ └── io/
│ └── codis/
│ └── jodis/
│ ├── RedisServer.java
│ ├── TestBoundedExponentialBackoffRetryUntilElapsed.java
│ └── TestRoundRobinJedisPool.java
└── wandoujia-LICENSE
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.classpath
.project
.settings
target
================================================
FILE: .travis.yml
================================================
language: java
jdk:
- oraclejdk8
install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true -B -V
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 CodisLabs.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Jodis - Java client for codis
[](https://travis-ci.org/CodisLabs/jodis)
Jodis is a java client for codis based on [Jedis](https://github.com/xetorthio/jedis) and [Curator](http://curator.apache.org/).
# Features
- Use a round robin policy to balance load to multiple codis proxies.
- Detect proxy online and offline automatically.
# How to use
Add this to your pom.xml. We deploy jodis to https://oss.sonatype.org.
```xml
io.codis.jodisjodis0.5.1
```
To use it for Codis2.x:
```java
JedisResourcePool jedisPool = RoundRobinJedisPool.create()
.curatorClient("zkserver:2181", 30000).zkProxyDir("/zk/codis/db_xxx/proxy").build();
try (Jedis jedis = jedisPool.getResource()) {
jedis.set("foo", "bar");
String value = jedis.get("foo");
System.out.println(value);
}
```
Or for Codis3.x with `jodis_compatible=false`:
```java
JedisResourcePool jedisPool = RoundRobinJedisPool.create()
.curatorClient("zkserver:2181", 30000).zkProxyDir("/jodis/xxx").build();
try (Jedis jedis = jedisPool.getResource()) {
jedis.set("foo", "bar");
String value = jedis.get("foo");
System.out.println(value);
}
```
Note: JDK8 is required to use and build jodis, as JDK7 has been EOL since May 2015.
================================================
FILE: pom.xml
================================================
4.0.0io.codis.jodisjodis0.6.0-SNAPSHOTjar${project.artifactId}Round Robin jedis pool for codishttps://github.com/CodisLabs/jodisscm:git:ssh://github.com:CodisLabs/jodis.gitscm:git:ssh://github.com:CodisLabs/jodis.gitssh://github.com:CodisLabs/codis.gitHEAD1.7UTF-8UTF-8UTF-8UTF-83.4.114.0.12.12.02.9.02.9.5redis.clientsjedis${jedis.version}org.apache.zookeeperzookeeper${zookeeper.version}org.apache.curatorcurator-recipes${curator.version}org.apache.zookeeperzookeeperorg.slf4jslf4j-api1.7.12com.google.guavaguava23.0com.fasterxml.jackson.corejackson-databind${jackson.version}junitjunit4.12testorg.slf4jslf4j-simple1.7.12testorg.apache.curatorcurator-test${curator-test.version}testorg.apache.zookeeperzookeeperossrhhttps://oss.sonatype.org/content/repositories/snapshotsossrhhttps://oss.sonatype.org/service/local/staging/deploy/maven2/org.apache.maven.pluginsmaven-compiler-plugin2.5.1${java.version}${java.version}org.apache.maven.pluginsmaven-source-plugin2.4attach-sourcesjar-no-forkorg.apache.maven.pluginsmaven-javadoc-plugin2.10.3-Xdoclint:noneattach-javadocsjarorg.apache.maven.pluginsmaven-gpg-plugin1.5sign-artifactsverifysignorg.sonatype.pluginsnexus-staging-maven-plugin1.6.7trueossrhhttps://oss.sonatype.org/falseorg.jacocojacoco-maven-plugin0.7.5.201505241946default-prepare-agentprepare-agentdefault-reportprepare-packagereportorg.apache.maven.pluginsmaven-surefire-plugin2.18.1alwaystrue${argLine} -Dfile.encoding=UTF-8org.apache.maven.pluginsmaven-surefire-report-plugin2.18.1org.apache.maven.pluginsmaven-deploy-plugin2.8.2deploydeploydeploydefault-deploytrueorg.apache.maven.pluginsmaven-site-plugin3.4org.codehaus.mojofindbugs-maven-pluginorg.jacocojacoco-maven-pluginorg.codehaus.mojofindbugs-maven-plugin3.0.2truetarget/findbugstarget/findbugsMIT Licensehttp://opensource.org/licenses/MITrepoapache9Apache9palomino219@gmail.comdeveloper+8
================================================
FILE: src/main/java/io/codis/jodis/BoundedExponentialBackoffRetryUntilElapsed.java
================================================
/**
* @(#)BoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2.
*
* Copyright (c) 2014 CodisLabs.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.codis.jodis;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.apache.curator.RetryPolicy;
import org.apache.curator.RetrySleeper;
/**
* Similar to {@link org.apache.curator.retry.BoundedExponentialBackoffRetry},
* but limit the retry elapsed time, not retry number.
*
* @author Apache9
*/
public class BoundedExponentialBackoffRetryUntilElapsed implements RetryPolicy {
private final int baseSleepTimeMs;
private final int maxSleepTimeMs;
private final long maxElapsedTimeMs;
/**
* @param baseSleepTimeMs
* initial amount of time to wait between retries
* @param maxSleepTimeMs
* max time in ms to sleep on each retry
* @param maxElapsedTimeMs
* total time in ms to retry
*/
public BoundedExponentialBackoffRetryUntilElapsed(int baseSleepTimeMs, int maxSleepTimeMs,
long maxElapsedTimeMs) {
this.baseSleepTimeMs = baseSleepTimeMs;
this.maxSleepTimeMs = maxSleepTimeMs;
if (maxElapsedTimeMs < 0) {
this.maxElapsedTimeMs = Long.MAX_VALUE;
} else {
this.maxElapsedTimeMs = maxElapsedTimeMs;
}
}
private long getSleepTimeMs(int retryCount, long elapsedTimeMs) {
return Math.min(
maxSleepTimeMs,
(long) baseSleepTimeMs
* Math.max(
1,
ThreadLocalRandom.current().nextInt(
1 << Math.min(30, retryCount + 1))));
}
@Override
public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper) {
if (elapsedTimeMs >= maxElapsedTimeMs) {
return false;
}
long sleepTimeMs = Math.min(maxElapsedTimeMs - elapsedTimeMs,
getSleepTimeMs(retryCount, elapsedTimeMs));
try {
sleeper.sleepFor(sleepTimeMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
return true;
}
}
================================================
FILE: src/main/java/io/codis/jodis/CodisProxyInfo.java
================================================
/**
* @(#)CodisProxyInfo.java, 2015-10-14.
*
* Copyright (c) 2014 CodisLabs.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.codis.jodis;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* We only care about the proxy address and if it is online.
*
* @author Apache9
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class CodisProxyInfo {
private String addr;
private String state;
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
================================================
FILE: src/main/java/io/codis/jodis/JedisPoolAdaptor.java
================================================
/**
* @(#)JedisPoolAdaptor.java, 2014-12-2.
*
* Copyright (c) 2014 CodisLabs.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.codis.jodis;
import java.net.URI;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.JedisPool;
/**
* Adaptor of JedisPool to make writing testcase easier.
*
* @author Apache9
*/
public class JedisPoolAdaptor extends JedisPool implements JedisResourcePool {
public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,
String password, int database, String clientName) {
super(poolConfig, host, port, timeout, password, database, clientName);
}
public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,
String password, int database) {
super(poolConfig, host, port, timeout, password, database);
}
public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,
String password) {
super(poolConfig, host, port, timeout, password);
}
public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout) {
super(poolConfig, host, port, timeout);
}
public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port) {
super(poolConfig, host, port);
}
public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host) {
super(poolConfig, host);
}
public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri, int timeout) {
super(poolConfig, uri, timeout);
}
public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri) {
super(poolConfig, uri);
}
public JedisPoolAdaptor(String host, int port) {
super(host, port);
}
public JedisPoolAdaptor(String host) {
super(host);
}
public JedisPoolAdaptor(URI uri, int timeout) {
super(uri, timeout);
}
public JedisPoolAdaptor(URI uri) {
super(uri);
}
}
================================================
FILE: src/main/java/io/codis/jodis/JedisResourcePool.java
================================================
/**
* @(#)JedisResourcePool.java, 2014-12-2.
*
* Copyright (c) 2014 CodisLabs.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.codis.jodis;
import java.io.Closeable;
import redis.clients.jedis.Jedis;
/**
* Describe a pool which we can acquire jedis instance.
*
* @author Apache9
*/
public interface JedisResourcePool extends Closeable {
/**
* Get a jedis instance from pool.
*
* We do not have a returnResource method, just close the jedis instance
* returned directly.
*/
Jedis getResource();
}
================================================
FILE: src/main/java/io/codis/jodis/RoundRobinJedisPool.java
================================================
/**
* @(#)RoundRobinJedisPool.java, 2014-11-30.
*
* Copyright (c) 2014 CodisLabs.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.codis.jodis;
import static org.apache.curator.framework.imps.CuratorFrameworkState.LATENT;
import static org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode.BUILD_INITIAL_CACHE;
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 java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
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.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.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Closeables;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.exceptions.JedisException;
/**
* A round robin connection pool for connecting multiple codis proxies based on
* Jedis and Curator.
*
* @author Apache9
* @see https://github.com/xetorthio/jedis
* @see http://curator.apache.org/
*/
public class RoundRobinJedisPool implements JedisResourcePool {
private static final Logger LOG = LoggerFactory.getLogger(RoundRobinJedisPool.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String CODIS_PROXY_STATE_ONLINE = "online";
private static final int CURATOR_RETRY_BASE_SLEEP_MS = 100;
private static final int CURATOR_RETRY_MAX_SLEEP_MS = 30 * 1000;
private static final long DELAY_BEFORE_CLOSING_POOL = 10000; // milliseconds
private static final ImmutableSet RESET_TYPES = Sets
.immutableEnumSet(CHILD_ADDED, CHILD_UPDATED, CHILD_REMOVED);
private final CuratorFramework curatorClient;
private final boolean closeCurator;
private final PathChildrenCache watcher;
private static final class PooledObject {
public final String addr;
public final JedisPool pool;
public PooledObject(String addr, JedisPool pool) {
this.addr = addr;
this.pool = pool;
}
public Jedis getResource() {
return pool.getResource();
}
public void close() {
try {
pool.close();
LOG.info("Connection pool to {} closed", addr);
} catch (Exception e) {
LOG.error("Error closing connection pool to " + addr, e);
}
}
}
private volatile ImmutableList pools = ImmutableList.of();
private final AtomicInteger nextIdx = new AtomicInteger(-1);
private final JedisPoolConfig poolConfig;
private final int connectionTimeoutMs;
private final int soTimeoutMs;
private final String password;
private final int database;
private final String clientName;
private final ScheduledThreadPoolExecutor jedisPoolClosingExecutor =
new ScheduledThreadPoolExecutor(1);
private RoundRobinJedisPool(CuratorFramework curatorClient, boolean closeCurator,
String zkProxyDir, JedisPoolConfig poolConfig, int connectionTimeoutMs, int soTimeoutMs,
String password, int database, String clientName) {
this.poolConfig = poolConfig;
this.connectionTimeoutMs = connectionTimeoutMs;
this.soTimeoutMs = soTimeoutMs;
this.password = password;
this.database = database;
this.clientName = clientName;
this.curatorClient = curatorClient;
this.closeCurator = closeCurator;
watcher = new PathChildrenCache(curatorClient, zkProxyDir, true);
watcher.getListenable().addListener(new PathChildrenCacheListener() {
private void logEvent(PathChildrenCacheEvent event) {
StringBuilder msg = new StringBuilder("Receive child event: ");
msg.append("type=").append(event.getType());
ChildData data = event.getData();
if (data != null) {
msg.append(", path=").append(data.getPath());
msg.append(", stat=").append(data.getStat());
if (data.getData() != null) {
msg.append(", bytes length=").append(data.getData().length);
} else {
msg.append(", no bytes");
}
} else {
msg.append(", no data");
}
LOG.info(msg.toString());
}
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
throws Exception {
synchronized (RoundRobinJedisPool.this) {
logEvent(event);
if (RESET_TYPES.contains(event.getType())) {
resetPools();
}
}
}
});
synchronized (this) {
try {
watcher.start(BUILD_INITIAL_CACHE);
} catch (Exception e) {
close();
throw new JedisException(e);
}
resetPools();
}
}
private void resetPools() {
ImmutableList pools = this.pools;
Map addr2Pool = Maps.newHashMapWithExpectedSize(pools.size());
for (PooledObject pool: pools) {
addr2Pool.put(pool.addr, pool);
}
ImmutableList.Builder builder = ImmutableList.builder();
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) {
String[] hostAndPort = addr.split(":");
String host = hostAndPort[0];
int port = Integer.parseInt(hostAndPort[1]);
pool = new PooledObject(addr,
new JedisPool(poolConfig, host, port, connectionTimeoutMs, soTimeoutMs,
password, database, clientName, false, null, null, null));
LOG.info("Add new proxy: " + addr);
}
builder.add(pool);
} catch (Exception e) {
LOG.warn("parse " + childData.getPath() + " failed", e);
}
}
this.pools = builder.build();
for (final PooledObject pool: addr2Pool.values()) {
LOG.info("Remove proxy: " + pool.addr);
jedisPoolClosingExecutor.schedule(new Runnable() {
@Override
public void run() {
pool.close();
}
}, DELAY_BEFORE_CLOSING_POOL, TimeUnit.MILLISECONDS);
}
}
@Override
public Jedis getResource() {
ImmutableList pools = this.pools;
if (pools.isEmpty()) {
throw new JedisException("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).getResource();
}
}
}
@Override
public void close() {
try {
Closeables.close(watcher, true);
} catch (IOException e) {
throw new AssertionError("IOException should not have been thrown", e);
}
if (closeCurator) {
curatorClient.close();
}
List pools = this.pools;
this.pools = ImmutableList.of();
for (PooledObject pool: pools) {
pool.close();
}
jedisPoolClosingExecutor.shutdown();
}
/**
* Create a {@link RoundRobinJedisPool} using the fluent style api.
*
* @return
*/
public static Builder create() {
return new Builder();
}
public static final class Builder {
private CuratorFramework curatorClient;
private boolean closeCurator;
private String zkProxyDir;
private String zkAddr;
private int zkSessionTimeoutMs;
private JedisPoolConfig poolConfig;
private int connectionTimeoutMs = Protocol.DEFAULT_TIMEOUT;
private int soTimeoutMs = Protocol.DEFAULT_TIMEOUT;
private String password;
private int database = Protocol.DEFAULT_DATABASE;
private String clientName;
private Builder() {}
/**
* Set curator client.
*
* @param curatorClient
* the client to be used
* @param closeCurator
* whether to close curator client while closing pool
*/
public Builder curatorClient(CuratorFramework curatorClient, boolean closeCurator) {
this.curatorClient = curatorClient;
this.closeCurator = closeCurator;
return this;
}
/**
* Set codis proxy path on zk.
*
* @param zkProxyDir
* the codis proxy dir on ZooKeeper. e.g.,
* "/zk/codis/db_xxx/proxy"
*/
public Builder zkProxyDir(String zkProxyDir) {
this.zkProxyDir = zkProxyDir;
return this;
}
/**
* Set curator client.
*
* We will create curator client based on these parameters and close it
* while closing pool.
*
* @param zkAddr
* ZooKeeper connect string. e.g., "zk1:2181"
* @param zkSessionTimeoutMs
* ZooKeeper session timeout in milliseconds
*/
public Builder curatorClient(String zkAddr, int zkSessionTimeoutMs) {
this.zkAddr = zkAddr;
this.zkSessionTimeoutMs = zkSessionTimeoutMs;
return this;
}
/**
* Set jedis pool config.
*/
public Builder poolConfig(JedisPoolConfig poolConfig) {
this.poolConfig = poolConfig;
return this;
}
/**
* Set jedis pool timeout in milliseconds.
*
* We will set connectionTimeoutMs and soTimeoutMs both.
*
* @param timeoutMs
* timeout is milliseconds
*/
public Builder timeoutMs(int timeoutMs) {
this.connectionTimeoutMs = this.soTimeoutMs = timeoutMs;
return this;
}
/**
* Set jedis pool connection timeout in milliseconds.
*
* @param connectionTimeoutMs
* timeout is milliseconds
*/
public Builder connectionTimeoutMs(int connectionTimeoutMs) {
this.connectionTimeoutMs = connectionTimeoutMs;
return this;
}
/**
* Set jedis pool connection soTimeout in milliseconds.
*
* @param soTimeoutMs
* timeout is milliseconds
*/
public Builder soTimeoutMs(int soTimeoutMs) {
this.soTimeoutMs = soTimeoutMs;
return this;
}
/**
* Set password.
*/
public Builder password(String password) {
this.password = password;
return this;
}
/**
* Set redis database.
*/
public Builder database(int database) {
this.database = database;
return this;
}
/**
* Set redis client name.
*/
public Builder clientName(String clientName) {
this.clientName = clientName;
return this;
}
private void validate() {
Preconditions.checkNotNull(zkProxyDir, "zkProxyDir can not be null");
if (curatorClient == null) {
Preconditions.checkNotNull(zkAddr, "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;
} else {
// we need to get the initial data so client must be started
if (curatorClient.getState() == LATENT) {
curatorClient.start();
}
}
if (poolConfig == null) {
poolConfig = new JedisPoolConfig();
}
}
/**
* Create the {@link RoundRobinJedisPool}.
*/
public RoundRobinJedisPool build() {
validate();
return new RoundRobinJedisPool(curatorClient, closeCurator, zkProxyDir, poolConfig,
connectionTimeoutMs, soTimeoutMs, password, database, clientName);
}
}
}
================================================
FILE: src/test/java/io/codis/jodis/RedisServer.java
================================================
/**
* @(#)RedisServer.java, 2014-11-30.
*
* Copyright (c) 2014 CodisLabs.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.codis.jodis;
import java.io.IOException;
/**
* @author Apache9
*/
public class RedisServer {
private final ProcessBuilder builder;
private Process process;
public RedisServer(int port) {
builder = new ProcessBuilder().command("redis-server", "--port",
Long.toString(port)).inheritIO();
}
public void start() throws IOException {
process = builder.start();
}
public void stop() {
if (process != null) {
process.destroy();
}
}
}
================================================
FILE: src/test/java/io/codis/jodis/TestBoundedExponentialBackoffRetryUntilElapsed.java
================================================
/**
* @(#)TestBoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2.
*
* Copyright (c) 2014 CodisLabs.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.codis.jodis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.apache.curator.RetrySleeper;
import org.junit.Test;
/**
* @author Apache9
*/
public class TestBoundedExponentialBackoffRetryUntilElapsed {
private static final class FakeRetrySleeper implements RetrySleeper {
public long sleepTimeMs;
@Override
public void sleepFor(long time, TimeUnit unit) {
this.sleepTimeMs = unit.toMillis(time);
}
}
@Test
public void test() {
FakeRetrySleeper sleeper = new FakeRetrySleeper();
BoundedExponentialBackoffRetryUntilElapsed r = new BoundedExponentialBackoffRetryUntilElapsed(
10, 2000, 60000);
for (int i = 0; i < 100; i++) {
assertTrue(r.allowRetry(i, ThreadLocalRandom.current().nextInt(60000), sleeper));
System.out.println(sleeper.sleepTimeMs);
assertTrue(sleeper.sleepTimeMs <= 2000);
}
assertTrue(r.allowRetry(1000, 59900, sleeper));
System.out.println(sleeper.sleepTimeMs);
assertTrue(sleeper.sleepTimeMs <= 100);
sleeper.sleepTimeMs = -1L;
assertFalse(r.allowRetry(1, 60000, sleeper));
assertEquals(-1L, sleeper.sleepTimeMs);
}
}
================================================
FILE: src/test/java/io/codis/jodis/TestRoundRobinJedisPool.java
================================================
/**
* @(#)TestRoundRobinJedisPool.java, 2014-12-1.
*
* Copyright (c) 2014 CodisLabs.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.codis.jodis;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import org.apache.curator.test.TestingServer;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.io.Closeables;
import io.codis.jodis.RoundRobinJedisPool;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisException;
/**
* @author Apache9
*/
public class TestRoundRobinJedisPool {
private ObjectMapper mapper = new ObjectMapper();
private int zkPort;
private File testDir = new File(getClass().getName());
private TestingServer zkServer;
private int redisPort1;
private RedisServer redis1;
private int redisPort2;
private RedisServer redis2;
private Jedis jedis1;
private Jedis jedis2;
private String zkProxyDir = "/" + getClass().getName();
private RoundRobinJedisPool jodisPool;
private static int probeFreePort() throws IOException {
try (ServerSocket ss = new ServerSocket(0)) {
ss.setReuseAddress(true);
return ss.getLocalPort();
}
}
private static void waitUntilRedisStarted(int port) throws InterruptedException {
for (;;) {
try (Jedis jedis = new Jedis("127.0.0.1", port)) {
if ("PONG".equals(jedis.ping())) {
break;
}
} catch (JedisException e) {}
Thread.sleep(100);
}
}
private void deleteDirectory(File directory) throws IOException {
if (!directory.exists()) {
return;
}
Files.walkFileTree(directory.toPath(), new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
private void addNode(String name, int port, String state)
throws IOException, InterruptedException, KeeperException {
ZooKeeper zk = new ZooKeeper("localhost:" + zkPort, 5000, null);
try {
if (zk.exists(zkProxyDir, null) == null) {
zk.create(zkProxyDir, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
ObjectNode node = mapper.createObjectNode();
node.put("addr", "127.0.0.1:" + port);
node.put("state", state);
zk.create(zkProxyDir + "/" + name, mapper.writer().writeValueAsBytes(node),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} finally {
zk.close();
}
}
private void removeNode(String name) throws InterruptedException, KeeperException, IOException {
ZooKeeper zk = new ZooKeeper("localhost:" + zkPort, 5000, null);
try {
zk.delete(zkProxyDir + "/" + name, -1);
} finally {
zk.close();
}
}
@Before
public void setUp() throws Exception {
deleteDirectory(testDir);
testDir.mkdirs();
zkServer = new TestingServer(-1, testDir, true);
zkPort = zkServer.getPort();
redisPort1 = probeFreePort();
redis1 = new RedisServer(redisPort1);
redis1.start();
waitUntilRedisStarted(redisPort1);
redisPort2 = probeFreePort();
redis2 = new RedisServer(redisPort2);
redis2.start();
waitUntilRedisStarted(redisPort2);
jedis1 = new Jedis("localhost", redisPort1);
jedis2 = new Jedis("localhost", redisPort2);
addNode("node1", redisPort1, "online");
jodisPool = RoundRobinJedisPool.create().curatorClient("localhost:" + zkPort, 5000)
.zkProxyDir(zkProxyDir).build();
}
@After
public void tearDown() throws IOException {
Closeables.close(jodisPool, true);
Closeables.close(jedis1, true);
Closeables.close(jedis2, true);
if (redis1 != null) {
redis1.stop();
}
if (redis2 != null) {
redis2.stop();
}
if (zkServer != null) {
zkServer.stop();
}
deleteDirectory(testDir);
}
@Test
public void test() throws IOException, InterruptedException, KeeperException {
try (Jedis jedis = jodisPool.getResource()) {
jedis.set("k1", "v1");
}
assertEquals("v1", jedis1.get("k1"));
// fake node
addNode("node2", 12345, "offline");
Thread.sleep(3000);
try (Jedis jedis = jodisPool.getResource()) {
jedis.set("k2", "v2");
}
assertEquals("v2", jedis1.get("k2"));
addNode("node3", redisPort2, "online");
Thread.sleep(3000);
try (Jedis jedis = jodisPool.getResource()) {
jedis.set("k3", "v3");
}
assertEquals("v3", jedis2.get("k3"));
removeNode("node1");
Thread.sleep(3000);
try (Jedis jedis = jodisPool.getResource()) {
jedis.set("k4", "v4");
}
assertEquals("v4", jedis2.get("k4"));
}
}
================================================
FILE: wandoujia-LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Wandoujia Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.