Master Redisson Distributed Locks: From Simple Reentrant to Fair Lock with WatchDog
This article explains how Redisson implements distributed locks on Redis, covering basic concepts, a simple lock with Lua scripts, reentrancy handling, automatic lock renewal via WatchDog, and the advanced fair lock mechanism that ensures FIFO ordering using Redis lists and sorted sets.
1. Redisson Overview
Redisson is a Java in‑memory data grid built on Redis that provides distributed objects and services such as locks.
What is Redisson?
It abstracts Redis into higher‑level distributed tools, separating concerns so developers can focus on business logic.
2. Distributed Lock
How to implement a distributed lock?
Various approaches exist (ZooKeeper, DB row locks, Redis setNx), but this article focuses on Redisson’s Redis‑based solution.
Simple Redis lock example
public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
}
public void unlock(String lockName, String uuid) {
if (uuid.equals(redisTemplate.opsForValue().get(lockName))) {
redisTemplate.opsForValue().del(lockName);
}
}This version is not atomic for get and del, so a Lua script is introduced.
Lua script for atomic unlock
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
endMaking the lock re‑entrant
Re‑entrancy stores lockName, threadId, and a count in a Redis hash. The lock is acquired by incrementing the count; unlocking decrements it and removes the hash when the count reaches zero.
hset lockname1 threadId 1
hincrby lockname1 threadId 1
hget lockname1 threadId3. Redisson Distributed Lock
Dependency
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>Configuration
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.password}")
private String password;
private int port = 6379;
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + redisHost + ":" + port)
.setPassword(password);
config.setCodec(new JsonJacksonCodec());
return Redisson.create(config);
}
}Using the lock
RLock rLock = redissonClient.getLock("myLock");
if (rLock.tryLock(expireTime, TimeUnit.MILLISECONDS)) {
// business logic
rLock.unlock();
}4. RLock Core
RLock implements java.util.concurrent.locks.Lock and adds asynchronous methods returning RFuture. The key method tryLockInnerAsync executes a Lua script that creates or updates a Redis hash and sets an expiration.
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;
-- 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 redis.call('pttl', KEYS[1]);If no lease time is provided, Redisson starts a WatchDog that periodically renews the lock expiration.
WatchDog lock renewal
private void scheduleExpirationRenewal(long threadId) {
// registers a Netty timeout that runs every leaseTime/3
// calls renewExpirationAsync(threadId) which executes:
// if (hexists(KEYS[1], ARGV[2]) == 1) then
// pexpire(KEYS[1], ARGV[1]);
// return 1;
// else return 0;
}5. Fair Lock
RedissonFairLock ensures FIFO ordering using a Redis list ( redisson_lock_queue:{name}) and a sorted set ( redisson_lock_timeout:{name}).
-- 1. Clean expired queue heads
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;
-- 2. First‑time lock acquisition when queue is empty or head matches current thread
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;
-- 3. 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;
-- 4. Return current thread TTL
local timeout = redis.call('zscore', KEYS[3], ARGV[2]);
if timeout ~= false then
return timeout - tonumber(ARGV[3]) - tonumber(ARGV[4]);
end;
-- 5. Compute tail TTL and enqueue
local last = redis.call('lindex', KEYS[2], -1);
local ttl = (last ~= false and last ~= ARGV[2])
and tonumber(redis.call('zscore', KEYS[3], last)) - tonumber(ARGV[4])
or redis.call('pttl', KEYS[1]);
local newScore = ttl + tonumber(ARGV[3]) + tonumber(ARGV[4]);
if redis.call('zadd', KEYS[3], newScore, ARGV[2]) == 1 then
redis.call('rpush', KEYS[2], ARGV[2]);
end;
return ttl;The algorithm cleans expired queue heads, attempts immediate acquisition if the head matches, handles re‑entrancy, returns remaining TTL, and otherwise enqueues the thread with a calculated score that preserves FIFO order.
6. Summary
Redisson provides a sophisticated distributed lock implementation that combines Redis Lua scripts, Netty asynchronous futures, and Java concurrency concepts. It supports re‑entrant locks with automatic WatchDog renewal and a fair lock that guarantees FIFO ordering using Redis lists and sorted sets, making it a powerful tool for backend Java services.
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.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
