Understanding Redisson from Scratch: A Java Distributed Toolbox Guide and Hands‑On
This article introduces Redisson, a Redis‑based Java client that wraps Redis commands into familiar Java concurrency primitives, compares it with Jedis and Lettuce, explains why custom distributed locks are error‑prone, and provides step‑by‑step code for configuring, using, and integrating its core features such as locks, maps, queues, and rate limiters in Spring Boot.
What is Redisson
Definition
Redisson is a Java client framework built on Redis that wraps low‑level Redis commands into Java‑familiar data structures and tools (locks, queues, maps, semaphores, etc.), allowing developers to use distributed features as if they were local objects.
Analogy
java.util.HashMap→ RMap (distributed map, data stored in Redis, shared across service instances) java.util.concurrent.locks.ReentrantLock → RLock (distributed lock, lock stored in Redis, works across JVMs) java.util.concurrent.BlockingQueue → RBlockingQueue (distributed queue, multiple consumers) java.util.concurrent.Semaphore → RSemaphore (distributed semaphore, rate‑limit across instances)
Redisson vs Jedis vs Lettuce
Jedis – low‑level Redis client, sends commands directly, lightweight.
Lettuce – low‑level client based on Netty, supports async, default in Spring Boot.
Redisson – high‑level client that encapsulates distributed locks, collections, queues and other advanced features.
// Jedis: direct Redis commands
jedis.set("key", "value");
jedis.setnx("lock-key", "holder");
// Lettuce (via Spring RedisTemplate): also command‑based
redisTemplate.opsForValue().set("key", "value");
redisTemplate.opsForValue().setIfAbsent("lock-key", "holder");
// Redisson: object‑oriented, hides low‑level commands
RLock lock = redissonClient.getLock("lock-key");
lock.lock(); // internally handles SETNX, expiration, watchdog, etc.Why use Redisson
Pain points of implementing a distributed lock yourself
public boolean tryLock(String key, String holderId, long timeout) {
// 1. Acquire lock (SETNX + expiration, must be atomic)
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, holderId, timeout, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
public void unlock(String key, String holderId) {
// 2. Release lock (verify holder, atomic via Lua)
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
"return redis.call('del',KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key), holderId);
// Additional concerns: watchdog renewal, re‑entrancy, fairness, RedLock, async, listeners.
}Redisson lock usage
RLock lock = redissonClient.getLock("my-lock");
lock.lock();
try {
// business logic
} finally {
lock.unlock();
}Atomic lock acquisition via Lua script
Holder verification on release
Watchdog‑based automatic lease renewal
Re‑entrancy counting
Waiting queue and fairness
Compatibility with cluster and sentinel modes
Quick start
Maven dependency
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.0</version>
</dependency>Connection configuration
spring:
redis:
host: 127.0.0.1
port: 6379
password: your-passwordor Redisson's own redisson.yml:
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: "your-password"
connectionMinimumIdleSize: 5
connectionPoolSize: 10Bean injection example
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private RedissonClient redissonClient;
public void processOrder(Integer orderId) {
RLock lock = redissonClient.getLock("order-" + orderId);
boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (!acquired) {
throw new RuntimeException("Failed to acquire lock");
}
try {
doProcess(orderId);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}Core Redisson features
Distributed lock (RLock)
RLock lock = redissonClient.getLock("my-lock");
// blocking lock
lock.lock();
// tryLock with wait time (10 s)
bool ok = lock.tryLock(10, TimeUnit.SECONDS);
// tryLock with wait time and lease time (30 s)
ok = lock.tryLock(10, 30, TimeUnit.SECONDS);
// tryLock with watchdog (no lease time)
ok = lock.tryLock(10, TimeUnit.SECONDS);
lock.unlock();Fair lock (RFairLock)
RLock fairLock = redissonClient.getFairLock("fair-lock");
fairLock.lock();
try {
// business logic
} finally {
fairLock.unlock();
}Read‑write lock (RReadWriteLock)
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rw-lock");
RLock readLock = rwLock.readLock();
readLock.lock();
try { /* read */ } finally { readLock.unlock(); }
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try { /* write */ } finally { writeLock.unlock(); }Distributed map (RMap)
RMap<String, UserInfo> userCache = redissonClient.getMap("user-cache");
userCache.put("user-123", userInfo);
UserInfo info = userCache.get("user-123");
userCache.put("user-123", userInfo, 30, TimeUnit.MINUTES);Distributed queue (RBlockingQueue)
// producer
RBlockingQueue<String> queue = redissonClient.getBlockingQueue("task-queue");
queue.offer("task-001");
// consumer (blocks if empty)
String task = queue.take();
processTask(task);Rate limiter (RRateLimiter)
RRateLimiter limiter = redissonClient.getRateLimiter("api-limiter");
limiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);
if (limiter.tryAcquire()) {
processRequest();
} else {
throw new RuntimeException("Too many requests");
}RLock internal mechanics
Lock acquisition script (simplified)
-- if lock does not exist
if redis.call('exists', KEYS[1]) == 0 then
redis.call('hset', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
-- if lock exists and is owned by the same holder (re‑enter)
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
redis.call('hincrby', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
-- lock held by another client, return remaining TTL
return redis.call('pttl', KEYS[1])Data structure in Redis
Key: "my-lock"
Type: Hash
Value: "holder-id-thread-1" → 2 (re‑enter count = 2)
TTL: 30000 msUnlock script (simplified)
-- verify ownership
if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then
return nil
end
-- decrement re‑enter count
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1)
if counter > 0 then
redis.call('pexpire', KEYS[1], ARGV[2])
return 0
else
redis.call('del', KEYS[1])
redis.call('publish', KEYS[2], ARGV[3])
return 1
endSpring Boot integration
Auto‑configuration (simplest)
@Service
public class MyService {
@Resource
private RedissonClient redissonClient;
public void doSomething() {
RLock lock = redissonClient.getLock("key");
// …
}
}Manual configuration
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("password")
.setConnectionMinimumIdleSize(5)
.setConnectionPoolSize(20)
.setTimeout(3000)
.setRetryAttempts(3);
return Redisson.create(config);
}
}Cluster mode
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://node1:6379", "redis://node2:6379", "redis://node3:6379")
.setPassword("password");
return Redisson.create(config);
}Real‑world example: Stock deduction
Full implementation
@Service
public class StockServiceImpl implements StockService {
@Resource
private RedissonClient redissonClient;
@Resource
private StockRepository stockRepository;
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(Integer skuId, Integer quantity) {
String lockKey = "stock-deduct-" + skuId;
RLock lock = redissonClient.getLock(lockKey);
try {
boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);
if (!acquired) {
log.warn("Lock timeout for skuId:{}", skuId);
return false;
}
Stock stock = stockRepository.findBySkuId(skuId);
if (stock == null || stock.getQuantity() < quantity) {
log.info("Insufficient stock, skuId:{}, current:{}, needed:{}",
skuId, stock == null ? 0 : stock.getQuantity(), quantity);
return false;
}
stock.setQuantity(stock.getQuantity() - quantity);
stockRepository.save(stock);
log.info("Stock deducted, skuId:{}, deducted:{}, remaining:{}",
skuId, quantity, stock.getQuantity());
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Lock interrupted for skuId:{}", skuId);
return false;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}Key steps
// 1. Obtain lock object (not yet locked)
RLock lock = redissonClient.getLock(lockKey);
// 2. tryLock(waitTime) – attempts to acquire, waits up to 5 s
boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);
// 3. isHeldByCurrentThread() – ensures only the owner releases
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}Performance notes
Lock acquisition and release typically 1–3 ms (network dependent).
Redisson adds modest overhead compared with raw RedisTemplate but remains much faster than ZooKeeper‑based locks.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
The Dominant Programmer
Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
