
Redis, short for Remote Dictionary Server, is a fast, open-source, in-memory key-value data store.
Redisson is an easy redis client for Java. In this post we’ll explore how to use this with Kotlin.
Redisson is an in-memory data grid that offers distributed Java objects and services backed by Redis. It is a high-performance async and lock-free Java Redis client and Netty framework.
In this post we’ll also see how to set up Redisson and explore some of Redisson’s features.
Sharing of domain objects and services across applications and servers is enabled with a distributed in-memory data model that supports different modes:
Gradle Dependencies
dependencies {
implementation("org.redisson:redisson:3.21.3")
}
https://github.com/lotharschulz/redis_kotlin/blob/main/app/build.gradle.kts#L11
Add the redisson dependency to your Gradle project (e.g. build.gradle.kts file). Refer to the quick start documentation in case you use maven or sbt.
Redis Installation
The easiest way to run Redis is via docker:
docker run -d --name my-redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
https://github.com/lotharschulz/redis_kotlin/blob/main/start-docker-run-code-stop-docker.sh#L25
or install Redis on your favorite operating system.
Redis Config
You can pass various configurations to the create function, in my case I use localhost because the docker image runs local.
val config = Config()
config.useSingleServer().address = "redis://localhost:6379"
Redisson.create(config)
based on
./start-docker-run-code-stop-docker.sh
https://github.com/lotharschulz/redis_kotlin/blob/main/start-docker-run-code-stop-docker.sh#L1-L31
We can find more configuration details here.
Cache
A cache is a component that stores data in a read optimized store so that future read requests can be served faster. One of the popular Redis use cases is to use it as a cache.
val expirationInMillis = 500
val offset = expirationInMillis + 500
val cache: RMapCache<String, String> = redissonClient.getMapCache(mapCacheName)
cache.put(key, value)
cache.expire(Duration.ofMillis(expirationInMillis))
// wait some time e.g. Thread.sleep(offset)
println("cache.size: ${cache.size} (0 means cache is expired)")
cache.destroy();
Operation Modes – sync/async/reactive/rxjava with AtomicLong
Redisson instances are fully thread-safe. Objects with synchronous/asynchronous methods could be reached via the RedissonClient interface. Reactive and RxJava3 methods are available through RedissonReactiveClient and RedissonRxClient interfaces respectively.
https://github.com/redisson/redisson/wiki/3.-operations-execution
An example of a synchronous function with atomicLong is:
val myAtomicLong: RAtomicLong = redisson.getAtomicLong("myAtomicLong")
myAtomicLong.set(newValue)
myAtomicLong.incrementAndGet()
myAtomicLong.get()
myAtomicLong.unlink()
The asynchronous counterpart would be:
val myAtomicLong: RAtomicLong = redisson.getAtomicLong("myAtomicLongAsync")
val setFuture: RFuture<Void> = myAtomicLong.setAsync(newValue)
try {
val setResult = setFuture.toCompletableFuture().get()
} catch (e: Exception) {
println("after async set an exception happened: ${e.localizedMessage}")
}
val igFuture: RFuture<Long> = myAtomicLong.incrementAndGetAsync()
try {
val result = igFuture.toCompletableFuture().get()
} catch (e: Exception) {
println("after async set an exception happened: ${e.localizedMessage}")
}
val igFuture2: RFuture<Long> = myAtomicLong.getAsync()
try {
val result = igFuture2.toCompletableFuture().get()
} catch (e: Exception) {
println("after async get an exception happened: ${e.localizedMessage}")
}
myAtomicLong.unlink() // clean up, happens async
The same approach following the reactive approach:
val redissonReactive: RedissonReactiveClient = redisson.reactive()
val myAtomicLong: RAtomicLongReactive = redissonReactive.getAtomicLong("myAtomicLongReactive")
val setMono: Mono<Void> = myAtomicLong.set(newValue)
setMono.doOnNext {i -> println("setMono next i: $i")}
.doOnSuccess{i -> println("setMono success i: $i")}
.doOnError{e -> println("setMono error i: ${e.localizedMessage}")}
.block()
val getMono: Mono<Long> = myAtomicLong.get()
getMono.doOnNext {i -> println("getMono next i: $i")}
val igMono: Mono<Long> = myAtomicLong.incrementAndGet()
igMono.doOnNext {i -> println("igMono next i: $i")}
.doOnSuccess{i -> println("igMono success i: $i")}
.doOnError{e -> println("igMono error i: ${e.localizedMessage}")}
.block()
Last but not least a code example based on RxJava:
val redissonReactive: RedissonRxClient = redisson.rxJava()
val atomicLong: RAtomicLongRx = redissonReactive.getAtomicLong("myAtomicLongReactiveRX")
val setMono: Completable = atomicLong.set(newValue)
setMono.doOnError { e -> println("setMono error: ${e.localizedMessage}") }
val getMono = atomicLong.get()
getMono.subscribe(
{ i -> println("getMono: $i") },
{ e -> println("getMono error: ${e.localizedMessage}") }
)
val igMono: Single<Long> = atomicLong.incrementAndGet()
igMono.subscribe(
{ i -> println("igMono: $i") },
{ e -> println("igMono error: ${e.localizedMessage}") }
)
Objects
With the RBucket class, an object can hold any type of object. The maximum size is 512 MB.
Atomic operations like compareAndSet and compareAndSet can be performed.
val bucket: RBucket<Book> = redissonClient.getBucket("book")
val book1 = Book(bookPages, bookChapter, bookAuthor)
bucket.set(book1)
val getBook1: Book = bucket.get()
val book2 = Book(bookPages + 2, bookChapter + 2, "$bookAuthor of book 2")
val setIfAbsent: Boolean = bucket.setIfAbsent(book2)
val getBook2: Book = bucket.get()
val book3 = Book(bookPages + 3, bookChapter + 3, "$bookAuthor of book 3")
val book4 = Book(bookPages + 4, bookChapter + 4, "$bookAuthor of book 4")
val compareAndSet: Boolean = bucket.compareAndSet(book3, book4)
val getBook3or4: Book = bucket.get()
val book5 = Book(bookPages + 5, bookChapter + 5, "$bookAuthor of book 5")
val currentBook: Book = bucket.getAndSet(book5)
val getBook5: Book = bucket.get()
Keys
Redis keys identify Redisson objects. The RKeys interface manages keys.
val rKeys: RKeys = redissonClient.keys
redissonClient.getBucket<Int>(keyName1).set(1)
redissonClient.getBucket<Int>(keyName2).set(1)
val keys = rKeys.keys
keys.sorted().forEach { println(it) }
Topic
Topics support publish and subscribe . Subscribe works with listeners and publishing messages can be done with publish.
val rTopic: RTopic = redissonClient.getTopic("myTopic")
val listenerId = rTopic.addListener(String::class.java) { channel, msg ->
println("channel: $channel, Message: $msg")
}
rTopic.publish(message)
rTopic.removeListener(listenerId)
Collections
Collections are handled similar to objects. Maps is a popular collection item:
val map: RMap<String, String> = redissonClient.getMap("myMap")
val prevString: String? = map.put(key, value)
val putIfAbsentStringValue: String = "42"
val putIfAbsentString: String? = map.putIfAbsent(key, putIfAbsentStringValue)
val removedString: String? = map.remove(key)
Set
Redisson based Set object implements the java.util.Set interface
val book = Book(pages, chapter, author)
val rSet: RSet<Book> = redissonClient.getSet("book-set")
rSet.add(book)
rSet.readAll()
rSet.remove(book)
List
Similar to set, redisson based distributed List implements the java.util.List interface. It keeps elements in insertion order.
val book = Book(pages, chapter, author)
val ledgerList: RList<Book> = redissonClient.getList("myList")
ledgerList.add(book)
val listData = ledgerList.get(0)
ledgerList.remove(book)
Scripting
We can script using Lua in Redisson.
val bucketName = "myScriptingBucket"
redissonClient.getBucket<String>(bucketName).set(value)
val result: String = redissonClient.script.eval(
RScript.Mode.READ_ONLY,
"return redis.call('get', '$bucketName')", RScript.ReturnType.VALUE
)
Pipeline
Redisson supports multiple commands: pipelining – multiple operations can be batched as a single atomic operation. A RBatch instance allows adding commands to its maps before those are executed.
val batch: RBatch = redissonClient.createBatch()
val rf1: RFuture<Boolean>? = batch.getMap<Int, Int>("pipeline").fastPutAsync(value1, value2)
val rf2: RFuture<Int>? = batch.getMap<Int, Int>("pipeline").putAsync(value3, value4)
batch.execute()
rf1?.toCompletableFuture()?.thenApply {
println("rf1 fastPutAsync result: $it")
}
rf2?.toCompletableFuture()?.thenApply {
println("rf2 putAsync result: $it")
}
MultiLock
Redisson’s RedissonMultiLock groups multiple independent locks (RLocks) and manages them as a single lock.
val lock1: RLock = redissonClient.getLock("l1")
val lock2: RLock = redissonClient.getLock("l2")
val lock3: RLock = redissonClient.getLock("l3")
val lock4: RLock = redissonClient.getLock("l4")
val lock5: RLock = redissonClient.getLock("l5")
val lock = RedissonMultiLock(lock1, lock2, lock3, lock4, lock5)
lock.lock()
// perform some tasks
lock.unlock()
Further integration can be found here: https://github.com/redisson/redisson/wiki/14.-Integration-with-frameworks
The code can be found here:
Leave a Reply