User Guide


AREDIS

Documentation

Getting Started
API Overview
Important Classes and Info
Data Handlers
Connections and Threads
LimitingTaskExecutor
Redis Scripting Support
More Examples

Getting Started

AREDIS requires commons-logging.jar. Include it in your CLASSPATH along with the latest aredis jar. You also need Java 7.

Below is an example piece of code to store and retrieve a String world against a Key hello:


        // In a Server create only one instance of AsyncRedisFactory
        // by configuring it as a Spring Bean or as a Singleton
        AsyncRedisFactory f = new AsyncRedisFactory(null);
        // The below call returns the same AsyncRedisClient for all calls to the same server
        AsyncRedisClient aredis = f.getClient("localhost");
        // Use sendCommand instead of submitCommand when you are not interested in the Return value
        aredis.sendCommand(RedisCommand.SETEX, "hello", "300", "world");
        Future<RedisCommandInfo> future = aredis.submitCommand(RedisCommand.GET, "hello");
        try {
            String val = (String) future.get().getResult();
            System.out.println("Got back val = " + val);
        }
        catch(Exception e) {
            e.printStackTrace();
        }

Here is the full code


API Overview

As you might have noticed in the Getting Started example, the API of AsyncRedisClient is designed as generic methods to submit any Redis command and not as a set of Type Safe methods, one for each command or class of commands. This is to accommodate a Future based synchronous API and Async API. So it helps to refer to the Redis Command Page when coding. Adding a new Redis Command introduced in a Redis Server release can be done by simply adding to the RedisCommand enum.

The API takes a RedisCommand enum value identifying the command followed by a variable argument list for the command parameters. Each command has an argInfo String given in the Enum constructor which identifies each of the command parameters, whether it is a, key, qualifier or value. Using the argInfo for the command the API treats each parameter as a key, parameter (qualifier) or value. Keys and qualifiers are usually Strings in the vararg list at are sent to redis after encoding them in UTF-8. The keys can be byte arrays if they cannot be represented as UTF-8. The values can be Strings or any other Serializable java Objects and are converted to bytes using a Data Handler. The default data handler stores Strings smaller than 1024 bytes as it is using UTF-8 encoding which makes String values compatible with other language clients. The default data handler compresses Strings or serialized values larger than 1 Kb using gzip.

The result of running the command is contained in a RedisCommandInfo Object which is returned by the API either as a Future return value or as a parameter in the supplied callback in Async mode. The getResult method of RedisCommandInfo returns the result which is of type Object. The Object returned is a String for INT, String and Redis Error responses. For bulk responses the data handler is used to convert the response to a String or the Object which was stored. For Commands like INCR where you know that the response is an int or double you can use methods getIntResult or getDoubleResult of RedisCommandInfo to convert the String result to your data type. For multi-bulk (Array) response the return value is an array of Objects each of which contains the individual result in the multi-bulk response. The result is null for a null bulk or multi-bulk response and also in case of an error executing the command such as a network error in which case the getRunStatus method returns the appropriate status other than SUCCESS.

For more details about the API please see the section on important classes, examples and also the Javadocs.


Important Classes and Info

AsyncRedisClient is a common client interface. It has two implementations AsyncRedisConnection and ShardedAsyncRedisClient.

AsyncRedisConnection is the central class providing the connection to the Redis Server via an Asynchronous socket API. The Asynchronous connection is abstracted by the interface AsyncSocketTransport. Currently AsyncJavaSocketTransport using the Java 7 NIO based Asynchronous channel is the only implementation of the interface which is why you need Java 7.

ShardedAsyncRedisClient is an implementation of AsyncRedisClient providing Sharding of Keys across 2 or more AsyncRedisConnection's. The Hash of the Key is computed and based on the Hash the command is passed on to one of the connections based on a Consistent Hashing algorithm. Commands with multiple keys are not allowed unless all of the keys map to the same connection. So you will get an Exception if you submit a command like MSETNX. MSET and MGET are commands with multiple keys which are exceptions to this rule. For these commands the keys are distributed based on their hashes and the results re-assembled as part of the API.

RedisCommandInfo is the DTO (Data Transfer Object) for a RedisCommand. It is a holder for the RedisCommand and its parameters, the DataHandler to be used for serializing and de-serializing data and the Result. The getResult method returns a String in case of a String result or Redis error. It returns the de-serialized result in case of a bulk response and an array of de-serialized Objects in case of a multi-bulk response. The response is an array of RedisCommandInfo for each command in the transaction in case of an EXEC command. getResult returns null in case of a null bulk or multi-bulk response and also in case of an error executing the command such as a network error in which case the getRunStatus method returns the appropriate status other than SUCCESS. null is also returned in case EXEC fails because of a change in a watched key.

There are different variations of the submitCommand method in AsyncRedisClient based on the following parameters:

  1. RedisCommandInfo parameter: You pass a RedisCommandInfo Object containing the command with the DataHandler along with other arguments.
  2. RedisCommand with arguments: This is a convenience wrapper on the previous set of commands where the RedisCommandInfo constructor arguments are flattened along with the rest of the parameters. These method implementations construct a RedisCommandInfo object and call one of the previous methods.
  3. RedisCommand[] array: These commands are executed together in the pipeline. So it can be used for a MULTI-EXEC set of commands not requiring a WATCH on one or more keys.
  4. Without a completionHandler: These commands return a Future Object on which a get method can be called to get the RedisCommandInfo or RedisCommandInfo[] array containing the results.
  5. With a completionHandler: These are async commands that call the completionHandler with the RedisCommandInfo or RedisCommandInfo[] array containing the results.
  6. With a null completionHandler: You can pass a null completionHandler when you want to submit the command and are not interested in the results. This Send and Forget call is slightly better than using a command that returns a Future and ignoring it because the Future object does not need to be created and notified. The sendCommand methods are convenience wrappers providing the same functionality.
  7. With a DataHandler: The values in a Redis Command are converted to bytes when sending them and converted back to Object when processing Return values using a DataHandler. You can pass a Data Handler in case you want to override the default Data Handler.
The return value if any of all submit command(s) calls is a Future Object of RedisCommandInfo or RedisCommandInfo[]; The value returned by the Future is the same as what was passed with the result set in the CommandInfo(s).

The submitCommand calls work as follows.

First the command is serialized and submitted to a request Q.

An async NIO based request Q listener de-queues the commands from the request Q one by one, sends them to the REDIS server and moves the command to the response Q.

An async NIO based response Q listener de-queues the commands from the response Q one by one, reads and parses the corresponding response from the REDIS server and sends back the response.

The de-serialization happens on the first call to getResult on the returned CommandInfo after which the de-serialized result is saved and returned on subsequent calls.

AsyncObjectPool is a fixed size Object pool with Async borrow functionality used by aredis to provide a connection pool for Redis Transactions using WATCH, MULTI and EXEC commands.

RedisSubscription provides an API for subscribing to channels using redis's SUBSCRIBE or PSUBSCRIBE commands.

AsyncRedisFactory is a factory class for getting one of the above services for a given redis server. It returns the same Object for the same connection and is the preferred way of using these services. The getClient method returns an AsyncRedisClient for a single or Multiple Servers. The implementation of AsyncRedisClient returned is an instance AsyncRedisConnection if only one server is passed and an instance of ShardedAsyncRedisClient if a comma separated list of servers is passed. The getConnectionPool method returns an instance of AsyncObjectPool and the getSubscription method returns an instance of RedisSubscription.

Script is an immutable Object holding a Redis Lua script and its digest. It should be used for the EVALCHECK command and is recommended as a parameter for other script commands like EVALSHA though you won't usually need to use them if you use the EVALCHECK pseudo command. A script object is created using the getInstance method which returns the same script object for identical scripts. Each script is also assigned a unique index starting with 0 upon creation. The index is used internally to maintain the status flags for each Redis Server to indicate if the script has been verified on that server.


Data Handlers

A DataHandler specifies the serialization and de-serialization of java Objects when storing and retrieving them.

The default handler is OPTI_JAVA_HANDLER which is a JavaHandler created by passing optimizeObjectStorage as true.

JavaHandler stores and retrieves Strings or compressed Strings without any changes so that it is compatible with other clients. It does this by looking for either a GZIP compression header or an aredis Marker header. If both are not found it is treated as a String. If a GZIP header is found it decompresses the data and interprets it as a String if it does not begin with an aredis marker header. In the unlikely case when the encoded String begins with an aredis Marker or GZIP header they are escaped with an escape marker which is discarded during de-serialization. Instances of Number like Integer, Long and Double are serialized and de-serialized as their String values. Other Java Objects are stored using Java Serialization and prefixed by an aredis Marker sequence to indicate if it is an optimized storage or regular serialization. When optimizeObjectStorage is true then The Class Descriptors for Classes is stored in a common key JAVA_CL_DESCRIPTORS as an array and what is written or read from the serialized data is only an index in the array. This saves the repetition of Class Descriptors data across keys. This is accomplished by overriding the ObjectOutputStream.writeClassDescriptor and ObjectInputStream.readClassDescriptor Java APIs. The serialized data is Gzipped if it crosses the configured compression threshold which is defaulted at 1024.

JAVA_HANDLER is a JavaHandler with optimizeObjectStorage as false.

Other DataHandlers are StringHandler and BinaryHandler.

You can change your default DataHandler by configuring your AsyncRedisFactory or by setting AsyncRedisConnection.DEFAULT_HANDLER

When there is an exception during Serialization or De-serialization CommandInfo.getRunStatus returns a status of DECODE_ERROR.


Connections and Threads

Aredis tries to reduce the usage of Socket connections and Threads.

Socket connections are created by AsyncRedisConnection or RedisSubscription only when they are first used. When the connection is unused for more than 15 minutes (or the Redis Server timeout if that is lesser) the connection is detected as idle and closed. It is re-opened only when it is used again.

The AsyncObjectPool used as connection pool uses a stack to maintain free connections which translates to a MRU algorithm. So even if the pool size is 10 if only 2 connections are required at a time for Redis transactions only 2 socket connections are created and re-used. The others are not used unless your app borrows more than 2 connections at a given point of time.

Re-connects: Whenever a Network error occurs the commands already sent are returned with a status of NETWORK_ERROR which also means that it is not known if the commands were executed by the Redis Server or not. The commands which are not yet sent are returned immediately with a status of SKIPPED. Re-connects are attempted during future submitCommand calls with an exponential back-off algorithm (Re-tries at 0.01s, 0.1s, 1s, 2s, 4s, 8s ...) till 2 minutes after which there is a re-try every 2 minutes.

By default the AsyncJavaSocketTransport uses only 10 daemon threads to manage all socket connections via NIO. You can change this for the entire JVM by initializing AsyncJavaSocketTransport.channelGroup in the static block of one of your classes.

There is also a pool of 10 threads used by AsyncRedisConnection.bootstrapExecutor. This Thread pool is also configured to shutdown all idle threads.

When using aredis with Async callbacks or RedisSubscription it is better you configure AsyncRedisFactory with a non-null Executor so that your callbacks do not use the internal threads.

There is also 1 thread used by AsyncRedisFactory for the purposes of closing idle connections, checking Connection Pool leaks and re-connecting broken connections for RedisSubscription. If you already have a scheduler in your system you can configure AsyncRedisFactory to use it instead of creating a new timer and thread by providing an implementation of RedisTimer and calling AsyncRedisFactory.setTimer.


LimitingTaskExecutor

LimitingTaskExecutor is generic utility that provides a fixed size executor by wrapping a ThreadPoolExecutor and limiting the number of pending tasks submitted to it thus re-using the underlying executor. For aredis it is useful in creating an Executor of smaller size for a Redis message subscription from a global thread pool. The LimitingTaskExecutor can be specified for all subscriptions by passing it to AsyncRedisFactory.getSubscription() or for a particular subscription by passing it to SubscriptionInfo. See the test directory for code examples using LimitingTaskExecutor.


Redis Scripting Support

Redis provides two commands EVAL and EVALSHA for running Lua scripts on the server. While EVALSHA is more efficient it requires EVAL or SCRIPT LOAD to be called for the script before it can be used.

aredis provides a single pseudo command EVALCHECK that translates into an EVALSHA command after ensuring that the script is present on the server using the SCRIPT EXISTS command and SCRIPT LOAD command to load the script if SCRIPT EXISTS returns 0. This is done only for the first time the script is run after which a flag is set in the jvm for the Redis Server against the Script to indicate that it is verified.

Subsequent EVALCHECK calls to the same script on the same Redis server translate into EVALSHA straight away since the flag for the server against the script will be set.


More Examples

Configuration and Customization

AREDIS allows configuration of most parameters. You can configure AsyncRedisFactory to change your default handler and connection pool size. You can also configure OPTI_JAVA_HANDLER to use a common Redis Server for storing class descriptors instead of the connection's server by using RedisClassDescriptorStorageFactory instead of the default PerConnectionRedisClassDescriptorStorageFactory. Please refer to the javadocs for more info. You can check the test directory for a custom data handler for mapping objects to json format using jackson API.



Copyright © 2013-2014, Suresh Mahalingam