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.
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> NXExample using the Redis CLI:
docker exec -it <container_id> redis-cli set wukong 1111 NXThe 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> NXExample:
# Set key wukong=1111 with 5 s TTL
set wukong 1111 PX 5000 NX
ttl wukong7. 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
endExecute 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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.