Backend Development 16 min read

Implementing Distributed Locks with Redis: From Basic to Advanced Schemes

This article examines the shortcomings of local locking in microservices, introduces distributed locking concepts, and walks through five progressive Redis-based lock implementations—from a simple SETNX bronze approach to a Lua‑scripted diamond solution—detailing their mechanisms, code examples, and trade‑offs.

Wukong Talks Architecture
Wukong Talks Architecture
Wukong Talks Architecture
Implementing Distributed Locks with Redis: From Basic to Advanced Schemes

1. Problem with Local Locks

Local locking (e.g., synchronized or lock ) works for a single JVM but fails in distributed microservice environments, leading to data inconsistency when multiple services update the same cache key concurrently.

In a scenario with four microservices handling 10 W requests, each service may lock its own thread while accessing the database, causing cache breakdown and inconsistent results.

2. What Is a Distributed Lock?

A distributed lock ensures that only one thread across a cluster can access a critical section at a time. The lock can be visualized as a door that only one person can open; others must wait until the lock is released.

3. Redis SETNX

Redis can serve as a shared place to store lock state. The SETNX command (SET if Not eXist) sets a key only when it does not already exist.

set <key> <value> NX

Example using the Redis CLI:

docker exec -it <container_id> redis-cli
set wukong 1111 NX

The command returns OK on success and nil on failure.

4. Bronze Scheme (Simple SETNX)

Use SETNX to acquire a lock; if successful, execute business logic and then delete the lock.

// 1. Try to acquire lock
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123");
if (lock) {
    // 2. Business logic
    List
list = getDataFromDB();
    // 3. Release lock
    redisTemplate.delete("lock");
    return list;
} else {
    // 4. Wait and retry
    sleep(100);
    return getTypeEntityListByRedisDistributedLock();
}

Drawback: if the business code throws an exception or the server crashes, the lock is never released, causing a deadlock.

5. Silver Scheme (Lock with Expiration)

After acquiring the lock, set an automatic expiration time so that the lock is cleared even if the holder crashes.

// Set lock and expiration separately
redisTemplate.expire("lock", 10, TimeUnit.SECONDS);

Problem: acquiring the lock and setting its TTL are two separate steps; if a failure occurs between them, the lock may never expire.

6. Gold Scheme (Atomic Set with Expiration)

Combine lock acquisition and expiration into a single atomic command:

# Set key with value and expiration atomically
set <key> <value> PX <milliseconds> NX
# or
set <key> <value> EX <seconds> NX

Example:

# Set key wukong=1111 with 5 s TTL
set wukong 1111 PX 5000 NX
ttl wukong

7. Platinum Scheme (Unique Identifier)

Generate a UUID for each lock instance, store it as the lock value, and only delete the lock if the stored value matches the UUID.

// 1. Generate UUID
String uuid = UUID.randomUUID().toString();
// 2. Acquire lock with TTL and UUID
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
if (lock) {
    // Business logic
    List
list = getDataFromDB();
    // 4. Verify ownership before releasing
    String lockValue = redisTemplate.opsForValue().get("lock");
    if (uuid.equals(lockValue)) {
        redisTemplate.delete("lock");
    }
    return list;
} else {
    // Retry logic
    sleep(100);
    return getTypeEntityListByRedisDistributedLock();
}

Drawback: the check‑then‑delete steps are still non‑atomic; a race condition can cause a lock to be removed by the wrong client.

8. Diamond Scheme (Lua Script for Atomic Unlock)

Use a Redis Lua script to atomically compare the lock value and delete it in one step.

if redis.call('get',KEYS[1]) == ARGV[1] then
    return redis.call('del',KEYS[1])
else
    return 0
end

Execute the script from Java:

String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript
(script, Long.class),
    Arrays.asList("lock"), uuid);

This approach guarantees that lock acquisition, verification, and release are performed atomically, eliminating the race conditions of previous schemes.

9. Summary

The article starts from the limitations of local locks, introduces distributed locking, and presents five Redis‑based solutions—Bronze, Silver, Gold, Platinum, and Diamond—each improving on the previous one by addressing deadlocks, expiration handling, uniqueness, and atomicity.

These patterns illustrate how to reason about concurrency issues in distributed systems and can be adapted to other technologies beyond Redis.

JavaRedisDistributed LockLua ScriptSETNX
Wukong Talks Architecture
Written by

Wukong Talks Architecture

Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.

0 followers
Reader feedback

How this landed with the community

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