Understanding Redisson Distributed Locks: Architecture, Reentrancy, Fairness, and WatchDog Mechanism
This article explains Redisson's role as a Java in‑memory data grid built on Redis, compares it with Jedis and Lettuce, and details how Redisson implements distributed locks—including simple lock/unlock, Lua‑based reentrant locks, automatic lock renewal (WatchDog), and a fair lock using Redis lists and sorted sets—providing complete code examples and execution flow.
1. Redisson Overview
Redisson is a Java in‑memory data grid built on top of Redis. It offers distributed implementations of common Java objects (Map, List, Set, Queue, etc.) and services (Lock, Semaphore, Bloom filter, Scheduler, etc.) to simplify Redis usage and let developers focus on business logic.
Compared with Jedis and Lettuce, Redisson provides a higher‑level abstraction: Jedis and Lettuce are thin wrappers around Redis commands, while Redisson adds distributed objects and services, handling connection pooling, serialization, and fault tolerance.
2. Distributed Lock Implementation
A basic Redis lock can be created with SETNX and an expiration time, but the GET and DEL operations are not atomic. To solve this, the article introduces a Lua script that performs lock acquisition and release atomically.
// lock (tryLock)
public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
}
// unlock (verify owner before delete)
public void unlock(String lockName, String uuid) {
if (uuid.equals(redisTemplate.opsForValue().get(lockName))) {
redisTemplate.opsForValue().del(lockName);
}
}The initial version (1.0) works but suffers from a race condition because GET and DEL are separate commands. The article therefore upgrades to a Lua‑based solution (2.0) that checks the lock owner and deletes it in a single atomic step.
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
endTo support re‑entrancy, the lock state is stored in a Redis hash where the field is the thread ID and the value is the acquisition count. The Lua scripts increment/decrement this counter and manage the lock’s TTL accordingly.
Re‑entrant Lock Logic
-- lock acquisition
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 (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
return redis.call('pttl', KEYS[1]) -- unlock
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
return nil
end
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1)
if (counter == 0) then
redis.call('del', KEYS[1])
redis.call('publish', KEYS[2], ARGV[1])
return 1
else
redis.call('pexpire', KEYS[1], ARGV[2])
return 0
endRedisson also adds a WatchDog mechanism: when a lock is acquired without an explicit lease time, a background Netty task periodically (one‑third of the lease time) extends the lock’s expiration to avoid premature release.
WatchDog Renewal
private void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
ExpirationEntry old = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
if (old != null) {
old.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
renewExpiration();
}
}
private void renewExpiration() {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee != null) {
Timeout task = commandExecutor.getConnectionManager().newTimeout(t -> {
ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent != null) {
Long id = ent.getFirstThreadId();
if (id != null) {
RFuture
f = renewExpirationAsync(id);
f.onComplete((res, e) -> {
if (e == null && res) {
renewExpiration();
}
});
}
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
}3. Redisson Distributed Lock (RLock)
Using Redisson, a lock is obtained with a single call:
@Resource
private RedissonClient redissonClient;
RLock lock = redissonClient.getLock("myLock");
if (lock.tryLock(leaseTime, TimeUnit.MILLISECONDS)) {
try {
// critical section
} finally {
lock.unlock();
}
}Internally, Redisson executes Lua scripts similar to the custom ones above, but also registers a Pub/Sub listener (LockPubSub) that notifies waiting threads when the lock is released, avoiding busy‑spinning.
4. Fair Lock (RedissonFairLock)
RedissonFairLock guarantees FIFO ordering by maintaining two Redis structures: a list ( redisson_lock_queue:{name} ) for the waiting thread order and a sorted set ( redisson_lock_timeout:{name} ) for each thread’s timeout (score). The Lua script performs six steps: clean expired entries, first‑acquire, re‑enter, return TTL, compute tail‑node TTL, and finally enqueue the thread.
-- clean expired queue entries
while true do
local first = redis.call('lindex', KEYS[2], 0)
if first == false then break end
local timeout = tonumber(redis.call('zscore', KEYS[3], first))
if timeout <= tonumber(ARGV[4]) then
redis.call('zrem', KEYS[3], first)
redis.call('lpop', KEYS[2])
else
break
end
end
-- first lock acquisition
if (redis.call('exists', KEYS[1]) == 0) and
((redis.call('exists', KEYS[2]) == 0) or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) then
redis.call('lpop', KEYS[2])
redis.call('zrem', KEYS[3], ARGV[2])
redis.call('hset', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
-- re‑entrant case
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
-- return remaining TTL
local ttl = redis.call('pttl', KEYS[1])
return ttlWhen the current lock holder releases the lock, Redisson removes the head of the queue, updates the sorted set, and publishes an unlock message so the next waiting thread can acquire the lock immediately.
5. Summary
Redisson provides a comprehensive distributed‑lock solution that covers simple mutexes, re‑entrant locks, automatic lease renewal (WatchDog), and fair FIFO locks. It leverages Redis hashes, Lua scripts, Pub/Sub, and Netty‑based asynchronous execution to achieve high performance and correctness. The library also supports multi‑node locks (MultiLock) and the RedLock algorithm, which are topics for future exploration.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.