Redisson provides Java Remote Services to execute remote procedure call using Redis or Valkey. Remote interface could have any type of method parameters and result object. Redis or Valkey is used to store method request and corresponding execution result.
The RemoteService provides two types of [RRemoteService](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RRemoteService.html) instances:
* __Server side instance__ - executes remote method (worker instance).
SomeServiceInterface service = remoteService.get(SomeServiceInterface.class);
String result = service.doSomeStuff(1L, "secondParam", new AnyParam());
```
Client and server side instances shall be using the same remote interface and backed by redisson instances created using the same server connection configuration. Client and server side instances could be run in same JVM. There are no limits to the amount of client and/or server instances.
(Note: While redisson does not enforce any limits, [limitations from Redis or Valkey still apply](https://redis.io/topics/faq#what-is-the-maximum-number-of-keys-a-single-redis-instance-can-hold-and-what-the-max-number-of-elements-in-a-hash-list-set-sorted-set).)
Remote invocations executes in __parallel mode__ if __1+__ workers are available.
The total number of parallel executors is calculated as such:
`T` = `R` * `N`
`T` - total available parallel executors
`R` - Redisson server side instance amount
`N` - executors amount defined during service registration
Commands exceeding this number will be queued for the next available executor.
Remote invocations executes in __sequential mode__ if only __1__ workers are available. Only one command can be handled concurrently in this case and the rest of commands will be queued.
RemoteService creates two queues per invocation. One queue for request (being listened by server side instance) and another one is for ack-response and result-response (being listened by client side instance). Ack-response used to determine if method executor has got a request. If it doesn't during ack timeout then `RemoteServiceAckTimeoutException` will be thrown.
Below is depicted a message flow for each remote invocation.
RemoteService options for each remote invocation could be defined via [RemoteInvocationOptions](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RemoteInvocationOptions.html) object. Such options allow to change timeouts and skip ack-response and/or result-response. Examples:
```java
// 1 second ack timeout and 30 seconds execution timeout
YourService service = remoteService.get(YourService.class, options);
```
### Asynchronous, Reactive and RxJava3 calls
Remote method could be executed using Async, Reactive and RxJava3 Api.
**Asynchronous Remote interface**. Interface should be annotated with [@RRemoteAsync](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/annotation/RRemoteAsync.html). Method signatures match with the methods in remote interface and return [RFuture](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RFuture.html) object.
**Reactive Remote interface**. Interface should be annotated with [@RRemoteReactive](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/annotation/RRemoteReactive.html). Method signatures match with the methods in remote interface and return `reactor.core.publisher.Mono` object.
**RxJava3 Remote interface**. Interface should be annotated with [@RRemoteRx](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/annotation/RRemoteRx.html). Method signatures match with the methods in remote interface and return one of the following object: `io.reactivex.Completable`, `io.reactivex.Single`, `io.reactivex.Maybe`.
It's not necessary to list all methods, only those which are needed. Below is an example of Remote Service interface:
```java
public interface RemoteInterface {
Long someMethod1(Long param1, String param2);
void someMethod2(MyObject param);
MyObject someMethod3();
}
```
**Asynchronous Remote interface** and method call example:
Single<Long> single = asyncService.myBusyMethod(1L, "someparam");
Disposable disp = single.subscribe();
// cancel invocation
disp.dispose();
```
## Live Object service
### Introduction
A **Live Object** can be understood as an enhanced version of standard Java object, of which an instance reference can be shared not only between threads in a single JVM, but can also be shared between different JVMs across different machines.Wikipedia discribes it as:
> Live distributed object (also abbreviated as live object) refers to a running instance of a distributed multi-party (or peer-to-peer) protocol, viewed from the object-oriented perspective, as an entity that has a distinct identity, may encapsulate internal state and threads of execution, and that exhibits a well-defined externally visible behavior.
Redisson Live Object (RLO) realised this idea by mapping all the fields inside a Java class to a HASH through a runtime-constructed proxy class. All the getters/setters methods of each field are translated to hget/hset commands operated on the HASH, making it accessable to/from any clients connected to the same Redis or Valkey server. Getters/setters/constructors can't be generated by byte-code tools like Lombok. Additional methods should use getters and not fields. As we all know, the field values of an object represent its state; having them stored in a remote repository, redis, makes it a distributed object. This object is a Redisson Live Object.
By using RLO, sharing an object between applications and/or servers is the same as sharing one in a standalone application. This removes the need for serialization and deserialization, and at the same time reduces the complexity of the programming model: Changes made to one field is (almost^) immediately accessable to other processes, applications and servers.
Since the Redis or Valkey server is a single-threaded application, all field access to the live object is automatically executed in atomic fashion: a value will not be changed when you are reading it.
With RLO, you can treat the Redis or Valkey server as a shared Heap space for all connected JVMs.
### Usage
Redisson provides different [annotations](#annotations) for Live Object. `@RId` and `@REntity` annotation are required to create and use Live Object.
"parent" field has a link to another instances of Live Object of the same type, but the type could be different. Redisson stores this link into Redis or Valkey as a reference object and not the whole object state, so you continue to work with reference object as Live Object.
Field types in the RLO can be almost anything, from Java util classes to collection/map types and of course your own custom objects, as long as it can be encoded and decoded by a supplied codec. More details about the codec can be found in the [Advanced Usage](#advanced-usage) section.
In order to keep RLOs behaving as closely to standard Java objects as possible, Redisson automatically converts the following standard Java field types to its counter types supported by Redisson `RObject`.
Standard Java Class | Converted Redisson Class
------------ | ------------
SortedSet.class | RedissonSortedSet.class
Set.class | RedissonSet.class
ConcurrentMap.class | RedissonMap.class
Map.class | RedissonMap.class
BlockingDeque.class | RedissonBlockingDeque.class
Deque.class | RedissonDeque.class
BlockingQueue.class | RedissonBlockingQueue.class
Queue.class | RedissonQueue.class
List.class | RedissonList.class
The conversion prefers the one nearer to the top of the table if a field type matches more than one entries. i.e. `LinkedList` implements `Deque`, `List`, `Queue`, it will be converted to a `RedissonDeque` because of this.
Instances of these Redisson classes retains their states/values/entries in Redis or Valkey too, changes to them are directly reflected into database without keeping values in local VM.
Redisson provides comprehensive search engine for RLO objects. To make a property participate in search it should be annotated with `@RIndex` annotation.
Use [Redisson PRO](https://redisson.pro) for **ultra-fast search engine**, **low JVM memory consumption during search process** and **search index partitiong in cluster**.
Usage example:
```java
@REntity
public class MyObject {
@RId
private String id;
@RIndex
private String field1;
@RIndex
private Integer field2;
@RIndex
private Long field3;
}
```
Different type of search conditions are available:
_This feature is available only in [Redisson PRO](https://redisson.pro) edition._
RLO objects can be cached in local cache on the Redisson side.
**local cache** - so called near cache used to speed up read operations and avoid network roundtrips. It caches JSON Store entries on Redisson side and executes read operations up to **45x faster** in comparison with regular implementation. Local cached instances with the same name are connected to the same pub/sub channel. This channel is used for exchanging of update/invalidate events between all instances. Local cache store doesn't use `hashCode()`/`equals()` methods of key object, instead it uses hash of serialized state.
To make an RLO entity stored in local cache it should be annotated with `@RCache` annotation.
`@RCache` annotation settings:
*`storeCacheMiss` - defines whether to store a cache miss into the local cache. Default value is `false`.
*`storeMode` - defines store mode of cache data. Default value is `LOCALCACHE_REDIS`. Follow options are available:
*`LOCALCACHE` - store data in local cache only and use Redis or Valkey only for data update/invalidation.
*`LOCALCACHE_REDIS` - store data in both Redis or Valkey and local cache.
*`cacheProvider` - defines Cache provider used as local cache store. Default value is `REDISSON`. Follow options are available:
*`REDISSON` - uses Redisson own implementation
*`CAFFEINE` - uses Caffeine implementation
*`evictionPolicy` - defines local cache eviction policy. Default value is `NONE`. Follow options are available:
*`LFU` - counts how often an item was requested. Those that are used least often are discarded first.
*`LRU` - discards the least recently used items first
*`SOFT` - uses soft references, entries are removed by GC
*`WEAK` - uses weak references, entries are removed by GC
*`NONE` - no eviction
*`cacheSize` - local cache size. If cache size is `0` then local cache is unbounded. If size is `-1` then local cache is always empty and doesn't store data. Default value is `0`.
*`reconnectionStrategy` - defines strategy for load missed local cache updates after connection failure. Default value is `NONE`. Follow options are available:
*`CLEAR` - clear local cache if map instance has been disconnected for a while.
*`NONE` - no reconnection handling
*`syncStrategy` - defines local cache synchronization strategy. Default value is `INVALIDATE`. Follow options are available:
*`INVALIDATE` - Default. Invalidate cache entry across all RLocalCachedJsonStore instances on map entry change
*`UPDATE` - Insert/update cache entry across all RLocalCachedJsonStore instances on map entry change
*`NONE` - No synchronizations on map changes
*`timeToLive` - defines time to live for each entry in local cache
*`maxIdle` - defines max idle time for each entry in local cache
*`useTopicPattern` - defines whether to use a global topic pattern listener that applies to all local cache instances belonging to the same Redisson instance. Default value is `true`.
As described before, RLO classes are proxy classes which can be fabricated when needed and then get cached in a `RedissonClient` instance against its original class. This process can be a bit slow and it is recommended to pre-register all the Redisson Live Object classes via `RedissonLiveObjectService` for any kind of delay-sensitive applications. The service can also be used to unregister a class if it is no longer needed. And of course it can be used to check if the class has already been registered.
```java
RLiveObjectService service = redisson.getLiveObjectService();
Applied to a class. The behaviour of each type of RLO can be customised through properties of the `@REntity` annotation. You can specify each of those properties to gain fine control over its behaviour:
*`namingScheme` - You can specify a naming scheme which tells Redisson how to assign key names for each instance of this class. It is used to create a reference to an existing Redisson Live Object and materialising a new one in redis. It defaults to use Redisson provided `DefaultNamingScheme`.
*`codec` - You can tell Redisson which `Codec` class you want to use for the RLO. Redisson will use an instance pool to locate the instance based on the class type. It defaults to `JsonJacksonCodec` provided by Redisson.
*`fieldTransformation` - You can also specify a field transformation mode for the RLO. As mentioned before, in order to keep everything as close to standard Java as possible, Redisson will automatically transform fields with commonly-used Java util classes to Redisson compatible classes. This uses `ANNOTATION_BASED` as the default value. You can set it to `IMPLEMENTATION_BASED` which will skip the transformation.
**@RId**
Applied to a field. Defines `primary key` field of this class. The value of this field is used to create a reference to existing RLO. The field with this annotation is the only field that has its value also kept in the local VM. You can only have one `RId` annotation per class.
You can supply a `generator` strategy to the `@RId` annotation if you want the value of this field to be programatically generated. The default generator is `null`.
**@RIndex**
Applied to a field. Specifies that the field is used in search index. Allows to execute search query based on that field through `RLiveObjectService.find` method.
**@RObjectField**
Applied to a field. Allows to specify `namingScheme` and/or `codec` different from what is specified in `@REntity`.
**@RCascade**
Applied to a field. Specifies that the defined cascade types are applied to the object/objects contained in Live Object field.
Different cascade types are available:
*`RCascadeType.ALL` - Includes all cascade types
*`RCascadeType.PERSIST` - Cascade persist operation during `RLiveObjectService.persist()` method invocation
*`RCascadeType.DETACH` - Cascade detach operation during `RLiveObjectService.detach()` method invocation
*`RCascadeType.MERGE` - Cascade merge operation during `RLiveObjectService.merge()` method invocation
*`RCascadeType.DELETE` - Cascade delete operation during `RLiveObjectService.delete()` method invocation.
## Executor service
### Overview
Redis or Valkey based [RExecutorService](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RExecutorService.html) object implements of [ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) interface to run [Callable](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html), [Runnable](https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) and Lambda tasks. Task and result objects are serialized using defined codec and stored in two request/response Redis or Valkey queues. Redisson instance and task id can be injected in task object. Task id has size 128-bits and globally unique. This allows to process Redis or Valkey data and execute distributed computations in fast and efficient way.
### Workers
Worker runs task submitted through [RExecutorService](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RExecutorService.html) interface. Worker should be registered with `WorkerOptions` settings object. Each worker polls a task from a head of Redis or Valkey queue in order it requested it. Polled tasks are executed with ExecutorService. Global default ExecutorService from Redisson configuration is used if ExecutorService in `WorkerOptions` isn't defined.
Consider to use [Redisson node](./12.-Standalone-node) if you need standalone jar which register and executes workers.
WorkerOptions exposes follow settings:
```java
WorkerOptions options = WorkerOptions.defaults()
// Defines workers amount used to execute tasks.
// Default is 1.
.workers(2)
// Defines Spring BeanFactory instance to execute tasks
// with Spring's '@Autowired', '@Value' or JSR-330's '@Inject' annotation.
.beanFactory(beanFactory)
// Defines custom ExecutorService to execute tasks
// Config.executor is used by default.
.executorService()
// Defines task timeout since task execution start moment
.taskTimeout(60, TimeUnit.SECONDS)
// add listener which is invoked on task successed event
.addListener(new TaskSuccessListener() {
public void onSucceeded(String taskId, T result) {
// ...
}
})
// add listener which is invoked on task failed event
.addListener(new TaskFailureListener() {
public void onFailed(String taskId, Throwable exception) {
// ...
}
})
// add listener which is invoked on task started event
.addListener(new TaskStartedListener() {
public void onStarted(String taskId) {
// ...
}
})
// add listener which is invoked on task finished event
.addListener(new TaskFinishedListener() {
public void onFinished(String taskId) {
// ...
}
});
```
Code example of worker registration with options defined above:
Redisson node doesn't require presence of task classes in classpath. Task classes are loaded automatically by Redisson node ClassLoader. Thus each new task class doesn't require restart of Redisson node.
Example with `Callable` task:
```java
public class CallableTask implements Callable<Long> {
Each Redisson node has ready to use RedissonClient which could be injected using `@RInject` annotation.
### Tasks with Spring beans
Redisson allows to inject not only RedissonClient and task id using `@RInject` annotation but also supports Spring's `@Autowire`, `@Value` and JSR-330's `@Inject` annotations. Injected Spring Bean service interface should implement Serializable.
Redisson uses Spring's BeanFactory object for injection. It should be defined in Redisson Node [configuration](../standalone-node.md#settings).
Example with `Callable` task:
```java
public class CallableTask implements Callable<Integer> {
It's easy to cancel any submitted task via `RFuture.cancel()` or `RExecutorService.cancelTask()` method. To handle case then task execution is already in progress you need to check Thread status for interruption with `Thread.currentThread().isInterrupted()` invocation:
```java
public class CallableTask implements Callable<Long> {
Redis or Valkey based [RScheduledExecutorService](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RScheduledExecutorService.html) implements [ScheduledExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html) interface to schedule [Callable](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html), [Runnable](https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) and Lambda tasks. Scheduled task is a job which needs to be execute in the future at a particular time one or more times. Task and result objects are serialized and stored in request/response Redis or Valkey queues. Redisson instance and task id can be injected into task object. Task id has size 128-bits and globally unique. This allows to process Redis or Valkey data and execute distributed computations in fast and efficient way.
### Workers
Worker runs task submitted through [RScheduledExecutorService](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RScheduledExecutorService.html) interface. Worker should be registered with `WorkerOptions` settings object. Each worker polls a task from head of Redis or Valkey queue in order it requested it. Polled tasks are executed with ExecutorService. Global default ExecutorService from Redisson configuration is used if ExecutorService in `WorkerOptions` isn't defined.
Consider to use [Redisson node](./12.-Standalone-node) if you need standalone jar which register and executes workers.
WorkerOptions exposes follow settings:
```java
WorkerOptions options = WorkerOptions.defaults()
// Defines workers amount used to execute tasks.
// Default is 1.
.workers(2)
// Defines Spring BeanFactory instance to execute tasks
// with Spring's '@Autowired', '@Value' or JSR-330's '@Inject' annotation.
.beanFactory(beanFactory)
// Defines custom ExecutorService to execute tasks
// Config.executor is used by default.
.executorService()
// Defines task timeout since task execution start moment
.taskTimeout(60, TimeUnit.SECONDS)
// add listener which is invoked on task successed event
.addListener(new TaskSuccessListener() {
public void onSucceeded(String taskId, T result) {
// ...
}
})
// add listener which is invoked on task failed event
.addListener(new TaskFailureListener() {
public void onFailed(String taskId, Throwable exception) {
// ...
}
})
// add listener which is invoked on task started event
.addListener(new TaskStartedListener() {
public void onStarted(String taskId) {
// ...
}
})
// add listener which is invoked on task finished event
.addListener(new TaskFinishedListener() {
public void onFinished(String taskId) {
// ...
}
});
```
Code example of worker registration with options defined above:
Redisson node doesn't require presence of task classes in classpath. Task classes are loaded automatically by Redisson node ClassLoader. Thus each new task class doesn't require restart of Redisson node.
Example with `Callable` task:
```java
public class CallableTask implements Callable<Long> {
Redisson allows to inject not only RedissonClient using `@RInject` annotation but also supports Spring's `@Autowire`, `@Value` and JSR-330's `@Inject` annotations. Injected Spring Bean service interface should implement Serializable.
Redisson uses Spring's BeanFactory object for injection. It should be defined in Redisson Node [configuration](https://github.com/redisson/redisson/wiki/12.-Standalone-node#beanfactory).
Tasks scheduler service allows to define more complex schedule with cron expressions. Which fully compatible with [Quartz cron format](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html).
Scheduled executor service provides two ways to cancel scheduled task via `RScheduledFuture.cancel()` or `RScheduledExecutorService.cancelTask()` method. To handle case then task execution is already in progress you need to check Thread status for interruption with `Thread.currentThread().isInterrupted()` invocation:
```java
public class RunnableTask implements Callable<Long> {
Redisson provides MapReduce programming model to process large amount of data stored in Redis. Based on ideas from different implementations and [Google's MapReduce publication](https://research.google.com/archive/mapreduce.html). Supported by different objects: `RMap`, `RMapCache`, `RLocalCachedMap`, `RClusteredMap`, `RClusteredMapCache`, `RClusteredLocalCachedMap`, `RClusteredSet`, `RClusteredSetCache`, `RSet`, `RSetCache`, `RList`, `RSortedSet`, `RScoredSortedSet`, `RQueue`, `RBlockingQueue`, `RDeque`, `RBlockingDeque`, `RPriorityQueue` and `RPriorityDeque`.
MapReduce model based on few objects: `RMapper`/`RCollectionMapper`, `RReducer` and `RCollator`.
All tasks for _map_ and _reduce_ phases are executed across [Redisson Nodes](https://github.com/redisson/redisson/wiki/12.-Standalone-node).
For `RLocalCachedMap`, `RClusteredMap`, `RClusteredMapCache`, `RClusteredLocalCachedMap`, `RClusteredSet`, `RClusteredSetCache` objects _Map phase_ is splitted to multiple sub-tasks and executes in parallel. For other objects _Map phase_ is executed in single sub-task. _Reduce phase_ is always splitted to multiple sub-tasks and executes in parallel. Sub-tasks amount equals to total amount of registered MapReduce workers.
**1. `RMapper` applied only for Map objects and transforms each Map's entry to intermediate key/value pair.**
```java
public interface RMapper<KIn,VIn,KOut,VOut> extends Serializable {
MapReduce is available for collection type objects: `RSet`, `RSetCache`, `RList`, `RSortedSet`, `RScoredSortedSet`, `RQueue`, `RBlockingQueue`, `RDeque`, `RBlockingDeque`, `RPriorityQueue` and `RPriorityDeque`.
Below is word count example using MapReduce:
```java
public class WordMapper implements RCollectionMapper<String,String,Integer> {
@Override
public void map(String value, RCollector<String,Integer> collector) {
String[] words = value.split("[^a-zA-Z]");
for (String word : words) {
collector.emit(word, 1);
}
}
}
```
```java
public class WordReducer implements RReducer<String,Integer> {
@Override
public Integer reduce(String reducedKey, Iterator<Integer> iter) {
int sum = 0;
while (iter.hasNext()) {
Integer i = (Integer) iter.next();
sum += i;
}
return sum;
}
}
```
```java
public class WordCollator implements RCollator<String,Integer,Integer> {
@Override
public Integer collate(Map<String,Integer> resultMap) {
int result = 0;
for (Integer count : resultMap.values()) {
result += count;
}
return result;
}
}
```
```java
RList<String> list = redisson.getList("myList");
list.add("Alice was beginning to get very tired");
list.add("of sitting by her sister on the bank and");
list.add("of having nothing to do once or twice she");
list.add("had peeped into the book her sister was reading");
list.add("but it had no pictures or conversations in it");
list.add("and what is the use of a book");
list.add("thought Alice without pictures or conversation");
Redisson provides RediSearch integration. Supports field indexing of [RMap](Distributed-collections.md/#map), [RJSONStore](Distributed-collections.md/#json-store) and [RJSONBucket](Distributed-objects.md/#json-object-holder) objects, query execution and aggregation.
It has [Async](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RSearchAsync.html), [Reactive](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RSearchReactive.html) and [RxJava3](https://www.javadoc.io/doc/org.redisson/redisson/latest/org/redisson/api/RSearchRx.html) interfaces.
### Query execution
Code example for RMap object:
```java
RMap<String,SimpleObject> m = redisson.getMap("doc:1", new CompositeCodec(StringCodec.INSTANCE, redisson.getConfig().getCodec()));
m.put("v1", new SimpleObject("name1"));
m.put("v2", new SimpleObject("name2"));
RMap<String,SimpleObject> m2 = redisson.getMap("doc:2", new CompositeCodec(StringCodec.INSTANCE, redisson.getConfig().getCodec()));
m2.put("v1", new SimpleObject("name3"));
m2.put("v2", new SimpleObject("name4"));
RSearch s = redisson.getSearch();
// creates an index for documents with prefix "doc"
s.createIndex("idx", IndexOptions.defaults()
.on(IndexType.HASH)
.prefix(Arrays.asList("doc:")),
FieldIndex.text("v1"),
FieldIndex.text("v2"));
SearchResult r = s.search("idx", "*", QueryOptions.defaults()
.returnAttributes(new ReturnAttribute("v1"), new ReturnAttribute("v2")));
SearchResult r = s.search("idx", "*", QueryOptions.defaults()
.filters(QueryFilter.geo("field")
.from(1, 1)
.radius(10, GeoUnit.FEET)));
```
Code example for JSON object:
```java
public class TestClass {
private List<Integer> arr;
private String value;
public TestClass() {
}
public TestClass(List<Integer> arr, String value) {
this.arr = arr;
this.value = value;
}
public List<Integer> getArr() {
return arr;
}
public TestClass setArr(List<Integer> arr) {
this.arr = arr;
return this;
}
public String getValue() {
return value;
}
public TestClass setValue(String value) {
this.value = value;
return this;
}
}
RJsonBucket<TestClass> b = redisson.getJsonBucket("doc:1", new JacksonCodec<>(TestClass.class));