Redis is an open source data structure server with an in-memory data set. It is called a data structure server, and not simply a key/value store, because Redis implements data structures allowing keys to contain binary safe strings, hashes, sets and sorted sets, as well as lists. This combination of flexibility and speed makes Redis the ideal tool for many applications.
Redis has incredible performance, due to the in-memory data set, but it is still possible to persist the data either by saving a snapshot of the data set to disk once in a while or appending each command to a log.
Redis also supports trivial-to-setup master/slave replication, with very fast non-blocking synchronization, auto reconnection on network split and so forth.
Redis first started in early 2009 as a key value store developed by Salvatore Sanfilippo in order to improve the performance of his own LLOOGG, an analytics product. Redis grew in popularity after getting support from people and companies in the developer world and has since been supported by VMWare, who hired Salvatore and Pieter Noordhuis to work full-time on the project.
This recipe will present a way of accessing a Redis data store with the help of existing libraries and Groovy.
In this recipe, we are going to take a look at the most common commands and data structures available in Redis.
Redis is easy to install. The recommended way to install Redis is by compiling the sources, as it doesn't have any external dependencies. Redis sources can be downloaded from the official website (http://redis.io) or directly from GitHub (https://github.com/antirez/redis). Build instructions can be found at the Redis quick start page (http://redis.io/topics/quickstart).
Alternatively, you can download the binaries (including Windows binaries) directly from the Redis download page (http://redis.io/download).
This recipe assumes that you have an instance of Redis 2.6 or higher, up and running on your development machine.
The Redis communication protocol is quite simple. A client needs to open a TCP/IP connection to the server and issue a Redis command terminated by (CR LF).
The general form is as follows:
*<number of arguments> CR LF $<number of bytes of argument 1> CR LF <argument data> CR LF .. $<number of bytes of argument N> CR LF <argument data> CR LF
Dealing with Redis at such a low level is not ideal. In order to simplify working with the commands, several clients have been implemented in the last years for almost every possible language.
There is no pure Groovy client at the moment, but there are several Java clients available. The one we are going to use in this recipe is Jedis, developed by Jonathan Leibiusky and considered the standard Java client for Redis.
Jedis can be downloaded directly from the project's GitHub page (https://github.com/xetorthio/jedis) or referenced as dependency with Grape.
For a complete list of Redis clients, please, refer to the official client page on the Redis website (http://redis.io/clients).
Let's see how we can access a Redis instance.
@Grab('redis.clients:jedis:2.1.0') import redis.clients.jedis.*
Jedis
class, which can be instantiated by passing the host of the Redis server:def jedis = new Jedis('localhost')
Jedis connects to the Redis default port, 6379. Should Redis run on a nonstandard port, you can pass the port value too:
def jedis = new Jedis('localhost', 9000)
jedis.set('foo', 'bar') String value = jedis.get('foo') assert value == 'bar'
jedis.set('counter', '1') jedis.incr('counter') jedis.incr('counter') assert jedis.get('counter') == '3'
expire
method passing the key and the number of seconds for the key to live:jedis.set('short lived', '10') jedis.expire('short lived', 3) Thread.sleep(3000) assert jedis.exists('short lived') == false
jedis.rpush('myList', 'a', 'b', 'c') assert 3 == jedis.llen('myList') assert '1' == jedis.lrange('myList', 0,0)[0] jedis.lpush('myList', '3', '2', '1') assert 6 == jedis.llen('myList')
rpop
method, which gets and removes the last element of a list:jedis.del('myQueue') jedis.lpush('myQueue', 'new york') jedis.lpush('myQueue', 'dallas') jedis.lpush('myQueue', 'tulsa') assert 'new york' == jedis.rpop('myQueue') assert 'dallas' == jedis.rpop('myQueue') assert 'tulsa' == jedis.rpop('myQueue')
Each method call of the Jedis API is translated into an actual Redis command. For example, the INCR
command, called in the step 4, atomically increments an integer value. It is important to note that this is a string operation, because Redis does not have a dedicated integer type. The EXPIRE
command, used in step 5, defines the amount of seconds a key is allowed to "live" before it expires.
Other commands that we demonstrated in the previous examples do the following:
One of the reasons Redis is a very popular caching solution for large web applications is the presence of hashes. A hash is essentially a map storing string fields and string values: the perfect data type for storing objects.
So, let's start by creating a simple User bean.
class User { Long userid String username String password }
The User
bean is a typical domain object that we want to store in Redis. Groovy has a very handy method exposed on the Object
class, getProperties
, which returns a Map
of the object's properties. The key/value structure is exactly what we need to store the domain object in Redis.
User u = new User() u.userid = 2001 u.username = 'john' u.password = '12345'
After creating an instance of the User
class, we proceed to store it in Redis by calling the HMSET
command.
jedis.hmset( "user:$u.userid", u.properties.findAll { !['class', 'userid'].contains(it.key) }.collectEntries { k,v -> [k, v.toString()] } )
The
hmset
method accepts a String
(the key) and a Map
, which contains the key/values we want to store. Logically, the key of the map is the primary key of the User, so we set it as user: $u.userid
.
The getProperties
method returns in the Map
also the actual class of the object on which we call the method on (in our case, User
). We don't need to store it, so we filter it out along with the userid
key, which would be redundant.
Finally, the collectEntries
method, invoked on the filtered Map
returns a new Map
where all the keys are Strings
, as this is the only data type Redis (and Jedis) accepts.
By calling the
HGETALL
command:
println jedis.hgetAll("user:$u.userid")
We get the following output:
> [username:john, password:12345]
One more Redis aspect we are going to touch is atomic operations. Redis doesn't support transactions in the same way as a relational database does (no rollbacks), but it is still possible to execute a number of commands atomically. Enter the MULTI
and EXEC
commands. The MULTI
command marks the beginning of a transaction, in which the following commands are queued and executed atomically.
This is how a MULTI
command works when executed in the Redis CLI (Command Line Interface):
> MULTI OK > INCR foo QUEUED > INCR bar QUEUED > EXEC
Jedis does support the MULTI
command, and we can actually implement it in a more Groovy way.
The following atomic method allows executing a group of commands inside the MULTI
/EXEC
transaction.
jedis = new Jedis('localhost') def atomic(Closure c) { def results = null try { def tx = jedis.multi() c.delegate = tx c.call() results = tx.exec() } catch(e) { tx.discard() } results }
The Jedis multi method returns a Transaction
object on which the commands have to be executed. This is an example of the actual Java code:
Transaction t = jedis.multi(); t.set("foo", "bar"); Response<String> result1 = t.get("foo");
Thanks to Groovy closures and the Closure
's delegate property we can invoke the atomic function as follows:
def res = atomic { incr ('foo') incr ('bar') } println jedis.get('foo') assert '1' == jedis.get('bar') assert res == [1, 1]
The two INCR
commands are executed atomically, and the res
variable contains the result of last command.