Mastering Distributed Locks with Redis: From Simple SETNX to RedLock and Redisson

This article explains how to implement distributed locks using Redis, starting with a basic SETNX approach, identifying its shortcomings, and then presenting robust solutions such as identifier‑based unlocking, Lua scripts for atomicity, the RedLock algorithm, and Redisson’s advanced features including a watchdog mechanism.

Architect
Architect
Architect
Mastering Distributed Locks with Redis: From Simple SETNX to RedLock and Redisson

1. Overview

In multithreaded Java programs, synchronized and ReentrantLock guarantee exclusive access within a single JVM. In distributed systems, a similar guarantee requires a distributed lock, which ensures that a shared resource is accessed by only one application instance at a time.

2. Naïve Redis Implementation

Redis, being a shared in‑memory store with high read/write performance, is often used to build a simple distributed lock. The SET command with the NX option inserts a key only when it does not exist, enabling the following workflow:

If the key does not exist, the insertion succeeds and the lock is considered acquired.

If the key already exists, the insertion fails and the lock acquisition fails.

Unlocking is performed by deleting the key.

An expiration time must be set to avoid deadlocks.

// Try to acquire lock
if (setnx(key, 1) == 1) {
    // lock acquired, set expiration
    expire(key, 30);
    try {
        // TODO: business logic
    } finally {
        // release lock
        del(key);
    }
}

This simple approach suffers from several critical problems.

3. Problems with the Naïve Approach

Non‑atomic operations: Multiple commands (SETNX + EXPIRE) are not atomic, leading to possible deadlocks.

Lock‑release errors: If a thread holding the lock blocks and its TTL expires, another thread may acquire the lock and the original thread could mistakenly delete the new lock.

Business‑timeout unlocks: Automatic expiration may cause concurrency issues when the business logic exceeds the TTL.

No re‑entrancy: The lock cannot be re‑entered by the same thread.

3.1 Mis‑deletion Scenario

Thread 1 acquires the lock, blocks, and its TTL expires. Thread 2 then acquires the lock. When Thread 1 resumes, it attempts to delete the key, inadvertently removing Thread 2’s lock.

3.2 Solution: Identifier‑Based Unlocking

Store a unique thread identifier in the lock value. When releasing, compare the stored identifier with the current thread’s identifier; delete only if they match. This also enables re‑entrancy by incrementing a counter.

// Try to acquire lock with identifier
if (setnx(key, "thread-id") == 1) {
    expire(key, 30);
    try {
        // business logic
    } finally {
        if ("thread-id".equals(get(key))) {
            del(key);
        }
    }
}

3.3 Ensuring Atomicity with Lua

Redis does not provide a single atomic API for the above steps, but a Lua script can combine them:

-- Acquire lock with expiration
if (redis.call('setnx', KEYS[1], ARGV[1]) < 1) then
    return 0;
end
redis.call('expire', KEYS[1], tonumber(ARGV[2]));
return 1;

-- Release lock only if identifier matches
if (redis.call('get', KEYS[1]) == ARGV[1]) then
    return redis.call('del', KEYS[1]);
end
return 0;

Calling this script from Java guarantees that lock acquisition and release are atomic.

4. RedLock Algorithm

To improve reliability in a cluster, Redis proposes the RedLock algorithm, which uses multiple independent Redis nodes (recommended at least five). A client attempts to acquire the same lock on each node; if it succeeds on a majority (N/2 + 1) and the total acquisition time is less than the lock’s TTL, the lock is considered acquired.

Record the current Unix time in milliseconds.

Try to set the same key with a unique value (e.g., UUID) on each of the five nodes, using a short network timeout.

Calculate the elapsed time (now – recorded time).

If a majority of nodes granted the lock and elapsed time < TTL, the lock is successful.

Adjust the effective TTL by subtracting the elapsed time.

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

Thus, RedLock tolerates node failures while preventing split‑brain lock acquisition.

5. Redisson – A High‑Level Java Client

Redisson provides a ready‑made implementation of distributed locks, including support for RedLock, re‑entrancy, automatic retries, and a watchdog mechanism. Example usage:

RLock lock = redissonClient.getLock("myLock");
try {
    boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
    if (locked) {
        System.out.println("Lock acquired, executing business logic...");
    } else {
        System.out.println("Failed to acquire lock.");
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
    redissonClient.shutdown();
}

Redisson stores lock information as a hash {threadId → reentryCount}, enabling true re‑entrancy.

5.1 Watchdog Mechanism

When a lock is held, Redisson starts a background watchdog thread that periodically extends the lock’s expiration (default every 30 seconds). This prevents accidental lock release if the business logic runs longer than the original TTL, while also limiting CPU usage by dynamically adjusting the check interval.

6. Advantages and Disadvantages of Redis‑Based Locks

Pros: High performance, easy to implement with SETNX, avoids single‑point failure when deployed in a cluster.

Cons: Choosing an appropriate TTL is difficult; asynchronous master‑slave replication can cause lock loss during failover; network partitions may lead to split‑brain scenarios where multiple masters grant the same lock.

7. Cluster‑Related Issues

In a master‑slave setup, if a lock is set on the master but not yet replicated, a master failure can cause the lock to disappear. Network partitions (brain split) can create two masters, allowing two clients to hold the same lock simultaneously.

8. Summary

Lock acquisition and release must be performed atomically (e.g., via Lua scripts).

Each lock key should have an expiration to avoid deadlocks.

The lock value must uniquely identify the client to prevent accidental releases.

By combining identifier‑based unlocking, atomic Lua scripts, the RedLock algorithm, or using a mature client like Redisson, developers can build reliable distributed locks on top of Redis.

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.

BackendJavaconcurrencyredisdistributed-lockredissonRedlock
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.