Fundamentals 12 min read

Mastering Redis Redlock: A Deep Dive into Distributed Lock Implementation

This article explains the classic Redis distributed lock using SETNX and Lua, highlights its pitfalls, introduces the advanced Redlock algorithm with step‑by‑step acquisition and release procedures, and provides concrete Java Redisson code examples, illustrating how to implement reliable distributed locks across multiple Redis nodes.

Programmer DD
Programmer DD
Programmer DD
Mastering Redis Redlock: A Deep Dive into Distributed Lock Implementation

Normal Implementation

When people think of Redis distributed locks they usually consider SETNX+Lua or the command SET key value PX milliseconds NX. The core implementation of the latter is shown below:

- Get lock (unique_value can be UUID, etc.)
SET resource_name unique_value NX PX 30000

- Release lock (Lua script must compare value to avoid accidental unlock)
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

This implementation has three key points (often asked in interviews):

Use the command SET key value PX milliseconds NX;

The value must be unique;

When releasing the lock, verify the value to prevent accidental unlock.

The biggest drawback is that the lock only operates on a single Redis node. Even if Redis uses Sentinel for high availability, a master‑slave failover can cause lock loss:

The lock is acquired on the master node;

The lock key has not yet been synchronized to the slave;

The master fails and a slave is promoted to master;

The lock is lost.

Because of this, Redis creator antirez proposed a more advanced distributed‑lock algorithm called Redlock , which is often the most impressive answer in interviews.

Redlock Implementation

Antirez's Redlock algorithm assumes N independent Redis masters (no replication or cluster coordination). For example, with 5 masters running on 5 servers, the client follows these steps to acquire a lock:

Get the current Unix time in milliseconds.

Try to acquire the lock on each of the 5 instances using the same key and a unique value (e.g., UUID). Set a network timeout shorter than the lock TTL (e.g., if TTL is 10 s, timeout 5‑50 ms). If a node does not respond in time, move to the next one.

Calculate the time spent acquiring the lock. The lock is considered successful only if a majority (N/2+1, i.e., 3 nodes) grant the lock and the elapsed time is less than the TTL.

If successful, adjust the effective TTL by subtracting the elapsed time.

If acquisition fails, unlock on all instances to avoid stale locks.

Redlock Source Code

Redisson provides a wrapper for the Redlock algorithm. Below is a simple usage example and core source analysis (assuming 5 Redis instances).

POM Dependency

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.3.2</version>
</dependency>

Usage

Creating a Redisson client and using a Redlock is similar to Java's ReentrantLock:

Config config = new Config();
config.useSentinelServers()
      .addSentinelAddress("127.0.0.1:6369","127.0.0.1:6379","127.0.0.1:6389")
      .setMasterName("masterName")
      .setPassword("password")
      .setDatabase(0);
RedissonClient redissonClient = Redisson.create(config);
RLock redLock = redissonClient.getLock("REDLOCK_KEY");
boolean isLock;
try {
    isLock = redLock.tryLock();
    isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
    if (isLock) {
        // TODO: lock acquired, do something
    }
} catch (Exception e) {
} finally {
    redLock.unlock(); // always unlock
}

Unique ID

The lock value must be unique; Redisson builds it as UUID+threadId in RedissonLock:

protected final UUID id = UUID.randomUUID();
String getLockName(long threadId) {
    return id + ":" + threadId;
}

Acquire Lock

The core of redLock.tryLock() (or the overloaded version) eventually calls the following Lua script on each Redis instance:

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
        "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 meanings:

KEYS[1] – the lock key (e.g., REDLOCK_KEY);

ARGV[1] – lock lease time (default 30 s);

ARGV[2] – the unique value (UUID+threadId).

Release Lock

Unlocking is performed by redLock.unlock(), which runs the following Lua script on all instances:

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return commandExecutor.evalWriteAsync(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(threadId));
}

Parameters:

KEYS[1] – lock key;

KEYS[2] – Pub/Sub channel for unlock notifications;

ARGV[1] – unlock message;

ARGV[2] – lease time;

ARGV[3] – unique lock value (UUID+threadId).

Reference: Redis Distributed Lock Documentation

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.

Javaredisdistributed-lockredissonRedlock
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.