Skip to content

10. Additional features

Nikita Koksharov edited this page Feb 22, 2024 · 71 revisions

10.1. Operations with Redis nodes

Redisson provides API to manage Redis nodes.

Code example of operations with Redis Cluster setup:

RedisCluster cluster = redisson.getRedisNodes(RedisNodes.CLUSTER);
cluster.pingAll();
Collection<RedisClusterMaster> masters = cluster.getMasters();
Collection<RedisClusterMaster> slaves = cluster.getSlaves();

Code example of operations with Redis Master Slave setup:

RedisMasterSlave masterSlave = redisson.getRedisNodes(RedisNodes.MASTER_SLAVE);
masterSlave.pingAll();
RedisMaster master = masterSlave.getMaster();
Collection<RedisSlave> slaves = masterSlave.getSlaves();

Code example of operations with Redis Sentinel setup:

RedisSentinelMasterSlave sentinelMasterSlave = redisson.getRedisNodes(RedisNodes.SENTINEL_MASTER_SLAVE);
sentinelMasterSlave.pingAll();
RedisMaster master = sentinelMasterSlave.getMaster();
Collection<RedisSlave> slaves = sentinelMasterSlave.getSlaves();
Collection<RedisSentinel> sentinels = sentinelMasterSlave.getSentinels();

Code example of operations with Redis Single setup:

RedisSingle single = redisson.getRedisNodes(RedisNodes.SINGLE);
single.pingAll();
RedisMaster instance = single.getInstance();

10.2. References to Redisson objects

It's possible to use a Redisson object inside another Redisson object in any combination. In this case a special reference object will be used and handled by Redisson. Usage example:

RMap<RSet<RList>, RList<RMap>> map = redisson.getMap("myMap");
RSet<RList> set = redisson.getSet("mySet");
RList<RMap> list = redisson.getList("myList");

map.put(set, list);
// With the help of the special reference object, we can even create a circular
// reference which is impossible to achieve if we were to serialize its content
set.add(list);
list.add(map);

As you may have noticed there is no need to re "save/persist" the map object after its elements have changed. Because it does not contain any value but merely a reference, this makes Redisson objects behaves much more like standard Java objects. In effect, making Redis becomes part of JVM's memory rather than just a simple repository.

One Redis HASH, one Redis SET and one Redis LIST will be created in this example.

10.3. Execution batches of commands

Multiple commands can be sent in a batch using RBatch object in a single network call. Command batches allows to reduce the overall execution time of a group of commands. In Redis this approach called Pipelining. Follow options could be supplied during object creation:

BatchOptions options = BatchOptions.defaults()
// Sets execution mode
//
// ExecutionMode.REDIS_READ_ATOMIC - Store batched invocations in Redis and execute them atomically as a single command
//
// ExecutionMode.REDIS_WRITE_ATOMIC - Store batched invocations in Redis and execute them atomically as a single command
//
// ExecutionMode.IN_MEMORY - Store batched invocations in memory on Redisson side and execute them on Redis. Default mode
//
// ExecutionMode.IN_MEMORY_ATOMIC - Store batched invocations on Redisson side and executes them atomically on Redis as a single command
.executionMode(ExecutionMode.IN_MEMORY)

// Inform Redis not to send reply back to client. This allows to save network traffic for commands with batch with big
.skipResult()

// Synchronize write operations execution across defined amount of Redis slave nodes
//
// sync with 2 slaves with 1 second for timeout
.syncSlaves(2, 1, TimeUnit.SECONDS)

// Response timeout
.responseTimeout(2, TimeUnit.SECONDS)

// Retry interval for each attempt to send Redis commands batch
.retryInterval(2, TimeUnit.SECONDS);

// Attempts amount to re-send Redis commands batch if it wasn't sent due to network delays or other issues
.retryAttempts(4);

Result Batch object contains follow data:

// list of result objects per command in batch
List<?> responses = res.getResponses();
// amount of successfully synchronized slaves during batch execution
int slaves = res.getSyncedSlaves();

Code example for Sync / Async mode:

RBatch batch = redisson.createBatch(BatchOptions.defaults());
batch.getMap("test1").fastPutAsync("1", "2");
batch.getMap("test2").fastPutAsync("2", "3");
batch.getMap("test3").putAsync("2", "5");
RFuture<Long> future = batch.getAtomicLong("counter").incrementAndGetAsync();
batch.getAtomicLong("counter").incrementAndGetAsync();

// result could be acquired through RFuture object returned by batched method
// or 
// through result list by corresponding index
future.whenComplete((res, exception) -> {
    // ...
});

BatchResult res = batch.execute();
// or
Future<BatchResult> resFuture = batch.executeAsync();

List<?> list = res.getResponses();
Long result = list.get(4);

Code example of Reactive interface usage:

RBatchReactive batch = redisson.createBatch(BatchOptions.defaults());
batch.getMap("test1").fastPut("1", "2");
batch.getMap("test2").fastPut("2", "3");
batch.getMap("test3").put("2", "5");
Mono<Long> commandMono = batch.getAtomicLongAsync("counter").incrementAndGet();
batch.getAtomicLongAsync("counter").incrementAndGet();

// result could be acquired through Reactive object returned by batched method
// or 
// through result list by corresponding index
commandMono.doOnNext(res -> {
   // ...
}).subscribe();

Mono<BatchResult> resMono = batch.execute();
resMono.doOnNext(res -> {
   List<?> list = res.getResponses();
   Long result = list.get(4);

   // ...
}).subscribe();

Code example of RxJava3 interface usage:

RBatchRx batch = redisson.createBatch(BatchOptions.defaults());
batch.getMap("test1").fastPut("1", "2");
batch.getMap("test2").fastPut("2", "3");
batch.getMap("test3").put("2", "5");
Single<Long> commandSingle = batch.getAtomicLongAsync("counter").incrementAndGet();
batch.getAtomicLongAsync("counter").incrementAndGet();

// result could be acquired through RxJava3 object returned by batched method
// or 
// through result list by corresponding index
commandSingle.doOnSuccess(res -> {
   // ...
}).subscribe();

Mono<BatchResult> resSingle = batch.execute();
resSingle.doOnSuccess(res -> {
   List<?> list = res.getResponses();
   Long result = list.get(4);

   // ...
}).subscribe();

In cluster environment batch executed in map\reduce way. It aggregates commands for each node and sends them simultaneously, then result got from each node added to common result list.

10.4. Transactions

RMap, RMapCache, RLocalCachedMap, RSet, RSetCache and RBucket Redisson objects may participant in Transaction with ACID properties. It uses locks for write operations and maintains data modification operations list until commit() method is executed. Acquired locks are released after rollback() method execution. Implementation throws org.redisson.transaction.TransactionException in case of any error during commit/rollback execution.

Transaction isolation level is: READ_COMMITTED.

See also Spring Transaction Manager section.
See also XA Transactions section.

Follow Transaction options are available:

TransactionOptions options = TransactionOptions.defaults()
// Synchronization data timeout between Redis master participating in transaction and its slaves.
// Default is 5000 milliseconds.
.syncSlavesTimeout(5, TimeUnit.SECONDS)

// Response timeout
// Default is 3000 milliseconds.
.responseTimeout(3, TimeUnit.SECONDS)

// Defines time interval for each attempt to send transaction if it hasn't been sent already.
// Default is 1500 milliseconds.
.retryInterval(2, TimeUnit.SECONDS)

// Defines attempts amount to send transaction if it hasn't been sent already.
// Default is 3 attempts.
.retryAttempts(3)

// If transaction hasn't committed within <code>timeout</code> it will rollback automatically.
// Default is 5000 milliseconds.
.timeout(5, TimeUnit.SECONDS);

For execution in Redis cluster use {} brackets for collocation data on the same slot otherwise CROSSSLOT error is thrown by Redis.

Code example for Redis cluster:

RMap<String, String> map = transaction.getMap("myMap{user:1}");
map.put("1", "2");
String value = map.get("3");
RSet<String> set = transaction.getSet("mySet{user:1}")
set.add(value);

Code example of Sync / Async exection:

RedissonClient redisson = Redisson.create(config);
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());

RMap<String, String> map = transaction.getMap("myMap");
map.put("1", "2");
String value = map.get("3");
RSet<String> set = transaction.getSet("mySet")
set.add(value);

try {
   transaction.commit();
} catch(TransactionException e) {
   transaction.rollback();
}

// or

RFuture<Void> future = transaction.commitAsync();
future.exceptionally(exception -> {
   transaction.rollbackAsync();
});

Code example of Reactive exection:

RedissonReactiveClient redisson = Redisson.create(config).reactive();
RTransactionReactive transaction = redisson.createTransaction(TransactionOptions.defaults());

RMapReactive<String, String> map = transaction.getMap("myMap");
map.put("1", "2");
Mono<String> mapGet = map.get("3");
RSetReactive<String> set = transaction.getSet("mySet")
set.add(value);

Mono<Void> mono = transaction.commit();
mono.onErrorResume(exception -> {
   return transaction.rollback();
});

Code example of RxJava3 exection:

RedissonRxClient redisson = Redisson.create(config).rxJava();
RTransactionRx transaction = redisson.createTransaction(TransactionOptions.defaults());

RMapRx<String, String> map = transaction.getMap("myMap");
map.put("1", "2");
Maybe<String> mapGet = map.get("3");
RSetRx<String> set = transaction.getSet("mySet")
set.add(value);

Completable res = transaction.commit();
res.onErrorResumeNext(exception -> {
   return transaction.rollback();
});

10.5. XA Transactions

Redisson provides XAResource implementation to participate in JTA transactions. RMap, RMapCache, RLocalCachedMap, RSet, RSetCache and RBucket Redisson objects may participant in Transaction.

Transaction isolation level is: READ_COMMITTED.

See also Transactions section.
See also Spring Transaction Manager section.

This feature is available only in Redisson PRO edition.

For execution in Redis cluster use {} brackets for collocation data on the same slot otherwise CROSSSLOT error is thrown by Redis.

Code example for Redis cluster:

RMap<String, String> map = transaction.getMap("myMap{user:1}");
map.put("1", "2");
String value = map.get("3");
RSet<String> set = transaction.getSet("mySet{user:1}")
set.add(value);

Code example:

// transaction obtained from JTA compatible transaction manager
Transaction globalTransaction = transactionManager.getTransaction();

RXAResource xaResource = redisson.getXAResource();
globalTransaction.enlistResource(xaResource);

RTransaction transaction = xaResource.getTransaction();
RBucket<String> bucket = transaction.getBucket("myBucket");
bucket.set("simple");
RMap<String, String> map = transaction.getMap("myMap");
map.put("myKey", "myValue");
        
transactionManager.commit();

10.6. Scripting

Redisson provides RScript object to execute Lua script. It has atomicity property and used to process data on Redis side. Script could be executed in two modes:

  • Mode.READ_ONLY scripts are executed as read operation
  • Mode.READ_WRITE scripts are executed as write operation

One of the follow types returned as a script result object:

  • ReturnType.BOOLEAN - Boolean type.
  • ReturnType.INTEGER - Integer type.
  • ReturnType.MULTI - List type of user defined type.
  • ReturnType.STATUS - Lua String type.
  • ReturnType.VALUE - User defined type.
  • ReturnType.MAPVALUE - Map value type.
  • ReturnType.MAPVALUELIST - List of Map value type.

Code example:

RBucket<String> bucket = redisson.getBucket("foo");
bucket.set("bar");

RScript script = redisson.getScript(StringCodec.INSTANCE);

String r = script.eval(Mode.READ_ONLY,
                       "return redis.call('get', 'foo')", 
                       RScript.ReturnType.VALUE);

// execute the same script stored in Redis lua script cache

// load lua script into Redis cache to all redis master instances
String res = script.scriptLoad("return redis.call('get', 'foo')");
// res == 282297a0228f48cd3fc6a55de6316f31422f5d17

// call lua script by sha digest
Future<Object> r1 = redisson.getScript().evalShaAsync(Mode.READ_ONLY,
   "282297a0228f48cd3fc6a55de6316f31422f5d17",
   RScript.ReturnType.VALUE, Collections.emptyList());

10.7. Functions

Redisson provides RFunction object to execute Redis Functions. It has atomicity property and used to process data on Redis side. Function can be executed in two modes:

  • Mode.READ executes function as read operation
  • Mode.WRITE executes function as write operation

One of the follow types returned as a script result object:

  • ReturnType.BOOLEAN - Boolean type
  • ReturnType.LONG - Long type
  • ReturnType.LIST - List type of user defined type.
  • ReturnType.STRING - plain String type
  • ReturnType.VALUE - user defined type
  • ReturnType.MAPVALUE - Map Value type. Codec.getMapValueDecoder() and Codec.getMapValueEncoder() methods are used for data deserialization or serialization
  • ReturnType.MAPVALUELIST - List type, which consists of objects of Map Value type. Codec.getMapValueDecoder() and Codec.getMapValueEncoder() methods are used for data deserialization or serialization

Code example:

RFunction f = redisson.getFunction();

// load function
f.load("lib", "redis.register_function('myfun', function(keys, args) return args[1] end)");

// execute function
String value = f.call(RFunction.Mode.READ, "myfun", RFunction.ReturnType.STRING, Collections.emptyList(), "test");

Code example of Async interface usage:

RFunction f = redisson.getFunction();

// load function
RFuture<Void> loadFuture = f.loadAsync("lib", "redis.register_function('myfun', function(keys, args) return args[1] end)");

// execute function
RFuture<String> valueFuture = f.callAsync(RFunction.Mode.READ, "myfun", RFunction.ReturnType.STRING, Collections.emptyList(), "test");

Code example of Reactive interface usage:

RedissonReactiveClient redisson = redissonClient.reactive();
RFunctionReactive f = redisson.getFunction();

// load function
Mono<Void> loadMono = f.load("lib", "redis.register_function('myfun', function(keys, args) return args[1] end)");

// execute function
Mono<String> valueMono = f.callAsync(RFunction.Mode.READ, "myfun", RFunction.ReturnType.STRING, Collections.emptyList(), "test");

Code example of RxJava3 interface usage:

RedissonRxClient redisson = redissonClient.rxJava();
RFunctionRx f = redisson.getFunction();

// load function
Completable loadMono = f.load("lib", "redis.register_function('myfun', function(keys, args) return args[1] end)");

// execute function
Maybe<String> valueMono = f.callAsync(RFunction.Mode.READ, "myfun", RFunction.ReturnType.STRING, Collections.emptyList(), "test");

10.8. Low level Redis client

Redisson uses high-perfomance async and lock-free Redis client for Java. It supports both async and sync modes. The most popular use case is to execute a command not supported by Redisson yet. Please make sure that required command is not supported already with Redis command mapping list. org.redisson.client.protocol.RedisCommands - contains all available commands. Code example:

// Use shared EventLoopGroup only if multiple clients are used
EventLoopGroup group = new NioEventLoopGroup();

RedisClientConfig config = new RedisClientConfig();
config.setAddress("redis://localhost:6379") // or rediss:// for ssl connection
      .setPassword("myPassword")
      .setDatabase(0)
      .setClientName("myClient")
      .setGroup(group);

RedisClient client = RedisClient.create(config);
RedisConnection conn = client.connect();
//or
CompletionStage<RedisConnection> connFuture = client.connectAsync();

// execute SET command in sync way
conn.sync(StringCodec.INSTANCE, RedisCommands.SET, "test", 0);
// execute GET command in async way
conn.async(StringCodec.INSTANCE, RedisCommands.GET, "test");

conn.close()
// or
conn.closeAsync()

client.shutdown();
// or
client.shutdownAsync();

10.9. Client tracking listener

Client tracking listener is invoked when an invalidation message is received if the data previously requested has been changed. Next listener invocation will be made only if a new data request has been made and another change has occurred since then.

Available for RBucket, RStream, RSet, RMap, RScoredSortedSet, RList, RQueue, RDeque, RBlockingQueue, RBlockingDeque, RDelayedQueue, RRingBuffer objects.

Requires protocol setting value set to RESP3.

Code usage example.

RBucket<String> b = redisson.getBucket("test");
int listenerId = b.addListener(new TrackingListener() {
     @Override
     public void onChange(String name) {
         // ...
     }
});

// data requested and change is now tracked
b.get();

// ...

// stop tracking
b.removeListener(listenerId);

Flush listener

Flush listener is executed on flushall/flushdb commands execution.

redisson.getKeys().addListener(new FlushListener() {
    @Override
    public void onFlush(InetSocketAddress address) {
       // ...
    }
});