$ mn create-app my-app --features redis-lettuce
Micronaut Redis
Integration between Micronaut and Redis
Version: 6.6.2-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 |
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 the application configuration file:
redis.uri
redis.uri=redis://localhost
redis:
uri: redis://localhost
[redis]
uri="redis://localhost"
redis {
uri = "redis://localhost"
}
{
redis {
uri = "redis://localhost"
}
}
{
"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 |
|
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 |
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 onString
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:
@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.
@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 the application configuration file:
redis.uri=redis://localhost
redis.ssl=true
redis.timeout=30s
redis:
uri: redis://localhost
ssl: true
timeout: 30s
[redis]
uri="redis://localhost"
ssl=true
timeout="30s"
redis {
uri = "redis://localhost"
ssl = true
timeout = "30s"
}
{
redis {
uri = "redis://localhost"
ssl = true
timeout = "30s"
}
}
{
"redis": {
"uri": "redis://localhost",
"ssl": true,
"timeout": "30s"
}
}
Multiple Redis Connections
You can configure multiple Redis connections using the redis.servers
setting. For example:
redis.servers.foo.uri=redis://foo
redis.servers.bar.uri=redis://bar
redis:
servers:
foo:
uri: redis://foo
bar:
uri: redis://bar
[redis]
[redis.servers]
[redis.servers.foo]
uri="redis://foo"
[redis.servers.bar]
uri="redis://bar"
redis {
servers {
foo {
uri = "redis://foo"
}
bar {
uri = "redis://bar"
}
}
}
{
redis {
servers {
foo {
uri = "redis://foo"
}
bar {
uri = "redis://bar"
}
}
}
}
{
"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.
@Inject @Named("foo") StatefulRedisConnection<String, String> connection;
The above example will inject the connection named foo
.
MasterReplica Configuration
You can configure a standalone redis instance with replicas by supplying the redis.replica-uris
setting to list out the location of all replicas.
redis:
uri: redis://localhost
replica-uris:
- redis://localhost:6578
ssl: true
timeout: 30s
ReadFrom Settings
For MasterReplica and Cluster configurations the ReadFrom Setting can be configured using the redis.read-from
setting.
redis:
uris:
- redis://localhost
read-from: replicaPreferred
This setting accepts a string matching the values accepted in the ReadFrom.valueOf method. These are currently master
, masterPreferred
, upstream
, upstreamPreferred
, replica
, replicaPreferred
, lowestLatency
, any
, and anyReplica
.
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:
@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.
The health indicator is enabled by default. To disable the health endpoint, you can do so via the config.
redis.health.enabled=false
redis:
health:
enabled: false
[redis]
[redis.health]
enabled=false
redis {
health {
enabled = false
}
}
{
redis {
health {
enabled = false
}
}
}
{
"redis": {
"health": {
"enabled": false
}
}
}
See the section on the Health Endpoint for more information.
Disabling Redis
You can disable the creation of Redis connections using the redis.enabled
setting, through configuration:
redis.enabled=false
redis:
enabled: false
[redis]
enabled=false
redis {
enabled = false
}
{
redis {
enabled = false
}
}
{
"redis": {
"enabled": false
}
}
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:
redis.uri=redis://localhost
redis.caches.my-cache.expire-after-write=1h
redis:
uri: redis://localhost
caches:
my-cache:
expire-after-write: 1h
[redis]
uri="redis://localhost"
[redis.caches]
[redis.caches.my-cache]
expire-after-write="1h"
redis {
uri = "redis://localhost"
caches {
myCache {
expireAfterWrite = "1h"
}
}
}
{
redis {
uri = "redis://localhost"
caches {
my-cache {
expire-after-write = "1h"
}
}
}
}
{
"redis": {
"uri": "redis://localhost",
"caches": {
"my-cache": {
"expire-after-write": "1h"
}
}
}
}
redis.uri=redis://localhost
redis.caches.my-cache.expiration-after-write-policy=<class path of class implementing ExpirationAfterWritePolicy>
redis:
uri: redis://localhost
caches:
my-cache:
expiration-after-write-policy: <class path of class implementing ExpirationAfterWritePolicy>
[redis]
uri="redis://localhost"
[redis.caches]
[redis.caches.my-cache]
expiration-after-write-policy="<class path of class implementing ExpirationAfterWritePolicy>"
redis {
uri = "redis://localhost"
caches {
myCache {
expirationAfterWritePolicy = "<class path of class implementing ExpirationAfterWritePolicy>"
}
}
}
{
redis {
uri = "redis://localhost"
caches {
my-cache {
expiration-after-write-policy = "<class path of class implementing ExpirationAfterWritePolicy>"
}
}
}
}
{
"redis": {
"uri": "redis://localhost",
"caches": {
"my-cache": {
"expiration-after-write-policy": "<class path of class implementing ExpirationAfterWritePolicy>"
}
}
}
}
-
Expiration is based on result from an implementation of ExpirationAfterWritePolicy
Property | Type | Description |
---|---|---|
|
java.lang.String |
|
|
java.lang.Class |
|
|
java.lang.Class |
The {@link ObjectSerializer} to use for serializing keys. Defaults to DefaultStringKeySerializer. |
|
java.time.Duration |
|
|
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. |
|
java.lang.String |
|
|
java.nio.charset.Charset |
|
|
java.lang.Long |
Returns the count used for the scan command in cache.RedisCache#invalidateAll(). See {@link io.lettuce.core.ScanArgs#limit(long)}. Defaults to 100L. |
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 the application configuration file:
micronaut.session.http.redis.enabled=true
micronaut.session.http.redis.namespace=myapp:sessions
micronaut.session.http.redis.write-mode=BACKGROUND
micronaut.session.http.redis.enable-keyspace-events=false
micronaut:
session:
http:
redis:
enabled: true
namespace: 'myapp:sessions'
write-mode: BACKGROUND
enable-keyspace-events: false
[micronaut]
[micronaut.session]
[micronaut.session.http]
[micronaut.session.http.redis]
enabled=true
namespace="myapp:sessions"
write-mode="BACKGROUND"
enable-keyspace-events=false
micronaut {
session {
http {
redis {
enabled = true
namespace = "myapp:sessions"
writeMode = "BACKGROUND"
enableKeyspaceEvents = false
}
}
}
}
{
micronaut {
session {
http {
redis {
enabled = true
namespace = "myapp:sessions"
write-mode = "BACKGROUND"
enable-keyspace-events = false
}
}
}
}
}
{
"micronaut": {
"session": {
"http": {
"redis": {
"enabled": true,
"namespace": "myapp:sessions",
"write-mode": "BACKGROUND",
"enable-keyspace-events": false
}
}
}
}
}
-
The Redis
namespace
spcifies where to write sessions. -
Using
BACKGROUND
write sessions changes in the background -
enable-keyspace-events
enables/disables programmatic activation of keyspace events
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:
micronaut.session.http.redis.enabled=true
micronaut.session.http.redis.valueSerializer=io.micronaut.jackson.serialize.JacksonObjectSerializer
micronaut:
session:
http:
redis:
enabled: true
valueSerializer: io.micronaut.jackson.serialize.JacksonObjectSerializer
[micronaut]
[micronaut.session]
[micronaut.session.http]
[micronaut.session.http.redis]
enabled=true
valueSerializer="io.micronaut.jackson.serialize.JacksonObjectSerializer"
micronaut {
session {
http {
redis {
enabled = true
valueSerializer = "io.micronaut.jackson.serialize.JacksonObjectSerializer"
}
}
}
}
{
micronaut {
session {
http {
redis {
enabled = true
valueSerializer = "io.micronaut.jackson.serialize.JacksonObjectSerializer"
}
}
}
}
}
{
"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
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 configuration file.
maxmemory 256M
redis.embedded.config-file=/full/path/to/embedded-redis.conf
redis:
embedded:
config-file: '/full/path/to/embedded-redis.conf'
[redis]
[redis.embedded]
config-file="/full/path/to/embedded-redis.conf"
redis {
embedded {
configFile = "/full/path/to/embedded-redis.conf"
}
}
{
redis {
embedded {
config-file = "/full/path/to/embedded-redis.conf"
}
}
}
{
"redis": {
"embedded": {
"config-file": "/full/path/to/embedded-redis.conf"
}
}
}