From baab2225f650010bb7eea7b0f0e4eac7327a1a25 Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 01:30:59 -0300 Subject: [PATCH 01/11] Added sharding using ketama --- .../redis/clients/jedis/ShardedJedis.java | 356 ++++++++++++++++++ .../java/redis/clients/util/ShardInfo.java | 98 +++++ src/main/java/redis/clients/util/Sharded.java | 102 +++++ .../redis/clients/jedis/tests/JedisTest.java | 1 + .../clients/jedis/tests/ShardedJedisTest.java | 53 +++ 5 files changed, 610 insertions(+) create mode 100644 src/main/java/redis/clients/jedis/ShardedJedis.java create mode 100644 src/main/java/redis/clients/util/ShardInfo.java create mode 100644 src/main/java/redis/clients/util/Sharded.java create mode 100644 src/test/java/redis/clients/jedis/tests/ShardedJedisTest.java diff --git a/src/main/java/redis/clients/jedis/ShardedJedis.java b/src/main/java/redis/clients/jedis/ShardedJedis.java new file mode 100644 index 0000000..d3b6266 --- /dev/null +++ b/src/main/java/redis/clients/jedis/ShardedJedis.java @@ -0,0 +1,356 @@ +package redis.clients.jedis; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import redis.clients.util.ShardInfo; +import redis.clients.util.Sharded; + +public class ShardedJedis extends Sharded { + public ShardedJedis(List shards) { + super(shards); + } + + public String set(String key, String value) { + Jedis j = getShard(key); + return j.set(key, value); + } + + public String get(String key) { + Jedis j = getShard(key); + return j.get(key); + } + + public int exists(String key) { + Jedis j = getShard(key); + return j.exists(key); + } + + public String type(String key) { + Jedis j = getShard(key); + return j.type(key); + } + + public int expire(String key, int seconds) { + Jedis j = getShard(key); + return j.expire(key, seconds); + } + + public int expireAt(String key, long unixTime) { + Jedis j = getShard(key); + return j.expireAt(key, unixTime); + } + + public int ttl(String key) { + Jedis j = getShard(key); + return j.ttl(key); + } + + public String getSet(String key, String value) { + Jedis j = getShard(key); + return j.getSet(key, value); + } + + public int setnx(String key, String value) { + Jedis j = getShard(key); + return j.setnx(key, value); + } + + public String setex(String key, int seconds, String value) { + Jedis j = getShard(key); + return j.setex(key, seconds, value); + } + + public int decrBy(String key, int integer) { + Jedis j = getShard(key); + return j.decrBy(key, integer); + } + + public int decr(String key) { + Jedis j = getShard(key); + return j.decr(key); + } + + public int incrBy(String key, int integer) { + Jedis j = getShard(key); + return j.incrBy(key, integer); + } + + public int incr(String key) { + Jedis j = getShard(key); + return j.incr(key); + } + + public int append(String key, String value) { + Jedis j = getShard(key); + return j.append(key, value); + } + + public String substr(String key, int start, int end) { + Jedis j = getShard(key); + return j.substr(key, start, end); + } + + public int hset(String key, String field, String value) { + Jedis j = getShard(key); + return j.hset(key, field, value); + } + + public String hget(String key, String field) { + Jedis j = getShard(key); + return j.hget(key, field); + } + + public int hsetnx(String key, String field, String value) { + Jedis j = getShard(key); + return j.hsetnx(key, field, value); + } + + public String hmset(String key, Map hash) { + Jedis j = getShard(key); + return j.hmset(key, hash); + } + + public List hmget(String key, String... fields) { + Jedis j = getShard(key); + return j.hmget(key, fields); + } + + public int hincrBy(String key, String field, int value) { + Jedis j = getShard(key); + return j.hincrBy(key, field, value); + } + + public int hexists(String key, String field) { + Jedis j = getShard(key); + return j.hexists(key, field); + } + + public int hdel(String key, String field) { + Jedis j = getShard(key); + return j.hdel(key, field); + } + + public int hlen(String key) { + Jedis j = getShard(key); + return j.hlen(key); + } + + public List hkeys(String key) { + Jedis j = getShard(key); + return j.hkeys(key); + } + + public List hvals(String key) { + Jedis j = getShard(key); + return j.hvals(key); + } + + public Map hgetAll(String key) { + Jedis j = getShard(key); + return j.hgetAll(key); + } + + public int rpush(String key, String string) { + Jedis j = getShard(key); + return j.rpush(key, string); + } + + public int lpush(String key, String string) { + Jedis j = getShard(key); + return j.lpush(key, string); + } + + public int llen(String key) { + Jedis j = getShard(key); + return j.llen(key); + } + + public List lrange(String key, int start, int end) { + Jedis j = getShard(key); + return j.lrange(key, start, end); + } + + public String ltrim(String key, int start, int end) { + Jedis j = getShard(key); + return j.ltrim(key, start, end); + } + + public String lindex(String key, int index) { + Jedis j = getShard(key); + return j.lindex(key, index); + } + + public String lset(String key, int index, String value) { + Jedis j = getShard(key); + return j.lset(key, index, value); + } + + public int lrem(String key, int count, String value) { + Jedis j = getShard(key); + return j.lrem(key, count, value); + } + + public String lpop(String key) { + Jedis j = getShard(key); + return j.lpop(key); + } + + public String rpop(String key) { + Jedis j = getShard(key); + return j.rpop(key); + } + + public int sadd(String key, String member) { + Jedis j = getShard(key); + return j.sadd(key, member); + } + + public Set smembers(String key) { + Jedis j = getShard(key); + return j.smembers(key); + } + + public int srem(String key, String member) { + Jedis j = getShard(key); + return j.srem(key, member); + } + + public String spop(String key) { + Jedis j = getShard(key); + return j.spop(key); + } + + public int scard(String key) { + Jedis j = getShard(key); + return j.scard(key); + } + + public int sismember(String key, String member) { + Jedis j = getShard(key); + return j.sismember(key, member); + } + + public String srandmember(String key) { + Jedis j = getShard(key); + return j.srandmember(key); + } + + public int zadd(String key, double score, String member) { + Jedis j = getShard(key); + return j.zadd(key, score, member); + } + + public Set zrange(String key, int start, int end) { + Jedis j = getShard(key); + return j.zrange(key, start, end); + } + + public int zrem(String key, String member) { + Jedis j = getShard(key); + return j.zrem(key, member); + } + + public double zincrby(String key, double score, String member) { + Jedis j = getShard(key); + return j.zincrby(key, score, member); + } + + public int zrank(String key, String member) { + Jedis j = getShard(key); + return j.zrank(key, member); + } + + public int zrevrank(String key, String member) { + Jedis j = getShard(key); + return j.zrevrank(key, member); + } + + public Set zrevrange(String key, int start, int end) { + Jedis j = getShard(key); + return j.zrevrange(key, start, end); + } + + public Set zrangeWithScores(String key, int start, int end) { + Jedis j = getShard(key); + return j.zrangeWithScores(key, start, end); + } + + public Set zrevrangeWithScores(String key, int start, int end) { + Jedis j = getShard(key); + return j.zrevrangeWithScores(key, start, end); + } + + public int zcard(String key) { + Jedis j = getShard(key); + return j.zcard(key); + } + + public double zscore(String key, String member) { + Jedis j = getShard(key); + return j.zscore(key, member); + } + + public List sort(String key) { + Jedis j = getShard(key); + return j.sort(key); + } + + public List sort(String key, SortingParams sortingParameters) { + Jedis j = getShard(key); + return j.sort(key, sortingParameters); + } + + public int zcount(String key, double min, double max) { + Jedis j = getShard(key); + return j.zcount(key, min, max); + } + + public Set zrangeByScore(String key, double min, double max) { + Jedis j = getShard(key); + return j.zrangeByScore(key, min, max); + } + + public Set zrangeByScore(String key, double min, double max, + int offset, int count) { + Jedis j = getShard(key); + return j.zrangeByScore(key, min, max, offset, count); + } + + public Set zrangeByScoreWithScores(String key, double min, double max) { + Jedis j = getShard(key); + return j.zrangeByScoreWithScores(key, min, max); + } + + public Set zrangeByScoreWithScores(String key, double min, + double max, int offset, int count) { + Jedis j = getShard(key); + return j.zrangeByScoreWithScores(key, min, max, offset, count); + } + + public int zremrangeByRank(String key, int start, int end) { + Jedis j = getShard(key); + return j.zremrangeByRank(key, start, end); + } + + public int zremrangeByScore(String key, double start, double end) { + Jedis j = getShard(key); + return j.zremrangeByScore(key, start, end); + } + + public void disconnect() throws IOException { + for (Jedis jedis : getAllShards()) { + jedis.disconnect(); + } + } + + protected Jedis create(ShardInfo shard) { + Jedis c = new Jedis(shard.getHost(), shard.getPort()); + if (shard.getPassword() != null) { + c.auth(shard.getPassword()); + } + return c; + } +} \ No newline at end of file diff --git a/src/main/java/redis/clients/util/ShardInfo.java b/src/main/java/redis/clients/util/ShardInfo.java new file mode 100644 index 0000000..3a14ea1 --- /dev/null +++ b/src/main/java/redis/clients/util/ShardInfo.java @@ -0,0 +1,98 @@ +package redis.clients.util; + +import redis.clients.jedis.Protocol; + +public class ShardInfo { + @Override + public String toString() { + return "ShardInfo [host=" + host + ", port=" + port + ", weight=" + + weight + "]"; + } + + private String host; + private int port; + private int timeout; + private int weight; + private String password = null; + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public int getTimeout() { + return timeout; + } + + public ShardInfo(String host) { + this(host, Protocol.DEFAULT_PORT); + } + + public ShardInfo(String host, int port) { + this(host, port, 2000); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((host == null) ? 0 : host.hashCode()); + result = prime * result + port; + result = prime * result + timeout; + result = prime * result + weight; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ShardInfo other = (ShardInfo) obj; + if (host == null) { + if (other.host != null) + return false; + } else if (!host.equals(other.host)) + return false; + if (port != other.port) + return false; + if (timeout != other.timeout) + return false; + if (weight != other.weight) + return false; + return true; + } + + public ShardInfo(String host, int port, int timeout) { + this(host, port, timeout, Sharded.DEFAULT_WEIGHT); + } + + public ShardInfo(String host, int port, int timeout, int weight) { + this.host = host; + this.port = port; + this.timeout = timeout; + this.weight = weight; + } + + public String getPassword() { + return password; + } + + public void setPassword(String auth) { + this.password = auth; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public int getWeight() { + return this.weight; + } +} diff --git a/src/main/java/redis/clients/util/Sharded.java b/src/main/java/redis/clients/util/Sharded.java new file mode 100644 index 0000000..e626f48 --- /dev/null +++ b/src/main/java/redis/clients/util/Sharded.java @@ -0,0 +1,102 @@ +package redis.clients.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public abstract class Sharded { + public static final int DEFAULT_WEIGHT = 1; + private static MessageDigest md5 = null; // avoid recurring construction + private TreeMap nodes; + private int totalWeight; + private Map resources; + + public Sharded(List shards) { + initialize(shards); + } + + private void initialize(List shards) { + nodes = new TreeMap(); + resources = new HashMap(); + + totalWeight = 0; + + for (ShardInfo shard : shards) { + totalWeight += shard.getWeight(); + } + + MessageDigest md5; + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("++++ no md5 algorythm found"); + } + + for (ShardInfo shard : shards) { + double factor = Math + .floor(((double) (40 * shards.size() * DEFAULT_WEIGHT)) + / (double) totalWeight); + + for (long j = 0; j < factor; j++) { + byte[] d = md5.digest((shard.toString() + "-" + j).getBytes()); + for (int h = 0; h < 4; h++) { + Long k = ((long) (d[3 + h * 4] & 0xFF) << 24) + | ((long) (d[2 + h * 4] & 0xFF) << 16) + | ((long) (d[1 + h * 4] & 0xFF) << 8) + | ((long) (d[0 + h * 4] & 0xFF)); + nodes.put(k, shard); + } + } + resources.put(shard, create(shard)); + } + } + + public ShardInfo getShardInfo(String key) { + long hv = calculateHash(key); + + return nodes.get(findPointFor(hv)); + } + + private Long calculateHash(String key) { + if (md5 == null) { + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("++++ no md5 algorythm found"); + } + } + + md5.reset(); + md5.update(key.getBytes()); + byte[] bKey = md5.digest(); + long res = ((long) (bKey[3] & 0xFF) << 24) + | ((long) (bKey[2] & 0xFF) << 16) + | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF); + return res; + } + + private Long findPointFor(Long hashK) { + Long k = nodes.ceilingKey(hashK); + + if (k == null) { + k = nodes.firstKey(); + } + + return k; + } + + public T getShard(String key) { + ShardInfo shard = getShardInfo(key); + return resources.get(shard); + } + + protected abstract T create(ShardInfo shard); + + public Collection getAllShards() { + return resources.values(); + } +} \ No newline at end of file diff --git a/src/test/java/redis/clients/jedis/tests/JedisTest.java b/src/test/java/redis/clients/jedis/tests/JedisTest.java index 2af5e42..1888cd2 100644 --- a/src/test/java/redis/clients/jedis/tests/JedisTest.java +++ b/src/test/java/redis/clients/jedis/tests/JedisTest.java @@ -13,6 +13,7 @@ public class JedisTest extends JedisCommandTestBase { @Test public void useWithoutConnecting() { Jedis jedis = new Jedis("localhost"); + jedis.auth("foobared"); jedis.dbSize(); } diff --git a/src/test/java/redis/clients/jedis/tests/ShardedJedisTest.java b/src/test/java/redis/clients/jedis/tests/ShardedJedisTest.java new file mode 100644 index 0000000..c8f861c --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/ShardedJedisTest.java @@ -0,0 +1,53 @@ +package redis.clients.jedis.tests; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Protocol; +import redis.clients.jedis.ShardedJedis; +import redis.clients.util.ShardInfo; + +public class ShardedJedisTest extends Assert { + @Test + public void checkSharding() throws IOException { + List shards = new ArrayList(); + shards.add(new ShardInfo("localhost", Protocol.DEFAULT_PORT)); + shards.add(new ShardInfo("localhost", Protocol.DEFAULT_PORT + 1)); + ShardedJedis jedis = new ShardedJedis(shards); + ShardInfo s1 = jedis.getShardInfo("a"); + ShardInfo s2 = jedis.getShardInfo("b"); + assertNotSame(s1, s2); + } + + @Test + public void trySharding() throws IOException { + List shards = new ArrayList(); + ShardInfo si = new ShardInfo("localhost", Protocol.DEFAULT_PORT); + si.setPassword("foobared"); + shards.add(si); + si = new ShardInfo("localhost", Protocol.DEFAULT_PORT + 1); + si.setPassword("foobared"); + shards.add(si); + ShardedJedis jedis = new ShardedJedis(shards); + jedis.set("a", "bar"); + ShardInfo s1 = jedis.getShardInfo("a"); + jedis.set("b", "bar1"); + ShardInfo s2 = jedis.getShardInfo("b"); + jedis.disconnect(); + + Jedis j = new Jedis(s1.getHost(), s1.getPort()); + j.auth("foobared"); + assertEquals("bar", j.get("a")); + j.disconnect(); + + j = new Jedis(s2.getHost(), s2.getPort()); + j.auth("foobared"); + assertEquals("bar1", j.get("b")); + j.disconnect(); + } +} \ No newline at end of file From 411b04a135e6d23a3bf3fdab95cb049ac2d0d7ae Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 01:34:04 -0300 Subject: [PATCH 02/11] version bump --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9d50773..cfa7ade 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 redis.clients jedis - 1.0.0-RC5 + 1.0.0 junit From 9b202c3fb1e6030812bb35b5ee2f93d8379c7aef Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 01:34:21 -0300 Subject: [PATCH 03/11] Updated readme --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9ca50ab..28aeff6 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,12 @@ Jedis was conceived to be EASY to use. Jedis is fully compatible with redis 2.0.0. -## Why Jedis is a Release Candidate? -Because I want to add Sharding and add more documentation to the site. And also publish the benchmark results, which are pretty good (around 26 Kops for GETs and SETs, and 126 Kops for GETs and SETs in pipeling mode). - ## What will be available soon? -- Sharding +- Sharding with connection pooling and with pipelining +- More examples and documentation - More and more code and performance improvements -But stay close because things are going fast and all this will be implemented soon! +Stay close because things are going fast! ## Ok.. so what can I do with Jedis? All of the following redis features are supported: @@ -32,6 +30,7 @@ All of the following redis features are supported: - Persistence control commands - Remote server control commands - Connection pooling +- Sharding (using ketama) ## How do I use it? @@ -41,7 +40,6 @@ You can download the latests build at: To use it just: Jedis jedis = new Jedis("localhost"); - jedis.connect(); jedis.set("foo", "bar"); String value = jedis.get("foo"); From 032fe7e134f8493040069fb257a70a17253ae4ac Mon Sep 17 00:00:00 2001 From: Alex Tkachman Date: Tue, 14 Sep 2010 11:50:49 +0200 Subject: [PATCH 04/11] ability to provide logger instead of stdout --- .../redis/clients/util/FixedResourcePool.java | 666 +++++++++--------- 1 file changed, 342 insertions(+), 324 deletions(-) diff --git a/src/main/java/redis/clients/util/FixedResourcePool.java b/src/main/java/redis/clients/util/FixedResourcePool.java index b902fbd..703dcbc 100644 --- a/src/main/java/redis/clients/util/FixedResourcePool.java +++ b/src/main/java/redis/clients/util/FixedResourcePool.java @@ -9,16 +9,15 @@ import java.util.concurrent.TimeoutException; /** * Abstract resource pool of type T. - * + *

* Needs implementation for creation, validation and destruction of the * resources. - * + *

* Keeps a fixed amount of resources - * + * * @author Luis Dario Simonassi - * * @param - * The type of the resource to be managed. + * The type of the resource to be managed. */ public abstract class FixedResourcePool { @@ -31,130 +30,140 @@ public abstract class FixedResourcePool { * Generic Resource Wrapper */ private static class Wrapper { - long timestamp; - T wrapped; + long timestamp; + T wrapped; - public Wrapper(T wrapped) { - this.wrapped = wrapped; - mark(); - } + public Wrapper(T wrapped) { + this.wrapped = wrapped; + mark(); + } - public void mark() { - timestamp = System.currentTimeMillis(); - } + public void mark() { + timestamp = System.currentTimeMillis(); + } - public long getLastMark() { - return timestamp; - } + public long getLastMark() { + return timestamp; + } + } + + public abstract static class Printer { + public abstract void print(String str); + } + + public static class DefaultPrinter extends Printer { + public void print(String str) { + System.out.println(str); + } } /** * Generic Repair Thread */ protected class RepairThread extends Thread { - public void run() { + public void run() { - // Contribute to the repairing and validation effort until the pool - // is destroyed (finishig=true) - while (!finishing) { - Wrapper wrapper; - try { - // Remove the oldest element from the repair queue. - wrapper = repairQueue.poll(timeBetweenValidation, - TimeUnit.MILLISECONDS); - if (wrapper == null) { - // If I've been waiting too much, i'll check the idle - // pool if connections need - // validation and move them to the repair queue - checkIdles(); - continue; - } - } catch (InterruptedException e) { - continue; - } + // Contribute to the repairing and validation effort until the pool + // is destroyed (finishig=true) + while (!finishing) { + Wrapper wrapper; + try { + // Remove the oldest element from the repair queue. + wrapper = repairQueue.poll(timeBetweenValidation, + TimeUnit.MILLISECONDS); + if (wrapper == null) { + // If I've been waiting too much, i'll check the idle + // pool if connections need + // validation and move them to the repair queue + checkIdles(); + continue; + } + } catch (InterruptedException e) { + continue; + } - // Now, I have something to repair! - T resource = wrapper.wrapped; - boolean valid = false; + // Now, I have something to repair! + T resource = wrapper.wrapped; + boolean valid = false; - // Resources are null right after initialization, it means the - // same as being an invalid resource - if (resource != null) { - valid = isResourceValid(resource); // Validate the resource. - if (!valid) - fails++; - } + // Resources are null right after initialization, it means the + // same as being an invalid resource + if (resource != null) { + valid = isResourceValid(resource); // Validate the resource. + if (!valid) + fails++; + } - // If resource is invalid or null, create a new resource and - // destroy the invalid one. - if (!valid) { - T replace = createResource(); - resourcesCreated++; - wrapper.wrapped = replace; - if (resource != null) - destroyResource(resource); - } + // If resource is invalid or null, create a new resource and + // destroy the invalid one. + if (!valid) { + T replace = createResource(); + resourcesCreated++; + wrapper.wrapped = replace; + if (resource != null) + destroyResource(resource); + } - // Mark the resource as fresh! - wrapper.mark(); + // Mark the resource as fresh! + wrapper.mark(); - // Offer the resource to the available resources pool. - if (!availableQueue.offer(wrapper)) { - System.err - .println("This shouldn't happen, offering to available was rejected."); - } - } + // Offer the resource to the available resources pool. + if (!availableQueue.offer(wrapper)) { + System.err + .println("This shouldn't happen, offering to available was rejected."); + } + } - System.out.println("Ending thread [" - + Thread.currentThread().getName() + "]"); - } + println("Ending thread [" + + Thread.currentThread().getName() + "]"); + } - /** - * Check if resources in the idle queue needs to be repaired - */ - private void checkIdles() { - // Get a sample without removing it - Wrapper wrapper = availableQueue.peek(); + /** + * Check if resources in the idle queue needs to be repaired + */ + private void checkIdles() { + // Get a sample without removing it + Wrapper wrapper = availableQueue.peek(); - // If no available items, nothing to repair. - if (wrapper == null) - return; + // If no available items, nothing to repair. + if (wrapper == null) + return; - // Check if the sampled resource needs to be repaired - boolean repairNeeded = isValidationNeeded(wrapper); - if (!repairNeeded) - return; + // Check if the sampled resource needs to be repaired + boolean repairNeeded = isValidationNeeded(wrapper); + if (!repairNeeded) + return; - // Move available resources from the available queue to the repair - // queue until no repair is needed. - while (repairNeeded) { + // Move available resources from the available queue to the repair + // queue until no repair is needed. + while (repairNeeded) { - // Get the connection from the available queue and check again. - wrapper = availableQueue.poll(); + // Get the connection from the available queue and check again. + wrapper = availableQueue.poll(); - // No resources in the available queue, nothing to do - if (wrapper == null) { - repairNeeded = false; - return; - } + // No resources in the available queue, nothing to do + if (wrapper == null) { + repairNeeded = false; + return; + } - // Add the resource to the corresponding queue, depending on - // weather the resource needs to be repaired or not. - repairNeeded = isValidationNeeded(wrapper); + // Add the resource to the corresponding queue, depending on + // weather the resource needs to be repaired or not. + repairNeeded = isValidationNeeded(wrapper); - if (repairNeeded) { - if (!repairQueue.offer(wrapper)) { - System.err - .print("FATAL: This shouldn't happen, offering to repairing was rejected."); - } - } else { - if (!availableQueue.offer(wrapper)) { - System.err - .print("FATAL: This shouldn't happen, offering to available was rejected."); - } - } - } - } + if (repairNeeded) { + if (!repairQueue.offer(wrapper)) { + System.err + .print("FATAL: This shouldn't happen, offering to repairing was rejected."); + } + } else { + if (!availableQueue.offer(wrapper)) { + System.err + .print("FATAL: This shouldn't happen, offering to available was rejected."); + } + } + } + } } /* @@ -166,28 +175,30 @@ public abstract class FixedResourcePool { private volatile long resourcesProvided = 0; private volatile long resourcesReturned = 0; + private Printer printer = new DefaultPrinter(); + /* * Pool metrics accessing methods. */ public long getFailsReported() { - return failsReported; + return failsReported; } public long getFails() { - return fails; + return fails; } public long getResourcesCreated() { - return resourcesCreated; + return resourcesCreated; } public long getResourcesProvided() { - return resourcesProvided; + return resourcesProvided; } public long getResourcesReturned() { - return resourcesReturned; + return resourcesReturned; } /* @@ -215,127 +226,138 @@ public abstract class FixedResourcePool { */ public int getResourcesNumber() { - return resourcesNumber; + return resourcesNumber; } public void setResourcesNumber(int resourcesNumber) { - this.resourcesNumber = resourcesNumber; + this.resourcesNumber = resourcesNumber; } public int getRepairThreadsNumber() { - return repairThreadsNumber; + return repairThreadsNumber; } public void setRepairThreadsNumber(int repairThreadsNumber) { - if (initializated) - throw new IllegalStateException( - "Repair threads should be setted up before init()"); - this.repairThreadsNumber = repairThreadsNumber; + if (initializated) + throw new IllegalStateException( + "Repair threads should be setted up before init()"); + this.repairThreadsNumber = repairThreadsNumber; } public long getTimeBetweenValidation() { - return timeBetweenValidation; + return timeBetweenValidation; } public void setTimeBetweenValidation(long timeBetweenValidation) { - this.timeBetweenValidation = timeBetweenValidation; + this.timeBetweenValidation = timeBetweenValidation; } public void setName(String name) { - if (initializated) - throw new IllegalStateException( - "Name should be setted up before init()"); - this.name = name; + if (initializated) + throw new IllegalStateException( + "Name should be setted up before init()"); + this.name = name; } public String getName() { - if (name == null || name.isEmpty()) { - name = this.getClass().getName(); - } - return name; + if (name == null || name.isEmpty()) { + name = this.getClass().getName(); + } + return name; } public void setDefaultPoolWait(long defaultPoolWait) { - this.defaultPoolWait = defaultPoolWait; + this.defaultPoolWait = defaultPoolWait; } public long getDefaultPoolWait() { - return defaultPoolWait; + return defaultPoolWait; + } + + public void setPrinter(Printer printer) { + this.printer = printer; + } + + private void println(String str) { + if(printer != null) + printer.print(str); } /** * Pool initialization & destruction */ public void destroy() { - checkInit(); + checkInit(); - System.out.println("Destroying [" + getName() + "]..."); + println("Destroying [" + getName() + "]..."); - // Signal al threads to end - finishing = true; + // Signal al threads to end + finishing = true; - System.out.println("Destroying [" + getName() + "] threads"); - // Wait for the Repair Threas - for (int i = 0; i < repairThreads.length; i++) { - boolean joined = false; - do { - try { - repairThreads[i].interrupt(); - repairThreads[i].join(); - joined = true; - } catch (InterruptedException e) { - e.printStackTrace(); - } - } while (!joined); - } + println("Destroying [" + getName() + "] threads"); + // Wait for the Repair Threas + for (int i = 0; i < repairThreads.length; i++) { + boolean joined = false; + do { + try { + repairThreads[i].interrupt(); + repairThreads[i].join(); + joined = true; + } catch (InterruptedException e) { + e.printStackTrace(); + } + } while (!joined); + } - System.out.println("Waiting for [" + getName() - + "] resources to be returned."); - // Wait for all resources to be returned to the pool - synchronized (this) { - while (!inUse.isEmpty()) { - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } + println("Waiting for [" + getName() + + "] resources to be returned."); + // Wait for all resources to be returned to the pool + synchronized (this) { + while (!inUse.isEmpty()) { + try { + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } - System.out.println("Destroying [" + getName() + "] resources."); - // Destroy resources - for (Wrapper resource : availableQueue) { - destroyResource(resource.wrapped); - } + printStatistics(); - availableQueue.clear(); - availableQueue = null; + println("Destroying [" + getName() + "] resources."); + // Destroy resources + for (Wrapper resource : availableQueue) { + destroyResource(resource.wrapped); + } - for (Wrapper resource : repairQueue) { - destroyResource(resource.wrapped); - } + availableQueue.clear(); + availableQueue = null; - repairQueue.clear(); - repairQueue = null; + for (Wrapper resource : repairQueue) { + destroyResource(resource.wrapped); + } - // Destroy metrics timer - System.out.println("Shuting metrics timer for [" + getName() - + "] down."); - t.cancel(); - t = null; + repairQueue.clear(); + repairQueue = null; - // Reset metrics - failsReported = 0; - fails = 0; - resourcesCreated = 0; - resourcesProvided = 0; - resourcesReturned = 0; + // Destroy metrics timer + println("Shuting metrics timer for [" + getName() + + "] down."); + t.cancel(); + t = null; - // Set states to initial values - initializated = false; - finishing = false; + // Reset metrics + failsReported = 0; + fails = 0; + resourcesCreated = 0; + resourcesProvided = 0; + resourcesReturned = 0; - System.out.println("Pool [" + getName() + "] successfully destroyed."); + // Set states to initial values + initializated = false; + finishing = false; + + println("Pool [" + getName() + "] successfully destroyed."); } /** @@ -343,203 +365,199 @@ public abstract class FixedResourcePool { */ @SuppressWarnings("unchecked") public void init() { - if (initializated == true) { - System.err.println("Warning, double initialization of [" + this - + "]"); - return; - } + if (initializated == true) { + println("Warning, double initialization of [" + this + + "]"); + return; + } - initializated = true; + initializated = true; - // Create queues with maximum possible capacity - availableQueue = new LinkedBlockingQueue>(resourcesNumber); - repairQueue = new LinkedBlockingQueue>(resourcesNumber); + // Create queues with maximum possible capacity + availableQueue = new LinkedBlockingQueue>(resourcesNumber); + repairQueue = new LinkedBlockingQueue>(resourcesNumber); - // Create and start the repair threads. - repairThreads = new FixedResourcePool.RepairThread[repairThreadsNumber]; - for (int i = 0; i < repairThreads.length; i++) { - repairThreads[i] = new RepairThread(); - repairThreads[i].setName("REPAIR[" + i + "]:" + getName()); - repairThreads[i].start(); - } + // Create and start the repair threads. + repairThreads = new FixedResourcePool.RepairThread[repairThreadsNumber]; + for (int i = 0; i < repairThreads.length; i++) { + repairThreads[i] = new RepairThread(); + repairThreads[i].setName("REPAIR[" + i + "]:" + getName()); + repairThreads[i].start(); + } - // Create resource wrappers with null content. - for (int i = 0; i < resourcesNumber; i++) { - if (!repairQueue.offer(new Wrapper(null))) - throw new IllegalStateException( - "What!? not enough space in the repairQueue to offer the element. This shouldn't happen!"); - } + // Create resource wrappers with null content. + for (int i = 0; i < resourcesNumber; i++) { + if (!repairQueue.offer(new Wrapper(null))) + throw new IllegalStateException( + "What!? not enough space in the repairQueue to offer the element. This shouldn't happen!"); + } - // Schedule a status report every 10 seconds. - t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - System.out.println("**********************************"); - System.out.println("* Pool name:[" + name + "]"); - System.out.println("* resourcesCreated....:" - + getResourcesCreated()); - System.out.println("* failsReported.......:" - + getFailsReported()); - System.out.println("* fails...............:" + getFails()); - System.out.println("* resourcesCreated....:" - + getResourcesCreated()); - System.out.println("* resourcesProvided...:" - + getResourcesProvided()); - System.out.println("* resourcesReturned...:" - + getResourcesReturned()); - System.out.println("* available size......:" - + availableQueue.size()); - System.out.println("* repair size.........:" - + repairQueue.size()); - System.out.println("**********************************"); - } - }, 10000, 10000); + // Schedule a status report every 10 seconds. + t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + printStatistics(); + } + }, 10000, 10000); - System.out.println("Initialized [" + name + "]"); + println("Initialized [" + name + "]"); + } + + private void printStatistics() { + println("**********************************" + + "\n* Pool name:[" + name + "]" + + "\n* resourcesCreated....:" + getResourcesCreated() + + "\n* failsReported.......:" + getFailsReported() + + "\n* fails...............:" + getFails() + + "\n* resourcesCreated....:" + getResourcesCreated() + + "\n* resourcesProvided...:" + getResourcesProvided() + + "\n* resourcesReturned...:" + getResourcesReturned() + + "\n* available size......:" + availableQueue.size() + + "\n* repair size.........:" + repairQueue.size() + + "\n**********************************"); } protected void checkInit() { - if (!initializated) - throw new IllegalStateException("Call the init() method first!"); + if (!initializated) + throw new IllegalStateException("Call the init() method first!"); } /** * Returns true if wrapped resource needs validation - * + * * @param wrapper * @return */ private boolean isValidationNeeded(Wrapper wrapper) { - // Add noise to the check times to avoid simultaneous resource checking. - long noisyTimeBetweenCheck = (timeBetweenValidation - (long) ((Math - .random() - 0.5) * (timeBetweenValidation / 10))); + // Add noise to the check times to avoid simultaneous resource checking. + long noisyTimeBetweenCheck = (timeBetweenValidation - (long) ((Math + .random() - 0.5) * (timeBetweenValidation / 10))); - // Check if the resource need to be checked. - return wrapper.getLastMark() + noisyTimeBetweenCheck < System - .currentTimeMillis(); + // Check if the resource need to be checked. + return wrapper.getLastMark() + noisyTimeBetweenCheck < System + .currentTimeMillis(); } /** * Return a resource to the pool. When no longer needed. - * + * * @param resource */ public void returnResource(T resource) { - checkInit(); + checkInit(); - Wrapper wrapper; + Wrapper wrapper; - if (resource == null) - throw new IllegalArgumentException( - "The resource shouldn't be null."); + if (resource == null) + throw new IllegalArgumentException( + "The resource shouldn't be null."); - // Delete the resource from the inUse list. - synchronized (inUse) { - wrapper = inUse.remove(resource); - } + // Delete the resource from the inUse list. + synchronized (inUse) { + wrapper = inUse.remove(resource); + } - if (wrapper == null) - throw new IllegalArgumentException("The resource [" + resource - + "] isn't in the busy resources list."); + if (wrapper == null) + throw new IllegalArgumentException("The resource [" + resource + + "] isn't in the busy resources list."); - if (isValidationNeeded(wrapper)) { - if (!repairQueue.offer(wrapper)) - throw new IllegalStateException( - "This shouldn't happen. Offering to repair queue rejected."); - } else { - if (!availableQueue.offer(wrapper)) - throw new IllegalStateException( - "This shouldn't happen. Offering to available queue rejected."); - } - resourcesReturned++; + if (isValidationNeeded(wrapper)) { + if (!repairQueue.offer(wrapper)) + throw new IllegalStateException( + "This shouldn't happen. Offering to repair queue rejected."); + } else { + if (!availableQueue.offer(wrapper)) + throw new IllegalStateException( + "This shouldn't happen. Offering to available queue rejected."); + } + resourcesReturned++; - if (finishing) { - synchronized (this) { - this.notify(); - } - } + if (finishing) { + synchronized (this) { + this.notify(); + } + } } /** * Return a broken resource to the pool. If the application detects a * malfunction of the resource. This resources will go directly to the * repair queue. - * + * * @param resource */ public void returnBrokenResource(T resource) { - checkInit(); - Wrapper wrapper; + checkInit(); + Wrapper wrapper; - // Delete the resource from the inUse list. - synchronized (inUse) { - wrapper = inUse.remove(resource); - } + // Delete the resource from the inUse list. + synchronized (inUse) { + wrapper = inUse.remove(resource); + } - if (wrapper == null) - throw new IllegalArgumentException("The resource [" + resource - + "] isn't in the busy resources list."); + if (wrapper == null) + throw new IllegalArgumentException("The resource [" + resource + + "] isn't in the busy resources list."); - if (!repairQueue.offer(wrapper)) - throw new IllegalStateException( - "This shouldn't happen. Offering to repair queue rejected."); - resourcesReturned++; + if (!repairQueue.offer(wrapper)) + throw new IllegalStateException( + "This shouldn't happen. Offering to repair queue rejected."); + resourcesReturned++; - if (finishing) { - synchronized (this) { - this.notify(); - } - } + if (finishing) { + synchronized (this) { + this.notify(); + } + } } /** * Get a resource from the pool waiting the default time. * {@link #setDefaultPoolWait(long)} - * + * * @return the resource of type T * @throws TimeoutException */ public T getResource() throws TimeoutException { - return getResource(defaultPoolWait); + return getResource(defaultPoolWait); } /** * Get a resource from the pool. - * - * @param maxTime - * Max time you would like to wait for the resource + * + * @param maxTime Max time you would like to wait for the resource * @return * @throws TimeoutException */ public T getResource(long maxTime) throws TimeoutException { - if (finishing) - throw new IllegalStateException("Pool [" + getName() - + "] is currently being destroyed."); - checkInit(); + if (finishing) + throw new IllegalStateException("Pool [" + getName() + + "] is currently being destroyed."); + checkInit(); - final long tInit = System.currentTimeMillis(); - do { - try { - long timeSpent = System.currentTimeMillis() - tInit; - long timeToSleep = maxTime - timeSpent; - timeToSleep = timeToSleep > 0 ? timeToSleep : 0; - if (timeToSleep == 0) - throw new TimeoutException("" + timeSpent + ">" + maxTime); - Wrapper ret = availableQueue.poll(timeToSleep, - TimeUnit.MILLISECONDS); - if (ret != null) { - synchronized (inUse) { - inUse.put(ret.wrapped, ret); - } - resourcesProvided++; - return ret.wrapped; - } - } catch (InterruptedException e1) { - e1.printStackTrace(); - } // If the wait gets interrupted, doesn't matter but print it (just - // in case). - } while (true); + final long tInit = System.currentTimeMillis(); + do { + try { + long timeSpent = System.currentTimeMillis() - tInit; + long timeToSleep = maxTime - timeSpent; + timeToSleep = timeToSleep > 0 ? timeToSleep : 0; + if (timeToSleep == 0) + throw new TimeoutException("" + timeSpent + ">" + maxTime); + Wrapper ret = availableQueue.poll(timeToSleep, + TimeUnit.MILLISECONDS); + if (ret != null) { + synchronized (inUse) { + inUse.put(ret.wrapped, ret); + } + resourcesProvided++; + return ret.wrapped; + } + } catch (InterruptedException e1) { + e1.printStackTrace(); + } // If the wait gets interrupted, doesn't matter but print it (just + // in case). + } while (true); } /* @@ -553,7 +571,7 @@ public abstract class FixedResourcePool { /** * Check if the resource is still valid. - * + * * @param resource * @return */ @@ -561,14 +579,14 @@ public abstract class FixedResourcePool { /** * Destroy a resource. - * + * * @param resource */ protected abstract void destroyResource(T resource); @Override public String toString() { - return getName() + "[" + super.toString() + "]"; + return getName() + "[" + super.toString() + "]"; } /** From 20dac7e9b41ffb08b94757266f3955ad003eb096 Mon Sep 17 00:00:00 2001 From: Alex Tkachman Date: Tue, 14 Sep 2010 11:59:25 +0200 Subject: [PATCH 05/11] more reasonable PoolBenchmark --- .../jedis/tests/benchmark/PoolBenchmark.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java b/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java index cc665b2..c87d51a 100644 --- a/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java +++ b/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java @@ -5,6 +5,7 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @@ -24,7 +25,7 @@ public class PoolBenchmark { // withoutPool(); withPool(); long elapsed = System.currentTimeMillis() - t; - System.out.println(((1000 * 3 * TOTAL_OPERATIONS) / elapsed) + " ops"); + System.out.println(((1000 * 2 * TOTAL_OPERATIONS) / elapsed) + " ops"); } private static void withoutPool() throws InterruptedException { @@ -60,30 +61,35 @@ public class PoolBenchmark { private static void withPool() throws InterruptedException { final JedisPool pool = new JedisPool("localhost"); - pool.setResourcesNumber(1000); - pool.setDefaultPoolWait(20); + pool.setResourcesNumber(50); + pool.setDefaultPoolWait(1000000); pool.init(); List tds = new ArrayList(); - for (int i = 0; i < TOTAL_OPERATIONS; i++) { - final String key = "foo" + i; + final AtomicInteger ind = new AtomicInteger(); + for (int i = 0; i < 50; i++) { Thread hj = new Thread(new Runnable() { - @Override public void run() { - try { - Jedis j = pool.getResource(); - j.auth("foobared"); - j.set(key, key); - j.get(key); - pool.returnResource(j); - } catch (Exception e) { - e.printStackTrace(); - } + for(int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS; ) { + try { + Jedis j = pool.getResource(); + final String key = "foo" + i; + j.set(key, key); + j.get(key); + pool.returnResource(j); + } catch (Exception e) { + e.printStackTrace(); + } + } } }); tds.add(hj); hj.start(); } + + for(Thread t : tds) + t.join(); + pool.destroy(); } } \ No newline at end of file From a8ffacd30ac55c502d1f006deb3e690e0135ba68 Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 12:08:56 -0300 Subject: [PATCH 06/11] Added bunch of missing commands and a test to check if Jedis is updated --- src/main/java/redis/clients/jedis/Client.java | 24 +++++++ src/main/java/redis/clients/jedis/Jedis.java | 29 ++++++++ .../tests/JedisNewCommandsCheckTest.java | 72 +++++++++++++++++++ .../commands/AllKindOfValuesCommandsTest.java | 16 +++++ .../tests/commands/ControlCommandsTest.java | 15 +++- .../tests/commands/ListCommandsTest.java | 19 +++++ .../commands/StringValuesCommandsTest.java | 6 ++ 7 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/test/java/redis/clients/jedis/tests/JedisNewCommandsCheckTest.java diff --git a/src/main/java/redis/clients/jedis/Client.java b/src/main/java/redis/clients/jedis/Client.java index d76164f..58a08f8 100644 --- a/src/main/java/redis/clients/jedis/Client.java +++ b/src/main/java/redis/clients/jedis/Client.java @@ -563,4 +563,28 @@ public class Client extends Connection { public void configSet(String parameter, String value) { sendCommand("CONFIG", "SET", parameter, value); } + + public void strlen(String key) { + sendCommand("STRLEN", key); + } + + public void sync() { + sendCommand("SYNC"); + } + + public void lpushx(String key, String string) { + sendCommand("LPUSHX", key, string); + } + + public void persist(String key) { + sendCommand("PERSIST", key); + } + + public void rpushx(String key, String string) { + sendCommand("RPUSHX", key, string); + } + + public void echo(String string) { + sendCommand("ECHO", string); + } } \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/Jedis.java b/src/main/java/redis/clients/jedis/Jedis.java index db7c84e..14af0ce 100644 --- a/src/main/java/redis/clients/jedis/Jedis.java +++ b/src/main/java/redis/clients/jedis/Jedis.java @@ -790,4 +790,33 @@ public class Jedis { public boolean isConnected() { return client.isConnected(); } + + public int strlen(String key) { + client.strlen(key); + return client.getIntegerReply(); + } + + public void sync() { + client.sync(); + } + + public int lpushx(String key, String string) { + client.lpushx(key, string); + return client.getIntegerReply(); + } + + public int persist(String key) { + client.persist(key); + return client.getIntegerReply(); + } + + public int rpushx(String key, String string) { + client.rpushx(key, string); + return client.getIntegerReply(); + } + + public String echo(String string) { + client.echo(string); + return client.getBulkReply(); + } } \ No newline at end of file diff --git a/src/test/java/redis/clients/jedis/tests/JedisNewCommandsCheckTest.java b/src/test/java/redis/clients/jedis/tests/JedisNewCommandsCheckTest.java new file mode 100644 index 0000000..509c5c2 --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/JedisNewCommandsCheckTest.java @@ -0,0 +1,72 @@ +package redis.clients.jedis.tests; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPubSub; +import redis.clients.jedis.Transaction; + +public class JedisNewCommandsCheckTest extends Assert { + @Test + @Ignore(value = "Ignored because still missing information for DEBUG and LINSERT commands") + public void checkJedisIsUpdated() throws IOException { + String[] commands = getAvailableCommands(); + Set implementedCommands = getImplementedCommands(); + + Set missingCommands = new HashSet(); + for (String command : commands) { + if (!implementedCommands.contains(command.trim())) { + missingCommands.add(command); + } + } + + if (!missingCommands.isEmpty()) { + fail("There are missing commands: " + missingCommands.toString()); + } + } + + private Set getImplementedCommands() { + Method[] methods = Jedis.class.getDeclaredMethods(); + Set implementedCommands = new HashSet(); + for (Method method : methods) { + implementedCommands.add(method.getName().trim().toLowerCase()); + } + + methods = JedisPubSub.class.getDeclaredMethods(); + for (Method method : methods) { + implementedCommands.add(method.getName().trim().toLowerCase()); + } + + methods = Transaction.class.getDeclaredMethods(); + for (Method method : methods) { + implementedCommands.add(method.getName().trim().toLowerCase()); + } + implementedCommands.add("config"); + return implementedCommands; + } + + private String[] getAvailableCommands() throws MalformedURLException, + IOException { + URL url = new URL("http://dimaion.com/redis/master"); + InputStream openStream = url.openStream(); + DataInputStream dis = new DataInputStream(new BufferedInputStream( + openStream)); + byte[] all = new byte[dis.available()]; + dis.readFully(all); + String commandList = new String(all); + String[] commands = commandList.split("\n"); + return commands; + } +} diff --git a/src/test/java/redis/clients/jedis/tests/commands/AllKindOfValuesCommandsTest.java b/src/test/java/redis/clients/jedis/tests/commands/AllKindOfValuesCommandsTest.java index 03e4de2..e0f3681 100644 --- a/src/test/java/redis/clients/jedis/tests/commands/AllKindOfValuesCommandsTest.java +++ b/src/test/java/redis/clients/jedis/tests/commands/AllKindOfValuesCommandsTest.java @@ -219,4 +219,20 @@ public class AllKindOfValuesCommandsTest extends JedisCommandTestBase { jedis.select(1); assertEquals(0, jedis.dbSize()); } + + @Test + public void persist() { + jedis.setex("foo", 60 * 60, "bar"); + assertTrue(jedis.ttl("foo") > 0); + int status = jedis.persist("foo"); + assertEquals(1, status); + assertEquals(-1, jedis.ttl("foo")); + } + + @Test + public void echo() { + String result = jedis.echo("hello world"); + assertEquals("hello world", result); + } + } \ No newline at end of file diff --git a/src/test/java/redis/clients/jedis/tests/commands/ControlCommandsTest.java b/src/test/java/redis/clients/jedis/tests/commands/ControlCommandsTest.java index 2dc4b5e..45f27f1 100644 --- a/src/test/java/redis/clients/jedis/tests/commands/ControlCommandsTest.java +++ b/src/test/java/redis/clients/jedis/tests/commands/ControlCommandsTest.java @@ -16,8 +16,13 @@ public class ControlCommandsTest extends JedisCommandTestBase { @Test public void bgsave() { - String status = jedis.bgsave(); - assertEquals("Background saving started", status); + try { + String status = jedis.bgsave(); + assertEquals("Background saving started", status); + } catch (JedisException e) { + assertEquals("ERR background save already in progress", e + .getMessage()); + } } @Test @@ -72,4 +77,10 @@ public class ControlCommandsTest extends JedisCommandTestBase { assertEquals("OK", status); jedis.configSet("maxmemory", memory); } + + @Test + public void sync() { + jedis.sync(); + } + } \ No newline at end of file diff --git a/src/test/java/redis/clients/jedis/tests/commands/ListCommandsTest.java b/src/test/java/redis/clients/jedis/tests/commands/ListCommandsTest.java index 44bf6f9..a49e936 100644 --- a/src/test/java/redis/clients/jedis/tests/commands/ListCommandsTest.java +++ b/src/test/java/redis/clients/jedis/tests/commands/ListCommandsTest.java @@ -249,6 +249,25 @@ public class ListCommandsTest extends JedisCommandTestBase { assertEquals(2, result.size()); assertEquals("foo", result.get(0)); assertEquals("bar", result.get(1)); + } + @Test + public void lpushx() { + int status = jedis.lpushx("foo", "bar"); + assertEquals(0, status); + + jedis.lpush("foo", "a"); + status = jedis.lpushx("foo", "b"); + assertEquals(2, status); + } + + @Test + public void rpushx() { + int status = jedis.rpushx("foo", "bar"); + assertEquals(0, status); + + jedis.lpush("foo", "a"); + status = jedis.rpushx("foo", "b"); + assertEquals(2, status); } } \ No newline at end of file diff --git a/src/test/java/redis/clients/jedis/tests/commands/StringValuesCommandsTest.java b/src/test/java/redis/clients/jedis/tests/commands/StringValuesCommandsTest.java index 0139ff4..1571193 100644 --- a/src/test/java/redis/clients/jedis/tests/commands/StringValuesCommandsTest.java +++ b/src/test/java/redis/clients/jedis/tests/commands/StringValuesCommandsTest.java @@ -169,4 +169,10 @@ public class StringValuesCommandsTest extends JedisCommandTestBase { assertEquals("This is a string", jedis.substr("s", 0, -1)); assertEquals(" string", jedis.substr("s", 9, 100000)); } + + @Test + public void strlen() { + jedis.set("s", "This is a string"); + assertEquals("This is a string".length(), jedis.strlen("s")); + } } \ No newline at end of file From 8629360954b8e9ab65a9ba32daf91eafd835d993 Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 14:08:49 -0300 Subject: [PATCH 07/11] Added MurmureHash as sharding algo. --- .../redis/clients/jedis/ShardedJedis.java | 5 ++ src/main/java/redis/clients/util/Hashing.java | 79 +++++++++++++++++++ src/main/java/redis/clients/util/Sharded.java | 22 ++---- .../clients/jedis/tests/ShardedJedisTest.java | 29 +++++++ 4 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 src/main/java/redis/clients/util/Hashing.java diff --git a/src/main/java/redis/clients/jedis/ShardedJedis.java b/src/main/java/redis/clients/jedis/ShardedJedis.java index d3b6266..8663a7b 100644 --- a/src/main/java/redis/clients/jedis/ShardedJedis.java +++ b/src/main/java/redis/clients/jedis/ShardedJedis.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import redis.clients.util.Hashing; import redis.clients.util.ShardInfo; import redis.clients.util.Sharded; @@ -13,6 +14,10 @@ public class ShardedJedis extends Sharded { super(shards); } + public ShardedJedis(List shards, Hashing algo) { + super(shards, algo); + } + public String set(String key, String value) { Jedis j = getShard(key); return j.set(key, value); diff --git a/src/main/java/redis/clients/util/Hashing.java b/src/main/java/redis/clients/util/Hashing.java new file mode 100644 index 0000000..b961992 --- /dev/null +++ b/src/main/java/redis/clients/util/Hashing.java @@ -0,0 +1,79 @@ +package redis.clients.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public abstract class Hashing { + public static final Hashing MURMURE_HASH = new Hashing() { + public long hash(String key) { + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + byte[] data = key.getBytes(); + int seed = 0x1234ABCD; + int m = 0x5bd1e995; + int r = 24; + + // Initialize the hash to a 'random' value + int len = data.length; + int h = seed ^ len; + + int i = 0; + while (len >= 4) { + int k = data[i + 0] & 0xFF; + k |= (data[i + 1] & 0xFF) << 8; + k |= (data[i + 2] & 0xFF) << 16; + k |= (data[i + 3] & 0xFF) << 24; + + k *= m; + k ^= k >>> r; + k *= m; + + h *= m; + h ^= k; + + i += 4; + len -= 4; + } + + switch (len) { + case 3: + h ^= (data[i + 2] & 0xFF) << 16; + case 2: + h ^= (data[i + 1] & 0xFF) << 8; + case 1: + h ^= (data[i + 0] & 0xFF); + h *= m; + } + + h ^= h >>> 13; + h *= m; + h ^= h >>> 15; + + return h; + } + }; + public static final Hashing MD5 = new Hashing() { + private MessageDigest md5 = null; // avoid recurring construction + + public long hash(String key) { + if (md5 == null) { + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException( + "++++ no md5 algorythm found"); + } + } + + md5.reset(); + md5.update(key.getBytes()); + byte[] bKey = md5.digest(); + long res = ((long) (bKey[3] & 0xFF) << 24) + | ((long) (bKey[2] & 0xFF) << 16) + | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF); + return res; + } + }; + + public abstract long hash(String key); +} \ No newline at end of file diff --git a/src/main/java/redis/clients/util/Sharded.java b/src/main/java/redis/clients/util/Sharded.java index e626f48..d075434 100644 --- a/src/main/java/redis/clients/util/Sharded.java +++ b/src/main/java/redis/clients/util/Sharded.java @@ -10,15 +10,19 @@ import java.util.TreeMap; public abstract class Sharded { public static final int DEFAULT_WEIGHT = 1; - private static MessageDigest md5 = null; // avoid recurring construction private TreeMap nodes; private int totalWeight; private Map resources; + private Hashing algo = Hashing.MD5; public Sharded(List shards) { initialize(shards); } + public Sharded(List shards, Hashing algo) { + initialize(shards); + } + private void initialize(List shards) { nodes = new TreeMap(); resources = new HashMap(); @@ -62,21 +66,7 @@ public abstract class Sharded { } private Long calculateHash(String key) { - if (md5 == null) { - try { - md5 = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("++++ no md5 algorythm found"); - } - } - - md5.reset(); - md5.update(key.getBytes()); - byte[] bKey = md5.digest(); - long res = ((long) (bKey[3] & 0xFF) << 24) - | ((long) (bKey[2] & 0xFF) << 16) - | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF); - return res; + return algo.hash(key); } private Long findPointFor(Long hashK) { diff --git a/src/test/java/redis/clients/jedis/tests/ShardedJedisTest.java b/src/test/java/redis/clients/jedis/tests/ShardedJedisTest.java index c8f861c..530f0e2 100644 --- a/src/test/java/redis/clients/jedis/tests/ShardedJedisTest.java +++ b/src/test/java/redis/clients/jedis/tests/ShardedJedisTest.java @@ -10,6 +10,7 @@ import org.junit.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.Protocol; import redis.clients.jedis.ShardedJedis; +import redis.clients.util.Hashing; import redis.clients.util.ShardInfo; public class ShardedJedisTest extends Assert { @@ -50,4 +51,32 @@ public class ShardedJedisTest extends Assert { assertEquals("bar1", j.get("b")); j.disconnect(); } + + @Test + public void tryShardingWithMurmure() throws IOException { + List shards = new ArrayList(); + ShardInfo si = new ShardInfo("localhost", Protocol.DEFAULT_PORT); + si.setPassword("foobared"); + shards.add(si); + si = new ShardInfo("localhost", Protocol.DEFAULT_PORT + 1); + si.setPassword("foobared"); + shards.add(si); + ShardedJedis jedis = new ShardedJedis(shards, Hashing.MURMURE_HASH); + jedis.set("a", "bar"); + ShardInfo s1 = jedis.getShardInfo("a"); + jedis.set("b", "bar1"); + ShardInfo s2 = jedis.getShardInfo("b"); + jedis.disconnect(); + + Jedis j = new Jedis(s1.getHost(), s1.getPort()); + j.auth("foobared"); + assertEquals("bar", j.get("a")); + j.disconnect(); + + j = new Jedis(s2.getHost(), s2.getPort()); + j.auth("foobared"); + assertEquals("bar1", j.get("b")); + j.disconnect(); + } + } \ No newline at end of file From f99c4ed6a2178175d3caf9e9bb5ce0330b93c4e2 Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 14:10:31 -0300 Subject: [PATCH 08/11] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28aeff6..3cb6135 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ All of the following redis features are supported: - Persistence control commands - Remote server control commands - Connection pooling -- Sharding (using ketama) +- Sharding (MD5, MurmureHash) ## How do I use it? From 708ae8a56e92c701b27afb8cfa4417a13bcdf9be Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 14:17:10 -0300 Subject: [PATCH 09/11] Fixed small bug in the benchmark test --- .../jedis/tests/benchmark/PoolBenchmark.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java b/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java index c87d51a..71d9482 100644 --- a/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java +++ b/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java @@ -25,7 +25,7 @@ public class PoolBenchmark { // withoutPool(); withPool(); long elapsed = System.currentTimeMillis() - t; - System.out.println(((1000 * 2 * TOTAL_OPERATIONS) / elapsed) + " ops"); + System.out.println(((1000 * 3 * TOTAL_OPERATIONS) / elapsed) + " ops"); } private static void withoutPool() throws InterruptedException { @@ -66,30 +66,31 @@ public class PoolBenchmark { pool.init(); List tds = new ArrayList(); - final AtomicInteger ind = new AtomicInteger(); + final AtomicInteger ind = new AtomicInteger(); for (int i = 0; i < 50; i++) { Thread hj = new Thread(new Runnable() { public void run() { - for(int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS; ) { - try { - Jedis j = pool.getResource(); - final String key = "foo" + i; - j.set(key, key); - j.get(key); - pool.returnResource(j); - } catch (Exception e) { - e.printStackTrace(); - } - } + for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) { + try { + Jedis j = pool.getResource(); + j.auth("foobared"); + final String key = "foo" + i; + j.set(key, key); + j.get(key); + pool.returnResource(j); + } catch (Exception e) { + e.printStackTrace(); + } + } } }); tds.add(hj); hj.start(); } - for(Thread t : tds) - t.join(); - + for (Thread t : tds) + t.join(); + pool.destroy(); } } \ No newline at end of file From 224555afd2c57ce49c62bfe8ef2bb44245f4aaa0 Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 16:43:48 -0300 Subject: [PATCH 10/11] Added password to Jedis, JedisPool and ShardedJedis constructor for easier and more efficient usage --- src/main/java/redis/clients/jedis/Jedis.java | 10 ++++++++ .../java/redis/clients/jedis/JedisPool.java | 19 +++++++++++++++ .../redis/clients/jedis/ShardedJedis.java | 6 +---- .../clients/jedis/tests/JedisPoolTest.java | 23 ++++++++----------- .../redis/clients/jedis/tests/JedisTest.java | 9 ++++++++ .../jedis/tests/benchmark/PoolBenchmark.java | 7 +++--- 6 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/main/java/redis/clients/jedis/Jedis.java b/src/main/java/redis/clients/jedis/Jedis.java index 14af0ce..d22c0f8 100644 --- a/src/main/java/redis/clients/jedis/Jedis.java +++ b/src/main/java/redis/clients/jedis/Jedis.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import redis.clients.util.ShardInfo; + public class Jedis { private Client client = null; @@ -26,6 +28,14 @@ public class Jedis { client.setTimeout(timeout); } + public Jedis(ShardInfo shardInfo) { + client = new Client(shardInfo.getHost(), shardInfo.getPort()); + client.setTimeout(shardInfo.getTimeout()); + if (shardInfo.getPassword() != null) { + this.auth(shardInfo.getPassword()); + } + } + public String ping() { checkIsInMulti(); client.ping(); diff --git a/src/main/java/redis/clients/jedis/JedisPool.java b/src/main/java/redis/clients/jedis/JedisPool.java index 46c0fe9..3d19482 100644 --- a/src/main/java/redis/clients/jedis/JedisPool.java +++ b/src/main/java/redis/clients/jedis/JedisPool.java @@ -1,11 +1,13 @@ package redis.clients.jedis; import redis.clients.util.FixedResourcePool; +import redis.clients.util.ShardInfo; public class JedisPool extends FixedResourcePool { private String host; private int port; private int timeout; + private String password; public JedisPool(String host) { this.host = host; @@ -23,6 +25,20 @@ public class JedisPool extends FixedResourcePool { this.timeout = timeout; } + public JedisPool(String host, int port, int timeout, String password) { + this.host = host; + this.port = port; + this.timeout = timeout; + this.password = password; + } + + public JedisPool(ShardInfo shardInfo) { + this.host = shardInfo.getHost(); + this.port = shardInfo.getPort(); + this.timeout = shardInfo.getTimeout(); + this.password = shardInfo.getPassword(); + } + @Override protected Jedis createResource() { Jedis jedis = new Jedis(this.host, this.port, this.timeout); @@ -30,6 +46,9 @@ public class JedisPool extends FixedResourcePool { while (!done) { try { jedis.connect(); + if (password != null) { + jedis.auth(password); + } done = true; } catch (Exception e) { try { diff --git a/src/main/java/redis/clients/jedis/ShardedJedis.java b/src/main/java/redis/clients/jedis/ShardedJedis.java index 8663a7b..a2aceae 100644 --- a/src/main/java/redis/clients/jedis/ShardedJedis.java +++ b/src/main/java/redis/clients/jedis/ShardedJedis.java @@ -352,10 +352,6 @@ public class ShardedJedis extends Sharded { } protected Jedis create(ShardInfo shard) { - Jedis c = new Jedis(shard.getHost(), shard.getPort()); - if (shard.getPassword() != null) { - c.auth(shard.getPassword()); - } - return c; + return new Jedis(shard); } } \ No newline at end of file diff --git a/src/test/java/redis/clients/jedis/tests/JedisPoolTest.java b/src/test/java/redis/clients/jedis/tests/JedisPoolTest.java index 4444ee6..89c15c9 100644 --- a/src/test/java/redis/clients/jedis/tests/JedisPoolTest.java +++ b/src/test/java/redis/clients/jedis/tests/JedisPoolTest.java @@ -13,12 +13,12 @@ import redis.clients.jedis.Protocol; public class JedisPoolTest extends Assert { @Test public void checkConnections() throws TimeoutException { - JedisPool pool = new JedisPool("localhost", Protocol.DEFAULT_PORT, 2000); + JedisPool pool = new JedisPool("localhost", Protocol.DEFAULT_PORT, + 2000, "foobared"); pool.setResourcesNumber(10); pool.init(); Jedis jedis = pool.getResource(200); - jedis.auth("foobared"); jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); pool.returnResource(jedis); @@ -27,12 +27,12 @@ public class JedisPoolTest extends Assert { @Test public void checkConnectionWithDefaultPort() throws TimeoutException { - JedisPool pool = new JedisPool("localhost"); + JedisPool pool = new JedisPool("localhost", Protocol.DEFAULT_PORT, + 2000, "foobared"); pool.setResourcesNumber(10); pool.init(); Jedis jedis = pool.getResource(200); - jedis.auth("foobared"); jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); pool.returnResource(jedis); @@ -41,17 +41,16 @@ public class JedisPoolTest extends Assert { @Test public void checkJedisIsReusedWhenReturned() throws TimeoutException { - JedisPool pool = new JedisPool("localhost"); + JedisPool pool = new JedisPool("localhost", Protocol.DEFAULT_PORT, + 2000, "foobared"); pool.setResourcesNumber(1); pool.init(); Jedis jedis = pool.getResource(200); - jedis.auth("foobared"); jedis.set("foo", "0"); pool.returnResource(jedis); jedis = pool.getResource(200); - jedis.auth("foobared"); jedis.incr("foo"); pool.returnResource(jedis); pool.destroy(); @@ -60,17 +59,16 @@ public class JedisPoolTest extends Assert { @Test public void checkPoolRepairedWhenJedisIsBroken() throws TimeoutException, IOException { - JedisPool pool = new JedisPool("localhost"); + JedisPool pool = new JedisPool("localhost", Protocol.DEFAULT_PORT, + 2000, "foobared"); pool.setResourcesNumber(1); pool.init(); Jedis jedis = pool.getResource(200); - jedis.auth("foobared"); jedis.quit(); pool.returnBrokenResource(jedis); jedis = pool.getResource(200); - jedis.auth("foobared"); jedis.incr("foo"); pool.returnResource(jedis); pool.destroy(); @@ -78,16 +76,15 @@ public class JedisPoolTest extends Assert { @Test(expected = TimeoutException.class) public void checkPoolOverflow() throws TimeoutException { - JedisPool pool = new JedisPool("localhost"); + JedisPool pool = new JedisPool("localhost", Protocol.DEFAULT_PORT, + 2000, "foobared"); pool.setResourcesNumber(1); pool.init(); Jedis jedis = pool.getResource(200); - jedis.auth("foobared"); jedis.set("foo", "0"); Jedis newJedis = pool.getResource(200); - newJedis.auth("foobared"); newJedis.incr("foo"); } } \ No newline at end of file diff --git a/src/test/java/redis/clients/jedis/tests/JedisTest.java b/src/test/java/redis/clients/jedis/tests/JedisTest.java index 1888cd2..64ff957 100644 --- a/src/test/java/redis/clients/jedis/tests/JedisTest.java +++ b/src/test/java/redis/clients/jedis/tests/JedisTest.java @@ -6,8 +6,10 @@ import java.util.Map; import org.junit.Test; import redis.clients.jedis.Jedis; +import redis.clients.jedis.Protocol; import redis.clients.jedis.tests.commands.JedisCommandTestBase; import redis.clients.util.RedisOutputStream; +import redis.clients.util.ShardInfo; public class JedisTest extends JedisCommandTestBase { @Test @@ -31,4 +33,11 @@ public class JedisTest extends JedisCommandTestBase { assertEquals(hash, jedis.hgetAll("foo")); } + @Test + public void connectWithShardInfo() { + ShardInfo shardInfo = new ShardInfo("localhost", Protocol.DEFAULT_PORT); + shardInfo.setPassword("foobared"); + Jedis jedis = new Jedis(shardInfo); + jedis.get("foo"); + } } diff --git a/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java b/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java index 71d9482..6ebf416 100644 --- a/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java +++ b/src/test/java/redis/clients/jedis/tests/benchmark/PoolBenchmark.java @@ -9,6 +9,7 @@ import java.util.concurrent.atomic.AtomicInteger; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; +import redis.clients.jedis.Protocol; public class PoolBenchmark { private static final int TOTAL_OPERATIONS = 100000; @@ -25,7 +26,7 @@ public class PoolBenchmark { // withoutPool(); withPool(); long elapsed = System.currentTimeMillis() - t; - System.out.println(((1000 * 3 * TOTAL_OPERATIONS) / elapsed) + " ops"); + System.out.println(((1000 * 2 * TOTAL_OPERATIONS) / elapsed) + " ops"); } private static void withoutPool() throws InterruptedException { @@ -60,7 +61,8 @@ public class PoolBenchmark { } private static void withPool() throws InterruptedException { - final JedisPool pool = new JedisPool("localhost"); + final JedisPool pool = new JedisPool("localhost", + Protocol.DEFAULT_PORT, 2000, "foobared"); pool.setResourcesNumber(50); pool.setDefaultPoolWait(1000000); pool.init(); @@ -73,7 +75,6 @@ public class PoolBenchmark { for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) { try { Jedis j = pool.getResource(); - j.auth("foobared"); final String key = "foo" + i; j.set(key, key); j.get(key); From f45488077617efb18770d19ac9ce2d0ae6451ba7 Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Sep 2010 16:46:36 -0300 Subject: [PATCH 11/11] Added redis conf files used in tests --- conf/redis.conf | 332 +++++++++++++++++++++++++++++++++++++++++++++++ conf/redis2.conf | 332 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 664 insertions(+) create mode 100644 conf/redis.conf create mode 100644 conf/redis2.conf diff --git a/conf/redis.conf b/conf/redis.conf new file mode 100644 index 0000000..eceedb6 --- /dev/null +++ b/conf/redis.conf @@ -0,0 +1,332 @@ +# Redis configuration file example + +# Note on units: when memory size is needed, it is possible to specifiy +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +daemonize no + +# When running daemonized, Redis writes a pid file in /var/run/redis.pid by +# default. You can specify a custom pid file location here. +pidfile /var/run/redis.pid + +# Accept connections on the specified port, default is 6379 +port 6379 + +# If you want you can bind a single interface, if the bind option is not +# specified all the interfaces will listen for incoming connections. +# +# bind 127.0.0.1 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 300 + +# Set server verbosity to 'debug' +# it can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel verbose + +# Specify the log file name. Also 'stdout' can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile stdout + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +################################ SNAPSHOTTING ################################# +# +# Save the DB on disk: +# +# save +# +# Will save the DB if both the given number of seconds and the given +# number of write operations against the DB occurred. +# +# In the example below the behaviour will be to save: +# after 900 sec (15 min) if at least 1 key changed +# after 300 sec (5 min) if at least 10 keys changed +# after 60 sec if at least 10000 keys changed +# +# Note: you can disable saving at all commenting all the "save" lines. + +save 900 1 +save 300 10 +save 60 10000 + +# Compress string objects using LZF when dump .rdb databases? +# For default that's set to 'yes' as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# The filename where to dump the DB +dbfilename dump.rdb + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# Also the Append Only File will be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir ./ + +################################# REPLICATION ################################# + +# Master-Slave replication. Use slaveof to make a Redis instance a copy of +# another Redis server. Note that the configuration is local to the slave +# so for example it is possible to configure the slave to save the DB with a +# different interval, or to listen to another port, and so on. +# +# slaveof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the slave to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the slave request. +# +# masterauth + +################################## SECURITY ################################### + +# Require clients to issue AUTH before processing any other +# commands. This might be useful in environments in which you do not trust +# others with access to the host running redis-server. +# +# This should stay commented out for backward compatibility and because most +# people do not need auth (e.g. they run their own servers). +# +# Warning: since Redis is pretty fast an outside user can try up to +# 150k passwords per second against a good box. This means that you should +# use a very strong password otherwise it will be very easy to break. +# +requirepass foobared + +################################### LIMITS #################################### + +# Set the max number of connected clients at the same time. By default there +# is no limit, and it's up to the number of file descriptors the Redis process +# is able to open. The special value '0' means no limits. +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# maxclients 128 + +# Don't use more memory than the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys with an +# EXPIRE set. It will try to start freeing keys that are going to expire +# in little time and preserve keys with a longer time to live. +# Redis will also try to remove objects from free lists if possible. +# +# If all this fails, Redis will start to reply with errors to commands +# that will use more memory, like SET, LPUSH, and so on, and will continue +# to reply to most read-only commands like GET. +# +# WARNING: maxmemory can be a good idea mainly if you want to use Redis as a +# 'state' server or cache, not as a real DB. When Redis is used as a real +# database the memory usage will grow over the weeks, it will be obvious if +# it is going to use too much memory in the long run, and you'll have the time +# to upgrade. With maxmemory after the limit is reached you'll start to get +# errors for write operations, and this may even lead to DB inconsistency. +# +# maxmemory + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. If you can live +# with the idea that the latest records will be lost if something like a crash +# happens this is the preferred way to run Redis. If instead you care a lot +# about your data and don't want to that a single record can get lost you should +# enable the append only mode: when this mode is enabled Redis will append +# every write operation received in the file appendonly.aof. This file will +# be read on startup in order to rebuild the full dataset in memory. +# +# Note that you can have both the async dumps and the append only file if you +# like (you have to comment the "save" statements above to disable the dumps). +# Still if append only mode is enabled Redis will load the data from the +# log file at startup ignoring the dump.rdb file. +# +# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append +# log file in background when it gets too big. + +appendonly no + +# The name of the append only file (default: "appendonly.aof") +# appendfilename appendonly.aof + +# The fsync() call tells the Operating System to actually write data on disk +# instead to wait for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log . Slow, Safest. +# everysec: fsync only if one second passed since the last fsync. Compromise. +# +# The default is "everysec" that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving the durability of Redis is +# the same as "appendfsync none", that in pratical terms means that it is +# possible to lost up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. +no-appendfsync-on-rewrite no + +################################ VIRTUAL MEMORY ############################### + +# Virtual Memory allows Redis to work with datasets bigger than the actual +# amount of RAM needed to hold the whole dataset in memory. +# In order to do so very used keys are taken in memory while the other keys +# are swapped into a swap file, similarly to what operating systems do +# with memory pages. +# +# To enable VM just set 'vm-enabled' to yes, and set the following three +# VM parameters accordingly to your needs. + +vm-enabled no +# vm-enabled yes + +# This is the path of the Redis swap file. As you can guess, swap files +# can't be shared by different Redis instances, so make sure to use a swap +# file for every redis process you are running. Redis will complain if the +# swap file is already in use. +# +# The best kind of storage for the Redis swap file (that's accessed at random) +# is a Solid State Disk (SSD). +# +# *** WARNING *** if you are using a shared hosting the default of putting +# the swap file under /tmp is not secure. Create a dir with access granted +# only to Redis user and configure Redis to create the swap file there. +vm-swap-file /tmp/redis.swap + +# vm-max-memory configures the VM to use at max the specified amount of +# RAM. Everything that deos not fit will be swapped on disk *if* possible, that +# is, if there is still enough contiguous space in the swap file. +# +# With vm-max-memory 0 the system will swap everything it can. Not a good +# default, just specify the max amount of RAM you can in bytes, but it's +# better to leave some margin. For instance specify an amount of RAM +# that's more or less between 60 and 80% of your free RAM. +vm-max-memory 0 + +# Redis swap files is split into pages. An object can be saved using multiple +# contiguous pages, but pages can't be shared between different objects. +# So if your page is too big, small objects swapped out on disk will waste +# a lot of space. If you page is too small, there is less space in the swap +# file (assuming you configured the same number of total swap file pages). +# +# If you use a lot of small objects, use a page size of 64 or 32 bytes. +# If you use a lot of big objects, use a bigger page size. +# If unsure, use the default :) +vm-page-size 32 + +# Number of total memory pages in the swap file. +# Given that the page table (a bitmap of free/used pages) is taken in memory, +# every 8 pages on disk will consume 1 byte of RAM. +# +# The total swap size is vm-page-size * vm-pages +# +# With the default of 32-bytes memory pages and 134217728 pages Redis will +# use a 4 GB swap file, that will use 16 MB of RAM for the page table. +# +# It's better to use the smallest acceptable value for your application, +# but the default is large in order to work in most conditions. +vm-pages 134217728 + +# Max number of VM I/O threads running at the same time. +# This threads are used to read/write data from/to swap file, since they +# also encode and decode objects from disk to memory or the reverse, a bigger +# number of threads can help with big objects even if they can't help with +# I/O itself as the physical device may not be able to couple with many +# reads/writes operations at the same time. +# +# The special value of 0 turn off threaded I/O and enables the blocking +# Virtual Memory implementation. +vm-max-threads 4 + +############################### ADVANCED CONFIG ############################### + +# Glue small output buffers together in order to send small replies in a +# single TCP packet. Uses a bit more CPU but most of the times it is a win +# in terms of number of queries per second. Use 'yes' if unsure. +glueoutputbuf yes + +# Hashes are encoded in a special way (much more memory efficient) when they +# have at max a given numer of elements, and the biggest element does not +# exceed a given threshold. You can configure this limits with the following +# configuration directives. +hash-max-zipmap-entries 64 +hash-max-zipmap-value 512 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into an hash table +# that is rhashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# active rehashing the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply form time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all redis server but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# include /path/to/local.conf +# include /path/to/other.conf diff --git a/conf/redis2.conf b/conf/redis2.conf new file mode 100644 index 0000000..c59e989 --- /dev/null +++ b/conf/redis2.conf @@ -0,0 +1,332 @@ +# Redis configuration file example + +# Note on units: when memory size is needed, it is possible to specifiy +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +daemonize no + +# When running daemonized, Redis writes a pid file in /var/run/redis.pid by +# default. You can specify a custom pid file location here. +pidfile /var/run/redis.pid + +# Accept connections on the specified port, default is 6379 +port 6380 + +# If you want you can bind a single interface, if the bind option is not +# specified all the interfaces will listen for incoming connections. +# +# bind 127.0.0.1 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 300 + +# Set server verbosity to 'debug' +# it can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel verbose + +# Specify the log file name. Also 'stdout' can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile stdout + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +################################ SNAPSHOTTING ################################# +# +# Save the DB on disk: +# +# save +# +# Will save the DB if both the given number of seconds and the given +# number of write operations against the DB occurred. +# +# In the example below the behaviour will be to save: +# after 900 sec (15 min) if at least 1 key changed +# after 300 sec (5 min) if at least 10 keys changed +# after 60 sec if at least 10000 keys changed +# +# Note: you can disable saving at all commenting all the "save" lines. + +save 900 1 +save 300 10 +save 60 10000 + +# Compress string objects using LZF when dump .rdb databases? +# For default that's set to 'yes' as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# The filename where to dump the DB +dbfilename dump.rdb + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# Also the Append Only File will be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir ./ + +################################# REPLICATION ################################# + +# Master-Slave replication. Use slaveof to make a Redis instance a copy of +# another Redis server. Note that the configuration is local to the slave +# so for example it is possible to configure the slave to save the DB with a +# different interval, or to listen to another port, and so on. +# +# slaveof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the slave to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the slave request. +# +# masterauth + +################################## SECURITY ################################### + +# Require clients to issue AUTH before processing any other +# commands. This might be useful in environments in which you do not trust +# others with access to the host running redis-server. +# +# This should stay commented out for backward compatibility and because most +# people do not need auth (e.g. they run their own servers). +# +# Warning: since Redis is pretty fast an outside user can try up to +# 150k passwords per second against a good box. This means that you should +# use a very strong password otherwise it will be very easy to break. +# +requirepass foobared + +################################### LIMITS #################################### + +# Set the max number of connected clients at the same time. By default there +# is no limit, and it's up to the number of file descriptors the Redis process +# is able to open. The special value '0' means no limits. +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# maxclients 128 + +# Don't use more memory than the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys with an +# EXPIRE set. It will try to start freeing keys that are going to expire +# in little time and preserve keys with a longer time to live. +# Redis will also try to remove objects from free lists if possible. +# +# If all this fails, Redis will start to reply with errors to commands +# that will use more memory, like SET, LPUSH, and so on, and will continue +# to reply to most read-only commands like GET. +# +# WARNING: maxmemory can be a good idea mainly if you want to use Redis as a +# 'state' server or cache, not as a real DB. When Redis is used as a real +# database the memory usage will grow over the weeks, it will be obvious if +# it is going to use too much memory in the long run, and you'll have the time +# to upgrade. With maxmemory after the limit is reached you'll start to get +# errors for write operations, and this may even lead to DB inconsistency. +# +# maxmemory + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. If you can live +# with the idea that the latest records will be lost if something like a crash +# happens this is the preferred way to run Redis. If instead you care a lot +# about your data and don't want to that a single record can get lost you should +# enable the append only mode: when this mode is enabled Redis will append +# every write operation received in the file appendonly.aof. This file will +# be read on startup in order to rebuild the full dataset in memory. +# +# Note that you can have both the async dumps and the append only file if you +# like (you have to comment the "save" statements above to disable the dumps). +# Still if append only mode is enabled Redis will load the data from the +# log file at startup ignoring the dump.rdb file. +# +# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append +# log file in background when it gets too big. + +appendonly no + +# The name of the append only file (default: "appendonly.aof") +# appendfilename appendonly.aof + +# The fsync() call tells the Operating System to actually write data on disk +# instead to wait for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log . Slow, Safest. +# everysec: fsync only if one second passed since the last fsync. Compromise. +# +# The default is "everysec" that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving the durability of Redis is +# the same as "appendfsync none", that in pratical terms means that it is +# possible to lost up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. +no-appendfsync-on-rewrite no + +################################ VIRTUAL MEMORY ############################### + +# Virtual Memory allows Redis to work with datasets bigger than the actual +# amount of RAM needed to hold the whole dataset in memory. +# In order to do so very used keys are taken in memory while the other keys +# are swapped into a swap file, similarly to what operating systems do +# with memory pages. +# +# To enable VM just set 'vm-enabled' to yes, and set the following three +# VM parameters accordingly to your needs. + +vm-enabled no +# vm-enabled yes + +# This is the path of the Redis swap file. As you can guess, swap files +# can't be shared by different Redis instances, so make sure to use a swap +# file for every redis process you are running. Redis will complain if the +# swap file is already in use. +# +# The best kind of storage for the Redis swap file (that's accessed at random) +# is a Solid State Disk (SSD). +# +# *** WARNING *** if you are using a shared hosting the default of putting +# the swap file under /tmp is not secure. Create a dir with access granted +# only to Redis user and configure Redis to create the swap file there. +vm-swap-file /tmp/redis.swap + +# vm-max-memory configures the VM to use at max the specified amount of +# RAM. Everything that deos not fit will be swapped on disk *if* possible, that +# is, if there is still enough contiguous space in the swap file. +# +# With vm-max-memory 0 the system will swap everything it can. Not a good +# default, just specify the max amount of RAM you can in bytes, but it's +# better to leave some margin. For instance specify an amount of RAM +# that's more or less between 60 and 80% of your free RAM. +vm-max-memory 0 + +# Redis swap files is split into pages. An object can be saved using multiple +# contiguous pages, but pages can't be shared between different objects. +# So if your page is too big, small objects swapped out on disk will waste +# a lot of space. If you page is too small, there is less space in the swap +# file (assuming you configured the same number of total swap file pages). +# +# If you use a lot of small objects, use a page size of 64 or 32 bytes. +# If you use a lot of big objects, use a bigger page size. +# If unsure, use the default :) +vm-page-size 32 + +# Number of total memory pages in the swap file. +# Given that the page table (a bitmap of free/used pages) is taken in memory, +# every 8 pages on disk will consume 1 byte of RAM. +# +# The total swap size is vm-page-size * vm-pages +# +# With the default of 32-bytes memory pages and 134217728 pages Redis will +# use a 4 GB swap file, that will use 16 MB of RAM for the page table. +# +# It's better to use the smallest acceptable value for your application, +# but the default is large in order to work in most conditions. +vm-pages 134217728 + +# Max number of VM I/O threads running at the same time. +# This threads are used to read/write data from/to swap file, since they +# also encode and decode objects from disk to memory or the reverse, a bigger +# number of threads can help with big objects even if they can't help with +# I/O itself as the physical device may not be able to couple with many +# reads/writes operations at the same time. +# +# The special value of 0 turn off threaded I/O and enables the blocking +# Virtual Memory implementation. +vm-max-threads 4 + +############################### ADVANCED CONFIG ############################### + +# Glue small output buffers together in order to send small replies in a +# single TCP packet. Uses a bit more CPU but most of the times it is a win +# in terms of number of queries per second. Use 'yes' if unsure. +glueoutputbuf yes + +# Hashes are encoded in a special way (much more memory efficient) when they +# have at max a given numer of elements, and the biggest element does not +# exceed a given threshold. You can configure this limits with the following +# configuration directives. +hash-max-zipmap-entries 64 +hash-max-zipmap-value 512 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into an hash table +# that is rhashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# active rehashing the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply form time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all redis server but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# include /path/to/local.conf +# include /path/to/other.conf