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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Understanding Redisson Distributed Lock Implementation

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
end

Executed 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaconcurrencydistributed-lockLuaredisson
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.