Overview
Redisson is an in-memory data grid built on top of Redis that provides distributed Java objects and services. Beyond standard Redis operations, it offers a rich set of distributed structures including Queue, Lock, AtomicLong, CountDownLatch, Publish/Subscribe, and ExecutorService. Redisson abstracts away the complexities of Redis, allowing developers to focus on business logic rather than infrastructure concerns. The library is built on Netty and supports Redis 2.8+ with Java 1.6+ compatibility.
Jedis vs Redisson
Jedis serves as a lightweight Redis client library that directly maps to Redis commands. It offers a more native approach with greater flexibility but requires manual implementation of advanced features.
Redisson prvoides a higher-level abstraction with built-in support for complex data structures, distributed locks, and enterprise features. While it carries a larger footprint, it significantly reduces development time for distributed systems.
Core Features
Distributed Locking
- Reentrant Lock: A thread holding a lock can acquire it multiple times without deadlocking. Other threads remain blocked.
- Fair Lock: Threads acquire locks in the exact order they request them, similar to a first-come-first-served queue.
- CountDownLatch: Provides distributed synchronization similar to
java.util.concurrent.CountDownLatch.
Rate Limiting
The distributed rate limiter controls request frequency across multiple threads and Redisson instances. It supports synchronous, asynchronous, reactive, and RxJava2 interfaces. Fairness is not guaranteed by default.
Pub/Sub Messaging
Topics enable message distribution where subscribers consume messages from designated channels.
Transactions
ACID-compliant transactions are available for RMap, RMapCache, RLocalCachedMap, RSet, RSetCache, and RBucket. Redisson implements distributed locks for atomic operations and uses an internal command queue to provide Redis-native transaction capabilities.
Queue Implementations
Support for bounded, unbounded, blocking, and non-blocking queue patterns.
Distributed Services
- Remote Service: Enables RPC-style method invocation across Redisson instances using shared interfaces.
- Execution Service: Implements
ExecutorServicefor executing Callable, Runnable, or Lambda tasks across distributed nodes. - Scheduled Service: Implements
ScheduledExecutorServicefor time-based task exectuion.
Bloom Filter
A probabilistic data structure optimized for space efficiency. It can definitively state thatt an element does not exist, but may produce false positives for existing elements.
Configuration
Single Redis Node
RedissonClient client = Redisson.create();
Config config = new Config();
config.useSingleServer().setAddress("redis-server:6379");
RedissonClient client = Redisson.create(config);
Spring Boot Integration
spring:
redis:
host: 127.0.0.1
password: secret
database: 0
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.12.5</version>
</dependency>
Data Structures
Map (RMap)
Distributed Map implementation supporting ConcurrentMap and Map interfaces. Elements maintain insertion order, with a maximum capacity of approximately 4.3 billion entries.
Map Classification:
-
Eviction-based Maps: Configure per-entry expiration and idle timeouts. Redisson uses
EvictionSchedulerfor cleanup since Redis lacks native hash expiration, processing up to 300 expired entries per run. -
LocalCache Maps: Store data in both Redis and local memory to reduce network latency. Can improve read performance by up to 45x. All local caches share a pub/sub channel for synchronization.
-
Sharded Maps: Distribute entries across Redis cluster slots for horizontal scalability.
Map Usage Example:
@RestController
@RequestMapping("/cache")
public class CacheController {
@Autowired
private RedissonClient redisClient;
private static final String CACHE_KEY = "user_session";
@PostConstruct
public void setupExpirationListener() {
RMapCache<String, Object> cacheMap = redisClient.getMapCache(CACHE_KEY);
cacheMap.addListener(new EntryExpiredListener<String, Object>() {
@Override
public void onExpired(EntryEvent<String, Object> event) {
log.info("Entry expired - key: {}, oldValue: {}, newValue: {}",
event.getKey(), event.getOldValue(), event.getValue());
}
});
}
@RequestMapping("/store")
public ResponseEntity<String> storeValue(String key, String value, boolean withTTL) {
RMapCache<String, String> cacheMap = redisClient.getMapCache(CACHE_KEY);
if (withTTL) {
cacheMap.put(key, value, 30, TimeUnit.MINUTES);
} else {
cacheMap.put(key, value);
}
return ResponseEntity.ok("Value stored successfully");
}
@RequestMapping("/display")
public ResponseEntity<String> displayCache() {
RMapCache<String, String> cacheMap = redisClient.getMapCache(CACHE_KEY);
cacheMap.entrySet().forEach(entry ->
log.info("{} -> {}", entry.getKey(), entry.getValue()));
return ResponseEntity.ok("Cache contents logged");
}
}
Set (RSet)
Distributed Set implementation supporting java.util.Set interface. Uniqueness is maintained through value comparison.
Eviction Support: Redis lacks native Set expiration, so EvictionScheduler handles cleanup, processing up to 100 entries per run.
Scored Sorted Set: Maintains elements sorted by associated scores.
Set Usage Example:
@RestController
@RequestMapping("/ranking")
public class LeaderboardController {
@Autowired
private RedissonClient redisClient;
@RequestMapping("/score")
public ResponseEntity<String> updateScore(String playerId, Double points) {
RScoredSortedSet<String> leaderboard = redisClient.getScoredSortedSet("leaderboard");
boolean isNew = !leaderboard.isExists();
leaderboard.addScore(playerId, points);
if (isNew) {
leaderboard.expireAt(DateUtils.addHours(new Date(), 2));
}
int rank = leaderboard.revRank(playerId);
Double score = leaderboard.getScore(playerId);
log.info("Player {} updated - rank: {}, score: {}", playerId, rank, score);
return ResponseEntity.ok("Score updated");
}
@RequestMapping("/rankings")
public ResponseEntity<String> getRankings(String boardKey) {
RScoredSortedSet<String> board = redisClient.getScoredSortedSet(boardKey);
board.forEach(player -> {
int position = board.revRank(player);
Double score = board.getScore(player);
log.info("Position {}: {} with score {}", position, player, score);
});
return ResponseEntity.ok("Rankings displayed");
}
@RequestMapping("/cleanup")
public ResponseEntity<String> cleanupExpired() {
long deleted = redisClient.getKeys().deleteByPattern("temp:*");
return ResponseEntity.ok("Deleted " + deleted + " expired entries");
}
}
List (RList)
Distributed List implementation preserving insertion order with maximum capacity of approximately 4.3 billion elements.
Queue (RQueue)
Distributed Queue implementation with unlimited capacity (subject to Redis memory constraints).
@RestController
@RequestMapping("/messaging")
public class QueueController {
@Autowired
private RedissonClient redisClient;
@PostConstruct
public void initializeConsumer() {
RBlockingQueue<String> queue = redisClient.getBlockingQueue("task_queue");
queue.subscribeOnElements(message ->
log.info("Processing task: {}", message));
}
@RequestMapping("/enqueue")
public List<String> enqueue(String item) {
RList<String> list = redisClient.getList("task_list");
list.add(item);
return list.readAll();
}
@RequestMapping("/dequeue")
public String dequeue() {
RQueue<String> queue = redisClient.getQueue("task_list");
return queue.poll();
}
@RequestMapping("/enqueue-blocked")
public List<String> enqueueWithBlocking(String item) {
RBlockingQueue<String> queue = redisClient.getBlockingQueue("work_queue");
queue.add(item);
return queue.readAll();
}
@RequestMapping("/retrieve-blocked")
public String retrieveBlocking() throws InterruptedException {
RBlockingQueue<String> queue = redisClient.getBlockingQueue("work_queue");
return queue.take();
}
@RequestMapping("/schedule")
public List<String> scheduleDelayed(String task, Long delaySeconds) {
RQueue<String> targetQueue = redisClient.getQueue("scheduled_tasks");
RDelayedQueue<String> delayedQueue = redisClient.getDelayedQueue(targetQueue);
delayedQueue.offer(task, delaySeconds, TimeUnit.SECONDS);
return delayedQueue.readAll();
}
}
CountDownLatch
@RestController
@RequestMapping("/sync")
public class SynchronizationController {
@Autowired
private RedissonClient redisClient;
@RequestMapping("/wait")
public ResponseEntity<String> awaitCompletion() {
RCountDownLatch latch = redisClient.getCountDownLatch("processing_latch");
latch.trySetCount(5);
latch.await();
return ResponseEntity.ok("All workers completed");
}
@RequestMapping("/signal")
public ResponseEntity<String> signalWorker() {
RCountDownLatch latch = redisClient.getCountDownLatch("processing_latch");
log.info("Worker {} reporting completion", Thread.currentThread().getName());
latch.countDown();
return ResponseEntity.ok("Signal sent");
}
}
Topic (Pub/Sub)
@RestController
@RequestMapping("/events")
public class EventController {
@Autowired
private RedissonClient redisClient;
@PostConstruct
public void subscribeToEvents() {
RTopic eventChannel = redisClient.getTopic("system_events");
eventChannel.addListener(String.class, (channel, message) ->
log.info("Received event: {}", message));
}
@RequestMapping("/publish")
public ResponseEntity<String> publishEvent(String eventData) {
RTopic channel = redisClient.getTopic("system_events");
channel.publish(eventData);
return ResponseEntity.ok("Event published: " + eventData);
}
}
Rate Limiter
@RestController
@RequestMapping("/api")
public class ApiController {
@Autowired
private RedissonClient redisClient;
@RequestMapping("/configure")
public ResponseEntity<String> configureRateLimit() {
RRateLimiter limiter = redisClient.getRateLimiter("api_throttle");
limiter.trySetRate(RateType.PER_CLIENT, 100, 1, RateIntervalUnit.SECONDS);
return ResponseEntity.ok("Rate limiter configured: 100 requests per second");
}
@RequestMapping("/request")
public ResponseEntity<String> handleRequest() {
RRateLimiter limiter = redisClient.getRateLimiter("api_throttle");
if (limiter.tryAcquire()) {
log.info("Request allowed for {}", Thread.currentThread().getName());
return ResponseEntity.ok("Request processed");
}
return ResponseEntity.status(429).body("Rate limit exceeded");
}
}
Transaction Support
@RestController
@RequestMapping("/batch")
public class BatchController {
@Autowired
private RedissonClient redisClient;
@RequestMapping("/execute/{key}")
public ResponseEntity<String> executeBatch(@PathVariable String key) {
TransactionOptions options = TransactionOptions.defaults()
.syncSlavesTimeout(5, TimeUnit.SECONDS)
.responseTimeout(3, TimeUnit.SECONDS)
.retryAttempts(3)
.timeout(10, TimeUnit.SECONDS);
RTransaction transaction = redisClient.createTransaction(options);
try {
RMap<String, Integer> dataMap = transaction.getMap("batch_data");
Integer currentValue = dataMap.get(key);
dataMap.put(key, currentValue != null ? currentValue + 1 : 1);
transaction.commit();
return ResponseEntity.ok("Transaction committed");
} catch (Exception e) {
transaction.rollback();
return ResponseEntity.status(500).body("Transaction failed: " + e.getMessage());
}
}
}
Distributed Locking
@RestController
@RequestMapping("/resource")
public class ResourceController {
public static int counter = 0;
@Autowired
private RedissonClient redisClient;
@RequestMapping("/exclusive")
public ResponseEntity<Integer> processWithBlockingLock() {
RLock lock = redisClient.getLock("exclusive_resource");
try {
lock.lock();
log.info("{} acquired lock at {}", Thread.currentThread().getName(), System.currentTimeMillis());
Thread.sleep(3000);
counter++;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return ResponseEntity.ok(counter);
}
@RequestMapping("/try-exclusive")
public ResponseEntity<Integer> tryLock() {
RLock lock = redisClient.getLock("exclusive_resource");
try {
if (lock.tryLock()) {
log.info("{} acquired lock immediately", Thread.currentThread().getName());
Thread.sleep(2000);
counter++;
} else {
log.info("{} could not acquire lock", Thread.currentThread().getName());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return ResponseEntity.ok(counter);
}
@RequestMapping("/timed-exclusive")
public ResponseEntity<Integer> timedLock() {
RLock lock = redisClient.getFairLock("fair_resource");
try {
if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
log.info("{} acquired fair lock", Thread.currentThread().getName());
Thread.sleep(5000);
counter++;
} else {
log.info("{} failed to acquire lock within timeout", Thread.currentThread().getName());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return ResponseEntity.ok(counter);
}
}
Use Cases
- RMap: Session caching, distributed configuration storage
- RSet: Unique user tracking, tag management
- RScoredSortedSet: Leaderboards, ranking systems
- RQueue/RBlockingQueue: Task queues, work distribution
- RCountDownLatch: Parallel processing synchronization
- RTopic: Event-driven architectures, notifications
- RRateLimiter: API throttling, request limiting
- RTransaction: Batch operations requiring consistency