Sunday, December 23, 2012

Redis for all your Caching Needs



On this post I'll be walking you through Redis and how to access it with Java. Redis also supports other drivers, so connecting to Redis with your favorite programming language is a breeze. Click http://redis.io/clients for a full list of clients.  

I have used Redis primarily as a caching solution although there are plenty of other use cases.

Let first get the pleasantries out of the way.

Redis is a key-value data store. More correctly it's an in-memory persistent multi-value data store. The persistence comes from the fact that Redis by default snapshots its memory content on a frequent basis on to disk. This process is configurable, i.e you can configure Redis to save the data store every X number or key changes, every Y seconds. Redis is a database, albeit not your everyday relational database. It does capture the essence of a typical database you and I are familiar with. Redis databases are identified by numbers, 0 being the default one, and hence the one automatically selected when you don't specify one. Apart from Strings Redis also lets you save sets, lists and hashes and finally sorted sets. I usually JSON encode my data before saving to Redis, that way I can store complete data structures. Interested readers can try out other data structure, Redis comes loaded with heaps of operations you can perform on your data.

Redis is fast, it's really really fast, but you need to carefully analyze your requirements to see if it is in fact what you need for your data storage requirements. Redis is no-sql, non-relational and most of all its in-memory, well at least for most of the time. So you if you are looking to store a lot of data and that data is mission critical then Redis might not be the best of choices. But if you are looking for a really fast multi-value data store, your data is relatively light weight, and is not critical then Redis is your guy. To add to the last point, Redis does allow asynchronous master-slave replication mode which alleviates data loss due to unexpected server failure, therefore it's not a risk though.

I prefer using hierarchical keys, per say, to make retrieval of data easier. A typical key is prepended by the application name, component name etc and of course a unique identifier, for e.g suppose I want to save details about users on my system, I would create a User entity, instantiate it and populate it with data, stringify it before finally saving it, and keys would take the form of "[application name]::[component name]::[unique id]", this also gives me the ability to retrieve content based on key patterns. Of course you can create keys and name them at your whims and fancies. Whats more, you can set TTls for keys too.

I believe this is enough for you to get through this post. Redis however deserves a full post.

You can download Redis for windows at https://github.com/dmajkic/redis/downloads. Unzip it, and invoke redis-server.exe in a suitable version (win-32 or win-64). You know you are good to go when you see "[37740] 23 Dec 15:37:27 * The server is now ready to accept connections on port 6379". You can override ports and a lot more, but that's outside the scope of this post.

Jedis is an excellent choice as an interface to Redis, because its comprehensive and easy to configure. Download Jedis from https://github.com/xetorthio/jedis. However it can also lead many astray, hence it's always a good idea to wrap Jedis Operations in a facade and only expose the operation you want to be performed on your data. 

Ok, on to the fun part. Now as I mentioned earlier you're going to need a driver to interact with Redis server, I use Jedis, following is the pom file with Jedis dependency.


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.redis.test</groupId>
 <artifactId>RedisCache</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>Redis Cache Client</name>
 <description>Redis Cache Client</description>
 <dependencies>
  <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>1.5.2</version>
        </dependency>
 </dependencies>
</project>

Now on to the Jedis facade as I call it.



package com.redis.client;

import java.util.Set;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class RedisClient {
 private JedisPool jedisPool;

 public void save(String key, String value) {
  if (key == null) {
   throw new RuntimeException("? Redis key can not be null");
  }
  Jedis jedis = jedisPool.getResource();
  try {
   jedis.set(key, value);
  } catch (Exception e) {
  } finally {
   jedisPool.returnResource(jedis);
  }
 }

 public String load(String key) {
  if (key == null) {
   throw new RuntimeException("? Redis key can not be null");
  }
  Jedis jedis = jedisPool.getResource();
  String result;
  try {
   result = jedis.get(key);
  } catch (Exception e) {
   result = null;
  } finally {
   jedisPool.returnResource(jedis);
  }
  return result;
 }

 public Long addToSet(String key, String member) {
  if (key == null) {
   throw new RuntimeException("? Redis key can not be null");
  }
  Jedis jedis = jedisPool.getResource();
  Long result;
  try {
   result = jedis.sadd(key, member);
  } catch (Exception e) {
   result = null;
  } finally {
   jedisPool.returnResource(jedis);
  }
  return result;
 }

 public Set getKeysMatchingPattern(String pattern) {
  Set result = null;
  Jedis jedis = jedisPool.getResource();
  try {
   result = jedis.keys("*"+pattern+"*");
  } catch (Exception e) {
   result = null;
  }finally {
   jedisPool.returnResource(jedis);
  }
  return result;
 }

 /**
  * @param jedisPool
  *            the jedisPool to set
  */
 public void setJedisPool(JedisPool jedisPool) {
  this.jedisPool = jedisPool;
 }

}
Notice how I always return the connection back to the pool. This is important, you don't want to hold on to your connections once you are done. Also opt for batch operations, where with a single connection you do a lot of things, this is an excellent way to avoid connection exhaustion. Note also, that are heaps more operations you can perform with Jedis on top of Redis, feel free to explore them at http://redis.io/commands.

Where there are connections there is pooling. Jedis allows you to configure pool properties. It is not always a good idea to go with defaults, as defaults have a tendency to come back and bite you where it hurts.


Finally here's how you can use it,




package com.redis;

import java.util.Set;

import org.apache.commons.pool.impl.GenericObjectPool;

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import com.redis.client.RedisClient;

public class Tester {
 public static void main(String[] args) {

  JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
  
  /* The maximum active connections per Redis instance */
  jedisPoolConfig.setMaxActive(10);
  /* The minimum idling connections- these connections are always open and always ready */
  jedisPoolConfig.setMinIdle(5);
  /* The maximum active connections per Redis instance */
  jedisPoolConfig.setMaxActive(5);
  /* Fail- fast behaviour Set the action to take when your pool runs out of connections 
   * default is to block the caller till a connection frees up */
  jedisPoolConfig
    .setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
  /*Tests if a connection is still alive at the time of retrieval*/
  jedisPoolConfig.setTestOnBorrow(true);
  /* Tests whether connections are dead during idle periods */
  jedisPoolConfig.setTestWhileIdle(true);
  /*Number of connections to check at each idle check*/
  jedisPoolConfig.setNumTestsPerEvictionRun(10);
  /* Check idling connections every */
  jedisPoolConfig.setTimeBetweenEvictionRunsMillis(60000);
  /*maximum time in milliseconds to wait when the exhaust action is set to block*/
  jedisPoolConfig.setMaxWait(3000);

  JedisPool jedisPool = new JedisPool(jedisPoolConfig, "localhost", 6379);
  RedisClient redisClient = new RedisClient();
  redisClient.setJedisPool(jedisPool);

  redisClient.save("rediscache::tester::test1", "test");
  redisClient.save("rj::rediscache::tester::test2", "test2");
  Set keys = redisClient.getKeysMatchingPattern("rediscache::tester::");
  for (String key : keys) {
   System.out.println("Key : " + key + " Value : " + redisClient.load(key));
  }
 }
}
Before running this make sure your Redis instance is up and running.

You can grab the full source here.

No comments:

Post a Comment