Unlocking Redisson: How Distributed Locks Work Under the Hood

This article walks through configuring Redisson with Maven, demonstrates a simple distributed lock example using Sentinel, lists the Redis commands and Lua script semantics involved, and provides a detailed source‑code analysis of the RLock interface, tryLock, unlock, and forceUnlock methods.

Programmer DD
Programmer DD
Programmer DD
Unlocking Redisson: How Distributed Locks Work Under the Hood

Maven Configuration

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>2.2.12</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.6.0</version>
</dependency>

RedissonLock Simple Example

Redisson supports four connection modes – single, master‑slave, Sentinel, and Cluster – and the example uses Sentinel.

Sentinel Configuration

Config config = new Config();
config.useSentinelServers()
    .addSentinelAddress("127.0.0.1:6479", "127.0.0.1:6489")
    .setMasterName("master")
    .setPassword("password")
    .setDatabase(0);
RedissonClient redisson = Redisson.create(config);

Simple Usage

RLock lock = redisson.getLock("test_lock");
try {
    boolean isLock = lock.tryLock();
    if (isLock) {
        doBusiness();
    }
} catch (Exception e) {
    // handle
} finally {
    lock.unlock();
}

Redis Commands Used by the Distributed Lock

EXISTS key – returns 1 if the key exists, otherwise 0.

GETSET key value – sets the key to a new value and returns the old value.

GET key – returns the string value of the key.

DEL key … – deletes one or more keys.

HSET key field value – sets a hash field.

HEXISTS key field – checks if a hash field exists.

HINCRBY key field increment – increments a hash field.

PEXPIRE key milliseconds – sets key expiration in ms.

PUBLISH channel message – publishes a message to a channel.

Lua Script Semantics in Redisson

Redisson executes Redis commands via Lua scripts. Key concepts include redis.call() to run a command, KEYS[1] for the first key argument, ARGV[1] for the first value argument, and the fact that nil and false are treated the same in return values.

Source Code Analysis

RLock Interface

public interface RLock extends Lock, RExpirable {
    void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException;
    boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
    void lock(long leaseTime, TimeUnit unit);
    void forceUnlock();
    boolean isLocked();
    boolean isHeldByCurrentThread();
    int getHoldCount();
    Future<Void> unlockAsync();
    Future<Boolean> tryLockAsync();
    Future<Void> lockAsync();
    Future<Void> lockAsync(long leaseTime, TimeUnit unit);
    Future<Boolean> tryLockAsync(long waitTime, TimeUnit unit);
    Future<Boolean> tryLockAsync(long waitTime, long leaseTime, TimeUnit unit);
}

The interface extends Lock and adds methods such as tryLock with a lease time, which automatically releases the lock after the specified period (default 30 s).

RedissonLock tryLock Implementation

Future<Long> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId) {
    internalLockLeaseTime = unit.toMillis(leaseTime);
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
        "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]);",
        Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

Key mappings: KEYS[1] is the lock name (e.g., test_lock), ARGV[1] is the lease time (default 30 s), and ARGV[2] is the thread identifier ( id:threadId). The script creates the lock if it does not exist, increments the re‑entry counter if the same thread already holds it, and returns the remaining TTL otherwise.

RedissonLock unlock Implementation

public void unlock() {
    Boolean opStatus = commandExecutor.evalWrite(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; " +
        "return nil;",
        Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId()));
    if (opStatus == null) {
        throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread");
    }
    if (opStatus) {
        cancelExpirationRenewal();
    }
}

The script checks existence, verifies the current thread owns the lock, decrements the re‑entry counter, renews the TTL if needed, or deletes the lock and publishes an unlock message.

RedissonLock forceUnlock Implementation

public void forceUnlock() {
    get(forceUnlockAsync());
}
Future<Boolean> forceUnlockAsync() {
    cancelExpirationRenewal();
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
        "if (redis.call('del', KEYS[1]) == 1) then " +
        "redis.call('publish', KEYS[2], ARGV[1]); " +
        "return 1 " +
        "else " +
        "return 0 " +
        "end",
        Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage);
}

Forceful unlock simply deletes the lock key and publishes a message; it is used mainly during resource cleanup.

Conclusion

The article provides a concise Redisson distributed‑lock example, explains the underlying Redis commands and Lua scripts, and analyses the relevant source code. Further deep‑dive would require examining Netty, which Redisson uses for asynchronous processing.

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.

redismavendistributed-lockLuaredisson
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.