Understanding Redisson Distributed Lock Implementation
This article provides a comprehensive, step‑by‑step analysis of Redisson’s distributed lock implementation, covering its architecture, Lua scripts for lock acquisition, re‑entrancy, watchdog renewal, unlocking, fair lock mechanisms, and code examples, enabling developers to grasp and apply robust Redis‑based locking in Java applications.
1. Redisson Overview
Redisson is a Java in‑memory data grid built on top of Redis, offering distributed Java objects and services that abstract Redis operations, allowing developers to focus on business logic.
Compared with Jedis and Lettuce, which are low‑level Redis clients, Redisson provides higher‑level abstractions such as distributed locks, maps, and semaphores.
2. Distributed Lock Basics
Distributed locks are essential for concurrent business scenarios. Various implementations exist (ZooKeeper, database row locks, Redis SETNX), but they all aim to achieve mutual exclusion.
2.1 Simple Redis Distributed Lock (Version 1.0)
Using RedisTemplate in Spring Data Redis:
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 suffers from non‑atomic GET and DEL, leading to race conditions under high contention.
2.2 Lua‑Based Atomic Lock (Version 2.0)
Lua scripts guarantee atomic execution in Redis.
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
endExecuted via:
DefaultRedisScript<Object> unlockScript = new DefaultRedisScript<>();
unlockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lockDel.lua")));
redisTemplate.execute(unlockScript, Collections.singletonList(keyName), value);2.3 Re‑entrancy
Re‑entrant locks allow the same thread to acquire the lock multiple times. The implementation stores lockName, threadId, and a count in a Redis hash, incrementing the count on re‑entry and decrementing on unlock.
Key Redis commands used: HSET / HGET for storing the count. HEXISTS to check thread ownership. HINCRBY to adjust the counter. PEXPIRE to set expiration.
3. Redisson Distributed Lock
3.1 Dependency
<!-- Maven dependency -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>3.2 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);
}
}3.3 Using the Lock
@Resource
private RedissonClient redissonClient;
RLock rLock = redissonClient.getLock(lockName);
try {
boolean isLocked = rLock.tryLock(expireTime, TimeUnit.MILLISECONDS);
if (isLocked) {
// TODO: business logic
}
} catch (Exception e) {
rLock.unlock();
}4. RLock Core Mechanics
RLock implements java.util.concurrent.locks.Lock and its asynchronous counterpart. The critical method is tryLock, which delegates to tryAcquireOnceAsync.
private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1L) {
return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
} else {
RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(waitTime,
this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null && ttlRemaining) {
this.scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
}If a lease time is not provided, Redisson registers a watchdog task that periodically renews the lock.
4.1 Unlocking
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('exists', KEYS[1]) == 0) then redis.call('publish', KEYS[2], ARGV[1]); return 1; end;"
+ "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('pexpire', KEYS[1], ARGV[2]); return 0; else redis.call('del', KEYS[1]);"
+ "redis.call('publish', KEYS[2], ARGV[1]); return 1; end;",
Arrays.asList(this.getName(), this.getChannelName()),
LockPubSub.unlockMessage, this.internalLockLeaseTime, this.getLockName(threadId));
}The Lua script decrements the re‑entrancy counter, deletes the lock when the count reaches zero, and publishes an unlock message.
4.2 Pub/Sub Notification
When a lock is released, LockPubSub receives the unlock message, runs the waiting thread’s callback, and releases a semaphore to wake up the next contender.
5. Fair Lock
Redisson also provides a fair lock that guarantees FIFO acquisition order using a Redis list (queue) and a sorted set (timeout ordering).
5.1 Fair Lock Lua Script (Simplified)
-- 1. Clean expired queue entries
while true do
local firstThreadId = redis.call('lindex', KEYS[2], 0);
if firstThreadId == false then break; end;
local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId));
if timeout <= tonumber(ARGV[4]) then
redis.call('zrem', KEYS[3], firstThreadId);
redis.call('lpop', KEYS[2]);
else
break;
end;
end;
-- 2. First‑time 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;
-- 3. Re‑entrancy check
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 TTL
local ttl = redis.call('zscore', KEYS[3], ARGV[2]);
if ttl ~= false then
return ttl - tonumber(ARGV[3]) - tonumber(ARGV[4]);
end;
-- 5. Tail‑node TTL calculation and queue insertion
local lastThreadId = redis.call('lindex', KEYS[2], -1);
local ttl;
if lastThreadId ~= false and lastThreadId ~= ARGV[2] then
ttl = tonumber(redis.call('zscore', KEYS[3], lastThreadId)) - tonumber(ARGV[4]);
else
ttl = redis.call('pttl', KEYS[1]);
end;
local timeout = ttl + tonumber(ARGV[3]) + tonumber(ARGV[4]);
if redis.call('zadd', KEYS[3], timeout, ARGV[2]) == 1 then
redis.call('rpush', KEYS[2], ARGV[2]);
end;
return ttl;The script first removes expired queue entries, then attempts to acquire the lock if it is free or if the current thread is at the head of the queue. If the lock is held by another thread, the caller is placed at the tail of the list and its timeout is recorded in the sorted set, ensuring FIFO ordering.
6. Summary
Redisson implements a sophisticated distributed lock mechanism on top of Redis, handling atomicity with Lua scripts, providing re‑entrancy, automatic watchdog renewal, Pub/Sub‑based waiting, and an optional fair lock that leverages Redis lists and sorted sets to guarantee FIFO acquisition. The library’s design combines Netty’s asynchronous model with Java’s concurrency primitives, offering a powerful tool for developers needing reliable distributed synchronization.
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.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
