├── .gitignore ├── .travis.yml ├── LICENSE ├── wandoujia-LICENSE ├── README.md ├── src ├── main │ └── java │ │ └── io │ │ └── codis │ │ └── jodis │ │ ├── JedisResourcePool.java │ │ ├── CodisProxyInfo.java │ │ ├── JedisPoolAdaptor.java │ │ ├── BoundedExponentialBackoffRetryUntilElapsed.java │ │ └── RoundRobinJedisPool.java └── test │ └── java │ └── io │ └── codis │ └── jodis │ ├── RedisServer.java │ ├── TestBoundedExponentialBackoffRetryUntilElapsed.java │ └── TestRoundRobinJedisPool.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | target 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true -B -V 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 CodisLabs. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /wandoujia-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wandoujia Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jodis - Java client for codis 2 | 3 | [![Build Status](https://travis-ci.org/CodisLabs/jodis.svg)](https://travis-ci.org/CodisLabs/jodis) 4 | 5 | Jodis is a java client for codis based on [Jedis](https://github.com/xetorthio/jedis) and [Curator](http://curator.apache.org/). 6 | 7 | # Features 8 | - Use a round robin policy to balance load to multiple codis proxies. 9 | - Detect proxy online and offline automatically. 10 | 11 | # How to use 12 | Add this to your pom.xml. We deploy jodis to https://oss.sonatype.org. 13 | ```xml 14 | 15 | io.codis.jodis 16 | jodis 17 | 0.5.1 18 | 19 | ``` 20 | To use it for Codis2.x: 21 | ```java 22 | JedisResourcePool jedisPool = RoundRobinJedisPool.create() 23 | .curatorClient("zkserver:2181", 30000).zkProxyDir("/zk/codis/db_xxx/proxy").build(); 24 | try (Jedis jedis = jedisPool.getResource()) { 25 | jedis.set("foo", "bar"); 26 | String value = jedis.get("foo"); 27 | System.out.println(value); 28 | } 29 | ``` 30 | Or for Codis3.x with `jodis_compatible=false`: 31 | ```java 32 | JedisResourcePool jedisPool = RoundRobinJedisPool.create() 33 | .curatorClient("zkserver:2181", 30000).zkProxyDir("/jodis/xxx").build(); 34 | try (Jedis jedis = jedisPool.getResource()) { 35 | jedis.set("foo", "bar"); 36 | String value = jedis.get("foo"); 37 | System.out.println(value); 38 | } 39 | ``` 40 | Note: JDK8 is required to use and build jodis, as JDK7 has been EOL since May 2015. 41 | -------------------------------------------------------------------------------- /src/main/java/io/codis/jodis/JedisResourcePool.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @(#)JedisResourcePool.java, 2014-12-2. 3 | * 4 | * Copyright (c) 2014 CodisLabs. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package io.codis.jodis; 26 | 27 | import java.io.Closeable; 28 | 29 | import redis.clients.jedis.Jedis; 30 | 31 | /** 32 | * Describe a pool which we can acquire jedis instance. 33 | * 34 | * @author Apache9 35 | */ 36 | public interface JedisResourcePool extends Closeable { 37 | 38 | /** 39 | * Get a jedis instance from pool. 40 | *

41 | * We do not have a returnResource method, just close the jedis instance 42 | * returned directly. 43 | */ 44 | Jedis getResource(); 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/io/codis/jodis/RedisServer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @(#)RedisServer.java, 2014-11-30. 3 | * 4 | * Copyright (c) 2014 CodisLabs. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package io.codis.jodis; 26 | 27 | import java.io.IOException; 28 | 29 | /** 30 | * @author Apache9 31 | */ 32 | public class RedisServer { 33 | 34 | private final ProcessBuilder builder; 35 | 36 | private Process process; 37 | 38 | public RedisServer(int port) { 39 | builder = new ProcessBuilder().command("redis-server", "--port", 40 | Long.toString(port)).inheritIO(); 41 | } 42 | 43 | public void start() throws IOException { 44 | process = builder.start(); 45 | } 46 | 47 | public void stop() { 48 | if (process != null) { 49 | process.destroy(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/codis/jodis/CodisProxyInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @(#)CodisProxyInfo.java, 2015-10-14. 3 | * 4 | * Copyright (c) 2014 CodisLabs. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package io.codis.jodis; 26 | 27 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 28 | 29 | /** 30 | * We only care about the proxy address and if it is online. 31 | * 32 | * @author Apache9 33 | */ 34 | @JsonIgnoreProperties(ignoreUnknown = true) 35 | public class CodisProxyInfo { 36 | 37 | private String addr; 38 | 39 | private String state; 40 | 41 | public String getAddr() { 42 | return addr; 43 | } 44 | 45 | public void setAddr(String addr) { 46 | this.addr = addr; 47 | } 48 | 49 | public String getState() { 50 | return state; 51 | } 52 | 53 | public void setState(String state) { 54 | this.state = state; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/io/codis/jodis/TestBoundedExponentialBackoffRetryUntilElapsed.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @(#)TestBoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2. 3 | * 4 | * Copyright (c) 2014 CodisLabs. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package io.codis.jodis; 26 | 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertFalse; 29 | import static org.junit.Assert.assertTrue; 30 | 31 | import java.util.concurrent.ThreadLocalRandom; 32 | import java.util.concurrent.TimeUnit; 33 | 34 | import org.apache.curator.RetrySleeper; 35 | import org.junit.Test; 36 | 37 | /** 38 | * @author Apache9 39 | */ 40 | public class TestBoundedExponentialBackoffRetryUntilElapsed { 41 | 42 | private static final class FakeRetrySleeper implements RetrySleeper { 43 | 44 | public long sleepTimeMs; 45 | 46 | @Override 47 | public void sleepFor(long time, TimeUnit unit) { 48 | this.sleepTimeMs = unit.toMillis(time); 49 | } 50 | } 51 | 52 | @Test 53 | public void test() { 54 | FakeRetrySleeper sleeper = new FakeRetrySleeper(); 55 | BoundedExponentialBackoffRetryUntilElapsed r = new BoundedExponentialBackoffRetryUntilElapsed( 56 | 10, 2000, 60000); 57 | for (int i = 0; i < 100; i++) { 58 | assertTrue(r.allowRetry(i, ThreadLocalRandom.current().nextInt(60000), sleeper)); 59 | System.out.println(sleeper.sleepTimeMs); 60 | assertTrue(sleeper.sleepTimeMs <= 2000); 61 | } 62 | assertTrue(r.allowRetry(1000, 59900, sleeper)); 63 | System.out.println(sleeper.sleepTimeMs); 64 | assertTrue(sleeper.sleepTimeMs <= 100); 65 | sleeper.sleepTimeMs = -1L; 66 | assertFalse(r.allowRetry(1, 60000, sleeper)); 67 | assertEquals(-1L, sleeper.sleepTimeMs); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/codis/jodis/JedisPoolAdaptor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @(#)JedisPoolAdaptor.java, 2014-12-2. 3 | * 4 | * Copyright (c) 2014 CodisLabs. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package io.codis.jodis; 26 | 27 | import java.net.URI; 28 | 29 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 30 | 31 | import redis.clients.jedis.JedisPool; 32 | 33 | /** 34 | * Adaptor of JedisPool to make writing testcase easier. 35 | * 36 | * @author Apache9 37 | */ 38 | public class JedisPoolAdaptor extends JedisPool implements JedisResourcePool { 39 | 40 | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout, 41 | String password, int database, String clientName) { 42 | super(poolConfig, host, port, timeout, password, database, clientName); 43 | } 44 | 45 | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout, 46 | String password, int database) { 47 | super(poolConfig, host, port, timeout, password, database); 48 | } 49 | 50 | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout, 51 | String password) { 52 | super(poolConfig, host, port, timeout, password); 53 | } 54 | 55 | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout) { 56 | super(poolConfig, host, port, timeout); 57 | } 58 | 59 | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port) { 60 | super(poolConfig, host, port); 61 | } 62 | 63 | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host) { 64 | super(poolConfig, host); 65 | } 66 | 67 | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri, int timeout) { 68 | super(poolConfig, uri, timeout); 69 | } 70 | 71 | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri) { 72 | super(poolConfig, uri); 73 | } 74 | 75 | public JedisPoolAdaptor(String host, int port) { 76 | super(host, port); 77 | } 78 | 79 | public JedisPoolAdaptor(String host) { 80 | super(host); 81 | } 82 | 83 | public JedisPoolAdaptor(URI uri, int timeout) { 84 | super(uri, timeout); 85 | } 86 | 87 | public JedisPoolAdaptor(URI uri) { 88 | super(uri); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/codis/jodis/BoundedExponentialBackoffRetryUntilElapsed.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @(#)BoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2. 3 | * 4 | * Copyright (c) 2014 CodisLabs. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package io.codis.jodis; 26 | 27 | import java.util.concurrent.ThreadLocalRandom; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | import org.apache.curator.RetryPolicy; 31 | import org.apache.curator.RetrySleeper; 32 | 33 | /** 34 | * Similar to {@link org.apache.curator.retry.BoundedExponentialBackoffRetry}, 35 | * but limit the retry elapsed time, not retry number. 36 | * 37 | * @author Apache9 38 | */ 39 | public class BoundedExponentialBackoffRetryUntilElapsed implements RetryPolicy { 40 | 41 | private final int baseSleepTimeMs; 42 | 43 | private final int maxSleepTimeMs; 44 | 45 | private final long maxElapsedTimeMs; 46 | 47 | /** 48 | * @param baseSleepTimeMs 49 | * initial amount of time to wait between retries 50 | * @param maxSleepTimeMs 51 | * max time in ms to sleep on each retry 52 | * @param maxElapsedTimeMs 53 | * total time in ms to retry 54 | */ 55 | public BoundedExponentialBackoffRetryUntilElapsed(int baseSleepTimeMs, int maxSleepTimeMs, 56 | long maxElapsedTimeMs) { 57 | this.baseSleepTimeMs = baseSleepTimeMs; 58 | this.maxSleepTimeMs = maxSleepTimeMs; 59 | if (maxElapsedTimeMs < 0) { 60 | this.maxElapsedTimeMs = Long.MAX_VALUE; 61 | } else { 62 | this.maxElapsedTimeMs = maxElapsedTimeMs; 63 | } 64 | } 65 | 66 | private long getSleepTimeMs(int retryCount, long elapsedTimeMs) { 67 | return Math.min( 68 | maxSleepTimeMs, 69 | (long) baseSleepTimeMs 70 | * Math.max( 71 | 1, 72 | ThreadLocalRandom.current().nextInt( 73 | 1 << Math.min(30, retryCount + 1)))); 74 | } 75 | 76 | @Override 77 | public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper) { 78 | if (elapsedTimeMs >= maxElapsedTimeMs) { 79 | return false; 80 | } 81 | long sleepTimeMs = Math.min(maxElapsedTimeMs - elapsedTimeMs, 82 | getSleepTimeMs(retryCount, elapsedTimeMs)); 83 | try { 84 | sleeper.sleepFor(sleepTimeMs, TimeUnit.MILLISECONDS); 85 | } catch (InterruptedException e) { 86 | Thread.currentThread().interrupt(); 87 | return false; 88 | } 89 | return true; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/io/codis/jodis/TestRoundRobinJedisPool.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @(#)TestRoundRobinJedisPool.java, 2014-12-1. 3 | * 4 | * Copyright (c) 2014 CodisLabs. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package io.codis.jodis; 26 | 27 | import static org.junit.Assert.assertEquals; 28 | 29 | import java.io.File; 30 | import java.io.IOException; 31 | import java.net.ServerSocket; 32 | import java.nio.file.FileVisitResult; 33 | import java.nio.file.Files; 34 | import java.nio.file.Path; 35 | import java.nio.file.SimpleFileVisitor; 36 | import java.nio.file.attribute.BasicFileAttributes; 37 | 38 | import org.apache.curator.test.TestingServer; 39 | import org.apache.zookeeper.CreateMode; 40 | import org.apache.zookeeper.KeeperException; 41 | import org.apache.zookeeper.ZooDefs; 42 | import org.apache.zookeeper.ZooKeeper; 43 | import org.junit.After; 44 | import org.junit.Before; 45 | import org.junit.Test; 46 | 47 | import com.fasterxml.jackson.databind.ObjectMapper; 48 | import com.fasterxml.jackson.databind.node.ObjectNode; 49 | import com.google.common.io.Closeables; 50 | 51 | import io.codis.jodis.RoundRobinJedisPool; 52 | import redis.clients.jedis.Jedis; 53 | import redis.clients.jedis.exceptions.JedisException; 54 | 55 | /** 56 | * @author Apache9 57 | */ 58 | public class TestRoundRobinJedisPool { 59 | 60 | private ObjectMapper mapper = new ObjectMapper(); 61 | 62 | private int zkPort; 63 | 64 | private File testDir = new File(getClass().getName()); 65 | 66 | private TestingServer zkServer; 67 | 68 | private int redisPort1; 69 | 70 | private RedisServer redis1; 71 | 72 | private int redisPort2; 73 | 74 | private RedisServer redis2; 75 | 76 | private Jedis jedis1; 77 | 78 | private Jedis jedis2; 79 | 80 | private String zkProxyDir = "/" + getClass().getName(); 81 | 82 | private RoundRobinJedisPool jodisPool; 83 | 84 | private static int probeFreePort() throws IOException { 85 | try (ServerSocket ss = new ServerSocket(0)) { 86 | ss.setReuseAddress(true); 87 | return ss.getLocalPort(); 88 | } 89 | } 90 | 91 | private static void waitUntilRedisStarted(int port) throws InterruptedException { 92 | for (;;) { 93 | try (Jedis jedis = new Jedis("127.0.0.1", port)) { 94 | if ("PONG".equals(jedis.ping())) { 95 | break; 96 | } 97 | } catch (JedisException e) {} 98 | Thread.sleep(100); 99 | } 100 | } 101 | 102 | private void deleteDirectory(File directory) throws IOException { 103 | if (!directory.exists()) { 104 | return; 105 | } 106 | Files.walkFileTree(directory.toPath(), new SimpleFileVisitor() { 107 | 108 | @Override 109 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 110 | throws IOException { 111 | Files.delete(file); 112 | return FileVisitResult.CONTINUE; 113 | } 114 | 115 | @Override 116 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) 117 | throws IOException { 118 | Files.delete(dir); 119 | return FileVisitResult.CONTINUE; 120 | } 121 | 122 | }); 123 | } 124 | 125 | private void addNode(String name, int port, String state) 126 | throws IOException, InterruptedException, KeeperException { 127 | ZooKeeper zk = new ZooKeeper("localhost:" + zkPort, 5000, null); 128 | try { 129 | if (zk.exists(zkProxyDir, null) == null) { 130 | zk.create(zkProxyDir, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 131 | } 132 | ObjectNode node = mapper.createObjectNode(); 133 | node.put("addr", "127.0.0.1:" + port); 134 | node.put("state", state); 135 | zk.create(zkProxyDir + "/" + name, mapper.writer().writeValueAsBytes(node), 136 | ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 137 | } finally { 138 | zk.close(); 139 | } 140 | } 141 | 142 | private void removeNode(String name) throws InterruptedException, KeeperException, IOException { 143 | ZooKeeper zk = new ZooKeeper("localhost:" + zkPort, 5000, null); 144 | try { 145 | zk.delete(zkProxyDir + "/" + name, -1); 146 | } finally { 147 | zk.close(); 148 | } 149 | } 150 | 151 | @Before 152 | public void setUp() throws Exception { 153 | deleteDirectory(testDir); 154 | testDir.mkdirs(); 155 | zkServer = new TestingServer(-1, testDir, true); 156 | zkPort = zkServer.getPort(); 157 | redisPort1 = probeFreePort(); 158 | redis1 = new RedisServer(redisPort1); 159 | redis1.start(); 160 | waitUntilRedisStarted(redisPort1); 161 | redisPort2 = probeFreePort(); 162 | redis2 = new RedisServer(redisPort2); 163 | redis2.start(); 164 | waitUntilRedisStarted(redisPort2); 165 | 166 | jedis1 = new Jedis("localhost", redisPort1); 167 | jedis2 = new Jedis("localhost", redisPort2); 168 | addNode("node1", redisPort1, "online"); 169 | jodisPool = RoundRobinJedisPool.create().curatorClient("localhost:" + zkPort, 5000) 170 | .zkProxyDir(zkProxyDir).build(); 171 | } 172 | 173 | @After 174 | public void tearDown() throws IOException { 175 | Closeables.close(jodisPool, true); 176 | Closeables.close(jedis1, true); 177 | Closeables.close(jedis2, true); 178 | if (redis1 != null) { 179 | redis1.stop(); 180 | } 181 | if (redis2 != null) { 182 | redis2.stop(); 183 | } 184 | if (zkServer != null) { 185 | zkServer.stop(); 186 | } 187 | deleteDirectory(testDir); 188 | } 189 | 190 | @Test 191 | public void test() throws IOException, InterruptedException, KeeperException { 192 | try (Jedis jedis = jodisPool.getResource()) { 193 | jedis.set("k1", "v1"); 194 | } 195 | assertEquals("v1", jedis1.get("k1")); 196 | // fake node 197 | addNode("node2", 12345, "offline"); 198 | Thread.sleep(3000); 199 | try (Jedis jedis = jodisPool.getResource()) { 200 | jedis.set("k2", "v2"); 201 | } 202 | assertEquals("v2", jedis1.get("k2")); 203 | 204 | addNode("node3", redisPort2, "online"); 205 | Thread.sleep(3000); 206 | try (Jedis jedis = jodisPool.getResource()) { 207 | jedis.set("k3", "v3"); 208 | } 209 | assertEquals("v3", jedis2.get("k3")); 210 | 211 | removeNode("node1"); 212 | Thread.sleep(3000); 213 | try (Jedis jedis = jodisPool.getResource()) { 214 | jedis.set("k4", "v4"); 215 | } 216 | assertEquals("v4", jedis2.get("k4")); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 23 | 25 | 4.0.0 26 | io.codis.jodis 27 | jodis 28 | 0.6.0-SNAPSHOT 29 | jar 30 | ${project.artifactId} 31 | Round Robin jedis pool for codis 32 | https://github.com/CodisLabs/jodis 33 | 34 | scm:git:ssh://github.com:CodisLabs/jodis.git 35 | scm:git:ssh://github.com:CodisLabs/jodis.git 36 | ssh://github.com:CodisLabs/codis.git 37 | HEAD 38 | 39 | 40 | 1.7 41 | UTF-8 42 | UTF-8 43 | UTF-8 44 | UTF-8 45 | 3.4.11 46 | 4.0.1 47 | 2.12.0 48 | 2.9.0 49 | 2.9.5 50 | 51 | 52 | 53 | 54 | redis.clients 55 | jedis 56 | ${jedis.version} 57 | 58 | 59 | org.apache.zookeeper 60 | zookeeper 61 | ${zookeeper.version} 62 | 63 | 64 | org.apache.curator 65 | curator-recipes 66 | ${curator.version} 67 | 68 | 69 | org.apache.zookeeper 70 | zookeeper 71 | 72 | 73 | 74 | 75 | org.slf4j 76 | slf4j-api 77 | 1.7.12 78 | 79 | 80 | com.google.guava 81 | guava 82 | 23.0 83 | 84 | 85 | com.fasterxml.jackson.core 86 | jackson-databind 87 | ${jackson.version} 88 | 89 | 90 | junit 91 | junit 92 | 4.12 93 | test 94 | 95 | 96 | org.slf4j 97 | slf4j-simple 98 | 1.7.12 99 | test 100 | 101 | 102 | org.apache.curator 103 | curator-test 104 | ${curator-test.version} 105 | test 106 | 107 | 108 | org.apache.zookeeper 109 | zookeeper 110 | 111 | 112 | 113 | 114 | 115 | 116 | ossrh 117 | https://oss.sonatype.org/content/repositories/snapshots 118 | 119 | 120 | ossrh 121 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-compiler-plugin 129 | 2.5.1 130 | 131 | ${java.version} 132 | ${java.version} 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-source-plugin 138 | 2.4 139 | 140 | 141 | attach-sources 142 | 143 | jar-no-fork 144 | 145 | 146 | 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-javadoc-plugin 151 | 2.10.3 152 | 153 | -Xdoclint:none 154 | 155 | 156 | 157 | attach-javadocs 158 | 159 | jar 160 | 161 | 162 | 163 | 164 | 165 | org.apache.maven.plugins 166 | maven-gpg-plugin 167 | 1.5 168 | 169 | 170 | sign-artifacts 171 | verify 172 | 173 | sign 174 | 175 | 176 | 177 | 178 | 179 | org.sonatype.plugins 180 | nexus-staging-maven-plugin 181 | 1.6.7 182 | true 183 | 184 | ossrh 185 | https://oss.sonatype.org/ 186 | false 187 | 188 | 189 | 190 | org.jacoco 191 | jacoco-maven-plugin 192 | 0.7.5.201505241946 193 | 194 | 195 | default-prepare-agent 196 | 197 | prepare-agent 198 | 199 | 200 | 201 | default-report 202 | prepare-package 203 | 204 | report 205 | 206 | 207 | 208 | 209 | 210 | org.apache.maven.plugins 211 | maven-surefire-plugin 212 | 2.18.1 213 | 214 | always 215 | true 216 | ${argLine} -Dfile.encoding=UTF-8 217 | 218 | 219 | 220 | org.apache.maven.plugins 221 | maven-surefire-report-plugin 222 | 2.18.1 223 | 224 | 225 | org.apache.maven.plugins 226 | maven-deploy-plugin 227 | 2.8.2 228 | 229 | 230 | deploy 231 | deploy 232 | 233 | deploy 234 | 235 | 236 | 237 | default-deploy 238 | 239 | true 240 | 241 | 242 | 243 | 244 | 245 | org.apache.maven.plugins 246 | maven-site-plugin 247 | 3.4 248 | 249 | 250 | 251 | org.codehaus.mojo 252 | findbugs-maven-plugin 253 | 254 | 255 | org.jacoco 256 | jacoco-maven-plugin 257 | 258 | 259 | 260 | 261 | 262 | org.codehaus.mojo 263 | findbugs-maven-plugin 264 | 3.0.2 265 | 266 | true 267 | target/findbugs 268 | target/findbugs 269 | 270 | 271 | 272 | 273 | 274 | 275 | MIT License 276 | http://opensource.org/licenses/MIT 277 | repo 278 | 279 | 280 | 281 | 282 | apache9 283 | Apache9 284 | palomino219@gmail.com 285 | 286 | developer 287 | 288 | +8 289 | 290 | 291 | 292 | -------------------------------------------------------------------------------- /src/main/java/io/codis/jodis/RoundRobinJedisPool.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @(#)RoundRobinJedisPool.java, 2014-11-30. 3 | * 4 | * Copyright (c) 2014 CodisLabs. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package io.codis.jodis; 26 | 27 | import static org.apache.curator.framework.imps.CuratorFrameworkState.LATENT; 28 | import static org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode.BUILD_INITIAL_CACHE; 29 | import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_ADDED; 30 | import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; 31 | import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_UPDATED; 32 | 33 | import java.io.IOException; 34 | import java.util.List; 35 | import java.util.Map; 36 | import java.util.concurrent.ScheduledThreadPoolExecutor; 37 | import java.util.concurrent.TimeUnit; 38 | import java.util.concurrent.atomic.AtomicInteger; 39 | 40 | import org.apache.curator.framework.CuratorFramework; 41 | import org.apache.curator.framework.CuratorFrameworkFactory; 42 | import org.apache.curator.framework.recipes.cache.ChildData; 43 | import org.apache.curator.framework.recipes.cache.PathChildrenCache; 44 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; 45 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; 46 | import org.slf4j.Logger; 47 | import org.slf4j.LoggerFactory; 48 | 49 | import com.fasterxml.jackson.databind.ObjectMapper; 50 | import com.google.common.base.Preconditions; 51 | import com.google.common.collect.ImmutableList; 52 | import com.google.common.collect.ImmutableSet; 53 | import com.google.common.collect.Maps; 54 | import com.google.common.collect.Sets; 55 | import com.google.common.io.Closeables; 56 | 57 | import redis.clients.jedis.Jedis; 58 | import redis.clients.jedis.JedisPool; 59 | import redis.clients.jedis.JedisPoolConfig; 60 | import redis.clients.jedis.Protocol; 61 | import redis.clients.jedis.exceptions.JedisException; 62 | 63 | /** 64 | * A round robin connection pool for connecting multiple codis proxies based on 65 | * Jedis and Curator. 66 | * 67 | * @author Apache9 68 | * @see https://github.com/xetorthio/jedis 69 | * @see http://curator.apache.org/ 70 | */ 71 | public class RoundRobinJedisPool implements JedisResourcePool { 72 | 73 | private static final Logger LOG = LoggerFactory.getLogger(RoundRobinJedisPool.class); 74 | 75 | private static final ObjectMapper MAPPER = new ObjectMapper(); 76 | 77 | private static final String CODIS_PROXY_STATE_ONLINE = "online"; 78 | 79 | private static final int CURATOR_RETRY_BASE_SLEEP_MS = 100; 80 | 81 | private static final int CURATOR_RETRY_MAX_SLEEP_MS = 30 * 1000; 82 | 83 | private static final long DELAY_BEFORE_CLOSING_POOL = 10000; // milliseconds 84 | 85 | private static final ImmutableSet RESET_TYPES = Sets 86 | .immutableEnumSet(CHILD_ADDED, CHILD_UPDATED, CHILD_REMOVED); 87 | 88 | private final CuratorFramework curatorClient; 89 | 90 | private final boolean closeCurator; 91 | 92 | private final PathChildrenCache watcher; 93 | 94 | private static final class PooledObject { 95 | public final String addr; 96 | 97 | public final JedisPool pool; 98 | 99 | public PooledObject(String addr, JedisPool pool) { 100 | this.addr = addr; 101 | this.pool = pool; 102 | } 103 | 104 | public Jedis getResource() { 105 | return pool.getResource(); 106 | } 107 | 108 | public void close() { 109 | try { 110 | pool.close(); 111 | LOG.info("Connection pool to {} closed", addr); 112 | } catch (Exception e) { 113 | LOG.error("Error closing connection pool to " + addr, e); 114 | } 115 | } 116 | } 117 | 118 | private volatile ImmutableList pools = ImmutableList.of(); 119 | 120 | private final AtomicInteger nextIdx = new AtomicInteger(-1); 121 | 122 | private final JedisPoolConfig poolConfig; 123 | 124 | private final int connectionTimeoutMs; 125 | 126 | private final int soTimeoutMs; 127 | 128 | private final String password; 129 | 130 | private final int database; 131 | 132 | private final String clientName; 133 | 134 | private final ScheduledThreadPoolExecutor jedisPoolClosingExecutor = 135 | new ScheduledThreadPoolExecutor(1); 136 | 137 | private RoundRobinJedisPool(CuratorFramework curatorClient, boolean closeCurator, 138 | String zkProxyDir, JedisPoolConfig poolConfig, int connectionTimeoutMs, int soTimeoutMs, 139 | String password, int database, String clientName) { 140 | this.poolConfig = poolConfig; 141 | this.connectionTimeoutMs = connectionTimeoutMs; 142 | this.soTimeoutMs = soTimeoutMs; 143 | this.password = password; 144 | this.database = database; 145 | this.clientName = clientName; 146 | this.curatorClient = curatorClient; 147 | this.closeCurator = closeCurator; 148 | watcher = new PathChildrenCache(curatorClient, zkProxyDir, true); 149 | watcher.getListenable().addListener(new PathChildrenCacheListener() { 150 | 151 | private void logEvent(PathChildrenCacheEvent event) { 152 | StringBuilder msg = new StringBuilder("Receive child event: "); 153 | msg.append("type=").append(event.getType()); 154 | ChildData data = event.getData(); 155 | if (data != null) { 156 | msg.append(", path=").append(data.getPath()); 157 | msg.append(", stat=").append(data.getStat()); 158 | if (data.getData() != null) { 159 | msg.append(", bytes length=").append(data.getData().length); 160 | } else { 161 | msg.append(", no bytes"); 162 | } 163 | } else { 164 | msg.append(", no data"); 165 | } 166 | LOG.info(msg.toString()); 167 | } 168 | 169 | @Override 170 | public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) 171 | throws Exception { 172 | synchronized (RoundRobinJedisPool.this) { 173 | logEvent(event); 174 | if (RESET_TYPES.contains(event.getType())) { 175 | resetPools(); 176 | } 177 | } 178 | } 179 | }); 180 | synchronized (this) { 181 | try { 182 | watcher.start(BUILD_INITIAL_CACHE); 183 | } catch (Exception e) { 184 | close(); 185 | throw new JedisException(e); 186 | } 187 | resetPools(); 188 | } 189 | } 190 | 191 | private void resetPools() { 192 | ImmutableList pools = this.pools; 193 | Map addr2Pool = Maps.newHashMapWithExpectedSize(pools.size()); 194 | for (PooledObject pool: pools) { 195 | addr2Pool.put(pool.addr, pool); 196 | } 197 | ImmutableList.Builder builder = ImmutableList.builder(); 198 | for (ChildData childData : watcher.getCurrentData()) { 199 | try { 200 | CodisProxyInfo proxyInfo = MAPPER.readValue(childData.getData(), 201 | CodisProxyInfo.class); 202 | if (!CODIS_PROXY_STATE_ONLINE.equals(proxyInfo.getState())) { 203 | continue; 204 | } 205 | String addr = proxyInfo.getAddr(); 206 | PooledObject pool = addr2Pool.remove(addr); 207 | if (pool == null) { 208 | String[] hostAndPort = addr.split(":"); 209 | String host = hostAndPort[0]; 210 | int port = Integer.parseInt(hostAndPort[1]); 211 | pool = new PooledObject(addr, 212 | new JedisPool(poolConfig, host, port, connectionTimeoutMs, soTimeoutMs, 213 | password, database, clientName, false, null, null, null)); 214 | LOG.info("Add new proxy: " + addr); 215 | } 216 | builder.add(pool); 217 | } catch (Exception e) { 218 | LOG.warn("parse " + childData.getPath() + " failed", e); 219 | } 220 | } 221 | this.pools = builder.build(); 222 | for (final PooledObject pool: addr2Pool.values()) { 223 | LOG.info("Remove proxy: " + pool.addr); 224 | jedisPoolClosingExecutor.schedule(new Runnable() { 225 | @Override 226 | public void run() { 227 | pool.close(); 228 | } 229 | }, DELAY_BEFORE_CLOSING_POOL, TimeUnit.MILLISECONDS); 230 | } 231 | } 232 | 233 | @Override 234 | public Jedis getResource() { 235 | ImmutableList pools = this.pools; 236 | if (pools.isEmpty()) { 237 | throw new JedisException("Proxy list empty"); 238 | } 239 | for (;;) { 240 | int current = nextIdx.get(); 241 | int next = current >= pools.size() - 1 ? 0 : current + 1; 242 | if (nextIdx.compareAndSet(current, next)) { 243 | return pools.get(next).getResource(); 244 | } 245 | } 246 | } 247 | 248 | @Override 249 | public void close() { 250 | try { 251 | Closeables.close(watcher, true); 252 | } catch (IOException e) { 253 | throw new AssertionError("IOException should not have been thrown", e); 254 | } 255 | if (closeCurator) { 256 | curatorClient.close(); 257 | } 258 | List pools = this.pools; 259 | this.pools = ImmutableList.of(); 260 | for (PooledObject pool: pools) { 261 | pool.close(); 262 | } 263 | jedisPoolClosingExecutor.shutdown(); 264 | } 265 | 266 | /** 267 | * Create a {@link RoundRobinJedisPool} using the fluent style api. 268 | * 269 | * @return 270 | */ 271 | public static Builder create() { 272 | return new Builder(); 273 | } 274 | 275 | public static final class Builder { 276 | 277 | private CuratorFramework curatorClient; 278 | 279 | private boolean closeCurator; 280 | 281 | private String zkProxyDir; 282 | 283 | private String zkAddr; 284 | 285 | private int zkSessionTimeoutMs; 286 | 287 | private JedisPoolConfig poolConfig; 288 | 289 | private int connectionTimeoutMs = Protocol.DEFAULT_TIMEOUT; 290 | 291 | private int soTimeoutMs = Protocol.DEFAULT_TIMEOUT; 292 | 293 | private String password; 294 | 295 | private int database = Protocol.DEFAULT_DATABASE; 296 | 297 | private String clientName; 298 | 299 | private Builder() {} 300 | 301 | /** 302 | * Set curator client. 303 | * 304 | * @param curatorClient 305 | * the client to be used 306 | * @param closeCurator 307 | * whether to close curator client while closing pool 308 | */ 309 | public Builder curatorClient(CuratorFramework curatorClient, boolean closeCurator) { 310 | this.curatorClient = curatorClient; 311 | this.closeCurator = closeCurator; 312 | return this; 313 | } 314 | 315 | /** 316 | * Set codis proxy path on zk. 317 | * 318 | * @param zkProxyDir 319 | * the codis proxy dir on ZooKeeper. e.g., 320 | * "/zk/codis/db_xxx/proxy" 321 | */ 322 | public Builder zkProxyDir(String zkProxyDir) { 323 | this.zkProxyDir = zkProxyDir; 324 | return this; 325 | } 326 | 327 | /** 328 | * Set curator client. 329 | *

330 | * We will create curator client based on these parameters and close it 331 | * while closing pool. 332 | * 333 | * @param zkAddr 334 | * ZooKeeper connect string. e.g., "zk1:2181" 335 | * @param zkSessionTimeoutMs 336 | * ZooKeeper session timeout in milliseconds 337 | */ 338 | public Builder curatorClient(String zkAddr, int zkSessionTimeoutMs) { 339 | this.zkAddr = zkAddr; 340 | this.zkSessionTimeoutMs = zkSessionTimeoutMs; 341 | return this; 342 | } 343 | 344 | /** 345 | * Set jedis pool config. 346 | */ 347 | public Builder poolConfig(JedisPoolConfig poolConfig) { 348 | this.poolConfig = poolConfig; 349 | return this; 350 | } 351 | 352 | /** 353 | * Set jedis pool timeout in milliseconds. 354 | *

355 | * We will set connectionTimeoutMs and soTimeoutMs both. 356 | * 357 | * @param timeoutMs 358 | * timeout is milliseconds 359 | */ 360 | public Builder timeoutMs(int timeoutMs) { 361 | this.connectionTimeoutMs = this.soTimeoutMs = timeoutMs; 362 | return this; 363 | } 364 | 365 | /** 366 | * Set jedis pool connection timeout in milliseconds. 367 | * 368 | * @param connectionTimeoutMs 369 | * timeout is milliseconds 370 | */ 371 | public Builder connectionTimeoutMs(int connectionTimeoutMs) { 372 | this.connectionTimeoutMs = connectionTimeoutMs; 373 | return this; 374 | } 375 | 376 | /** 377 | * Set jedis pool connection soTimeout in milliseconds. 378 | * 379 | * @param soTimeoutMs 380 | * timeout is milliseconds 381 | */ 382 | public Builder soTimeoutMs(int soTimeoutMs) { 383 | this.soTimeoutMs = soTimeoutMs; 384 | return this; 385 | } 386 | 387 | /** 388 | * Set password. 389 | */ 390 | public Builder password(String password) { 391 | this.password = password; 392 | return this; 393 | } 394 | 395 | /** 396 | * Set redis database. 397 | */ 398 | public Builder database(int database) { 399 | this.database = database; 400 | return this; 401 | } 402 | 403 | /** 404 | * Set redis client name. 405 | */ 406 | public Builder clientName(String clientName) { 407 | this.clientName = clientName; 408 | return this; 409 | } 410 | 411 | private void validate() { 412 | Preconditions.checkNotNull(zkProxyDir, "zkProxyDir can not be null"); 413 | if (curatorClient == null) { 414 | Preconditions.checkNotNull(zkAddr, "zk client can not be null"); 415 | curatorClient = CuratorFrameworkFactory.builder().connectString(zkAddr) 416 | .sessionTimeoutMs(zkSessionTimeoutMs) 417 | .retryPolicy(new BoundedExponentialBackoffRetryUntilElapsed( 418 | CURATOR_RETRY_BASE_SLEEP_MS, CURATOR_RETRY_MAX_SLEEP_MS, -1L)) 419 | .build(); 420 | curatorClient.start(); 421 | closeCurator = true; 422 | } else { 423 | // we need to get the initial data so client must be started 424 | if (curatorClient.getState() == LATENT) { 425 | curatorClient.start(); 426 | } 427 | } 428 | if (poolConfig == null) { 429 | poolConfig = new JedisPoolConfig(); 430 | } 431 | } 432 | 433 | /** 434 | * Create the {@link RoundRobinJedisPool}. 435 | */ 436 | public RoundRobinJedisPool build() { 437 | validate(); 438 | return new RoundRobinJedisPool(curatorClient, closeCurator, zkProxyDir, poolConfig, 439 | connectionTimeoutMs, soTimeoutMs, password, database, clientName); 440 | } 441 | } 442 | } 443 | --------------------------------------------------------------------------------