Master Redis Distributed Locks: From SETNX Basics to Redisson and RedLock

This guide explains why distributed locks are needed, compares three implementation stages—from basic SETNX+EXPIRE to atomic SET NX EX with Lua scripts, and finally Redisson and RedLock—provides code samples, interview questions, and practical tips for Java developers.

Java Architect Handbook
Java Architect Handbook
Java Architect Handbook
Master Redis Distributed Locks: From SETNX Basics to Redisson and RedLock

Evolution Stages of Redis Distributed Locks

Redis distributed lock implementations have evolved through three major stages:

Basic – SETNX + EXPIRE. Simple but non‑atomic; a crash between the two commands can leave the lock permanent, causing dead‑locks.

Intermediate – SET key value NX EX for atomic acquisition and a Lua script for safe release. Guarantees atomic lock/unlock, but the lock is not re‑entrant and has no automatic renewal.

Advanced – Redisson framework (recommended for production). Provides re‑entrancy, automatic lease renewal via a watchdog, and uses Lua scripts internally to ensure atomic release.

Why Distributed Locks Are Needed

Single‑process locks such as synchronized or ReentrantLock only work within one JVM. A distributed lock stores the lock state in a shared storage (typically Redis) so that multiple processes can compete for the same lock.

Five essential properties of a correct distributed lock:

Mutual exclusion : only one client may hold the lock at any time.

Dead‑lock prevention : the lock must have a timeout so it is released if the holder crashes.

Ownership : only the client that acquired the lock may release it.

High availability : the lock service itself must not be a single point of failure.

High performance : acquisition and release overhead should be minimal.

Basic Implementation – SETNX + EXPIRE (Flawed)

// ❌ Non‑atomic example – if the process crashes after SETNX
// the lock never expires → dead‑lock
jedis.setnx("lock", "1");
jedis.expire("lock", 10);

The two commands are executed separately, so a crash between them leaves the lock without an expiration.

Intermediate Implementation – Atomic SET + Lua Release

Since Redis 2.6.12 the SET command supports NX (set only if not exists) and EX (expire) flags, allowing a single‑command atomic lock:

String lockValue = UUID.randomUUID().toString();
jedis.set("lock", lockValue, SetParams.setParams().nx().ex(10));

Release must also be atomic. The safe way is a Lua script that deletes the key only if the stored value matches the owner’s identifier:

String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
             "return redis.call('del', KEYS[1]) " +
             "else return 0 end";
jedis.eval(lua, Collections.singletonList("lock"), Collections.singletonList(lockValue));

Lua runs single‑threaded inside Redis, guaranteeing atomic check‑and‑delete.

Remaining limitations:

Not re‑entrant – the same thread cannot reacquire the lock.

No automatic lease renewal – if business logic exceeds the TTL, the lock expires and other clients may acquire it.

Advanced Implementation – Redisson (Production‑Ready)

Redisson is a Java client that abstracts the above details and adds:

Re‑entrancy : uses a Redis Hash where the field stores a thread identifier and the value stores the re‑entry count. The lock is released only when the count reaches zero.

Watchdog automatic renewal : default lock TTL is 30 s; a background watchdog thread checks every 10 s (one‑third of TTL) and extends the lease while the lock is still held.

Typical Maven dependency:

// implementation 'org.redisson:redisson-spring-boot-starter:3.27.0'

Example usage in a Spring Boot controller:

@RestController
public class OrderController {
    @Autowired
    private RedissonClient redissonClient;

    @GetMapping("/order")
    public String createOrder() {
        RLock lock = redissonClient.getLock("order:lock");
        try {
            // wait up to 3 s, lock auto‑renews for 30 s
            boolean acquired = lock.tryLock(3, 30, TimeUnit.SECONDS);
            if (!acquired) {
                return "Failed to acquire lock, please retry later";
            }
            // business logic
            doBusiness();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return "Order placed successfully";
    }
}

RedLock Algorithm (Multi‑Node Solution)

Single‑node Redis can lose locks during master‑slave failover. RedLock mitigates this by requiring a majority of independent Redis instances (commonly 5) to grant the lock:

Client sends a lock request to all instances.

Lock is considered acquired only if > N/2 instances grant it.

The algorithm is debated (e.g., Martin Kleppmann’s critique). In most production environments a single‑node Redisson or a Sentinel‑based setup is sufficient; for ultra‑high reliability consider ZooKeeper or etcd.

Common Interview Follow‑Ups (Technical)

When does Redisson’s watchdog fail? The watchdog is disabled if a lease time is supplied explicitly via lock.lock(leaseTime, …) or lock.tryLock(..., leaseTime, …). It also stops if the owning process is killed abruptly (e.g., kill -9), because the watchdog thread terminates.

Redis vs. ZooKeeper distributed locks

Performance : Redis – in‑memory operations, microsecond latency; ZooKeeper – cluster communication, millisecond latency.

Reliability : Redis may lose a lock on master‑slave switch; ZooKeeper provides strong consistency (ZAB) and does not lose locks.

Implementation : Redis uses SETNX + expiration (or Redisson); ZooKeeper uses ephemeral sequential nodes with watches.

Use case : Redis for high‑throughput scenarios where occasional lock loss is acceptable; ZooKeeper for critical sections that must never lose mutual exclusion.

Memory Mnemonic – Three‑Step Evolution

Basic : SETNX + EXPIRE → non‑atomic, possible dead‑lock.

Intermediate : SET NX EX + Lua release → atomic, but not re‑entrant and no renewal.

Advanced : Redisson → hash‑based re‑entrancy + watchdog renewal, production‑grade.

Watchdog core : default TTL 30 s, renewal interval 10 s (1/3 of TTL). If a custom lease time is set, the watchdog does not run.

Final Summary

Redis distributed locks progressed from the non‑atomic SETNX + EXPIRE approach, to the atomic SET NX EX with Lua‑based release, and finally to the Redisson framework that offers re‑entrancy, automatic lease renewal, and atomic operations via Lua. For production use Redisson directly; for scenarios demanding the highest reliability consider the RedLock multi‑node algorithm or alternative consensus‑based services such as ZooKeeper or etcd.

JavaRedisinterviewDistributed Lockredisson
Java Architect Handbook
Written by

Java Architect Handbook

Focused on Java interview questions and practical article sharing, covering algorithms, databases, Spring Boot, microservices, high concurrency, JVM, Docker containers, and ELK-related knowledge. Looking forward to progressing together with you.

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.