Wonderful Redis & Kotlin – Awesome With Redisson

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)

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L31-L37

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();

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L280-L289

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()

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L40-L52

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

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L54-L92

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()

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L96-L118

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}") }
)

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L121-L137

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()

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L146-L169

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) }

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L171-L179

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)

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L181-L189

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)

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L191-L212

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)

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L214-L224

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)

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L226-L236

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
)

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L238-L247

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") 
}

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L249-L257

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()

https://github.com/lotharschulz/redis_kotlin/blob/main/app/src/main/kotlin/redis_kotlin/App.kt#L259-L275


Further integration can be found here: https://github.com/redisson/redisson/wiki/14.-Integration-with-frameworks

The code can be found here:

Be the first to comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.