Micronaut Redis

Integration between Micronaut and Redis

Version: 7.0.0-SNAPSHOT

1 Introduction

Micronaut features automatic configuration of the Lettuce driver for Redis via the redis-lettuce module.

2 Release History

For this project, you can find a list of releases (with release notes) here:

3 Setting up the Redis Lettuce Driver

Using the CLI

If you are creating your project using the Micronaut CLI, supply the redis-lettuce feature to configure the Lettuce driver in your project:

$ mn create-app my-app --features redis-lettuce

To configure the Lettuce driver you should first add the redis-lettuce module to your classpath:

implementation("io.micronaut.redis:micronaut-redis-lettuce")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce</artifactId>
</dependency>

You should then configure the URI of the Redis server you wish to communicate with in application.yml:

Configuring redis.uri
redis:
    uri: redis://localhost
The redis.uri setting should be in the format as described in the Connection URIs section of the Lettuce wiki

You can also specify multiple Redis URIs using redis.uris in which case a RedisClusterClient is created instead.

Configuring Lettuce ClientResources and threads

You can provide a custom instance of io.lettuce.core.resource.ClientResources it will be used to create io.lettuce.core.RedisClient.

It’s possible to configure thread pool size without providing custom io.lettuce.core.resource.ClientResources:

redis:
    uri: redis://localhost
    io-thread-pool-size: 5
    computation-thread-pool-size: 4

Lettuce description of pool size properties

Name Default

I/O Thread Pool Size

Number of processors

The number of threads in the I/O thread pools. The number defaults to the number of available processors that the runtime returns (which, as a well-known fact, sometimes does not represent the actual number of processors). Every thread represents an internal event loop where all I/O tasks are run. The number does not reflect the actual number of I/O threads because the client requires different thread pools for Network (NIO) and Unix Domain Socket (EPoll) connections. The minimum I/O threads are 3. A pool with fewer threads can cause undefined behavior.

Computation Thread Pool Size

Number of processors

The number of threads in the computation thread pool. The number defaults to the number of available processors that the runtime returns (which, as a well-known fact, sometimes does not represent the actual number of processors). Every thread represents an internal event loop where all computation tasks are run. The minimum computation threads are 3. A pool with fewer threads can cause undefined behavior.

You may see io.lettuce.core.RedisCommandTimeoutException: Command timed out after if your code is blocking Lettuce’s asynchronous execution because of the default value of the thread pool size being small.

Available Lettuce Beans

Once you have the above configuration in place you can inject one of the following beans:

  • io.lettuce.core.RedisClient - The main client interface

  • io.lettuce.core.api.StatefulRedisConnection - A connection interface that features synchronous, reactive (based on Reactor) and async APIs that operate on String values

  • io.lettuce.core.pubsub.StatefulRedisPubSubConnection - A connection interface for dealing with Redis Pub/Sub

The following example demonstrates the use of the StatefulRedisConnection interface’s synchronous API:

Using StatefulRedisConnection
@Inject StatefulRedisConnection<String, String> connection
...
RedisCommands<String, String> commands = connection.sync()
commands.set("foo", "bar")
commands.get("foo") == "bar"
final key = "foo".bytes
final value = "bar".bytes
RedisCommands<byte[], byte[]> commands = connection.sync()
commands.set(key, value)
commands.get(key) == value
The Lettuce driver’s StatefulRedisConnection interface is designed to be long-lived and there is no need to close the connection. It will be closed automatically when the application shuts down.

Redis codec configuration

By default, a StringCodec is used for redis connections. This can be configured by supplying your own Factory that replaces the default one.

Configuring a custom RedisCodecFactory
@Factory
class ByteArrayCodecReplacementFactory {

    @Singleton
    @Replaces(RedisCodec)
    RedisCodec<byte[], byte[]> redisCodec() {
        return ByteArrayCodec.INSTANCE
    }
}

4 Configuring the Redis Lettuce Driver

Customizing The Redis Configuration

You can customize the Redis configuration using any properties exposed by the DefaultRedisConfiguration class. For example, in application.yml:

Customizing Redis Configuration
redis:
    uri: redis://localhost
    ssl: true
    timeout: 30s

Multiple Redis Connections

You can configure multiple Redis connections using the redis.servers setting. For example:

Customizing Redis Configuration
redis:
    servers:
        foo:
            uri: redis://foo
        bar:
            uri: redis://bar

In which case the same beans will be created for each entry under redis.servers but exposed as @Named beans.

Using StatefulRedisConnection
@Inject @Named("foo") StatefulRedisConnection<String, String> connection;

The above example will inject the connection named foo.

Named connection codec configuration

When using named redis connections, you can change the codec for each connection by supplying a named RedisCodec bean. For example:

Supply different codecs for named connections
@Factory
class NamedCodecFactory {

    @Singleton
    @Named("foo")
    RedisCodec<byte[], byte[]> fooCodec() {
        return ByteArrayCodec.INSTANCE
    }

    @Singleton
    @Named("bar")
    RedisCodec<String, String> barCodec() {
        return StringCodec.ASCII
    }
}

Redis Health Checks

When the redis-lettuce module is activated a RedisHealthIndicator is activated resulting in the /health endpoint and CurrentHealthStatus interface resolving the health of the Redis connection or connections.

See the section on the Health Endpoint for more information.

5 Redis and Testing

For testing purposes, we recommend running a real version of Redis inside a Docker container via TestContainers.

GenericContainer<?> redisContainer = new GenericContainer<>(DockerImageName.parse(REDIS_DOCKER_NAME))
    .withExposedPorts(REDIS_PORT)
    .waitingFor(
            Wait.forLogMessage(".*Ready to accept connections.*\\n", 1)
    );
redisContainer.start();

The embedded redis container we used to recommend has been deprecated as of 5.3.0 and will be removed at a later date.

6 Redis for Caching

If you wish to use Redis to cache results then you need to have the Lettuce configuration dependency on your classpath. Lettuce is a non-blocking, reactive Redis client implementation and Micronaut provides an implementation that allows cached results to be read reactively.

Within your application configuration configure the Redis URL and Redis caches:

Cache Configuration Example
redis:
    uri: redis://localhost
    caches:
        my-cache:
            # expire one hour after write
            expire-after-write: 1h
Cache Configuration Example with a Dynamic Expiration policy
redis:
    uri: redis://localhost
    caches:
        my-cache:
            # expire based on result from class implementing ExpirationAfterWritePolicy
            expiration-after-write-policy: <class path of class implementing ExpirationAfterWritePolicy>
🔗
Table 1. Configuration Properties for RedisCacheConfiguration
Property Type Description

redis.caches.*.server

java.lang.String

redis.caches.*.value-serializer

java.lang.Class

redis.caches.*.key-serializer

java.lang.Class

The {@link ObjectSerializer} to use for serializing keys. Defaults to DefaultStringKeySerializer.

redis.caches.*.expire-after-write

java.time.Duration

redis.caches.*.expire-after-access

java.time.Duration

Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after the entry’s creation, the most recent replacement of its value, or its last read.

redis.caches.*.expiration-after-write-policy

java.lang.String

redis.caches.*.charset

java.nio.charset.Charset

7 Session State with Redis

Storing Session instances in Redis requires special considerations.

You can configure how sessions are stored in Redis using RedisHttpSessionConfiguration.

The following represents an example configuration in application.yml:

Configuring Redis Sessions
micronaut:
    session:
        http:
            redis:
                enabled: true
                # The Redis namespace to write sessions to
                namespace: 'myapp:sessions'
                # Write session changes in the background
                write-mode: BACKGROUND
                # Disable programmatic activation of keyspace events
                enable-keyspace-events: false
The RedisSessionStore implementation uses keyspace events to cleanup active sessions and fire SessionExpiredEvent and requires they are active.

By default sessions values are serialized using Java serialization and stored in Redis hashes. You can configure serialization to instead use Jackson to serialize to JSON if desired:

Using Jackson Serialization
micronaut:
    session:
        http:
            redis:
                enabled: true
                valueSerializer: io.micronaut.jackson.serialize.JacksonObjectSerializer

8 GraalVM support

It is possible to create native images for Micronaut applications that use the Lettuce driver. There are some limitations and configuration needed because of the driver itself so please make sure you read the official driver documentation about GraalVM. Micronaut provides the configuration for Netty so you don’t need to add that part to your own reflect-config.json.

See the section on GraalVM in the user guide for more information.

9 Repository

You can find the source code of this project in this repository:

10 Appendices

10.1 Breaking Changes

This section documents breaking changes between versions

7.0.0

The following were deprecated in version 6.0.0 and have been removed.

  • Singleton constructor: RedisHealthIndicator(BeanContext, HealthAggregator, RedisClient[], RedisClusterClient[]). It is replaced with RedisHealthIndicator(BeanContext, ExecutorService, HealthAggregator<?>, RedisClient[], RedisClusterClient[]).

  • Factory method: DefaultRedisClusterClientFactory.redisClient(AbstractRedisConfiguration, ClientResources). It is replaced with redisClient(AbstractRedisConfiguration, ClientResources, List<ClientResourcesMutator>).

5.3.0

  • The embedded Redis server that can be used for testing has been changed to only bind to localhost.

If you wish to revert to the previous behavior, you will need to use a configuration file specified in your test specific application.yml file.

embedded-redis.conf
maxmemory 256M
test-application.yml
redis:
  embedded:
    config-file: '/full/path/to/embedded-redis.conf'