Implementing Distributed Locks with Redis: Principles, Challenges, and Optimizations
This article explains why traditional local locks fail in distributed systems, surveys common distributed‑lock approaches, and provides a step‑by‑step guide to building a robust Redis‑based lock in Java—including expiration handling, UUID safety, Lua‑script atomicity, re‑entrancy, automatic renewal, and the RedLock algorithm—while comparing its performance against plain local locks.
Introduction
When a monolithic application evolves into a distributed cluster, local JVM locks become ineffective because multiple processes run on different machines. A cross‑JVM mutual‑exclusion mechanism, i.e., a distributed lock, is required to protect shared resources.
Common Distributed‑Lock Implementations
Database‑based locks
Cache‑based locks (Redis, Memcached) – this article focuses on Redis
Zookeeper‑based locks
Each solution has trade‑offs in performance, safety, and implementation difficulty (Redis > Zookeeper > MySQL for speed; Zookeeper > Redis ≈ MySQL for safety).
Key Characteristics of a Distributed Lock
Exclusive mutual exclusion (e.g., SETNX or SET key value EX 3 NX)
Dead‑lock prevention via expiration
Atomicity of lock acquisition and release (Lua scripts)
Protection against accidental deletion (store a UUID per lock)
Automatic renewal
Re‑entrancy support (hash + Lua)
Cluster‑wide safety (RedLock algorithm)
Basic Redis Lock Using SETNX
The simplest lock uses SETNX to create a key only if it does not exist. Multiple clients compete; only one succeeds.
public void testLock() {
Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", "111");
if (lock) {
// business logic
this.redisTemplate.delete("lock");
} else {
// retry after 1s
Thread.sleep(1000);
testLock();
}
}This approach fails if the client crashes after acquiring the lock because the lock is never released.
Adding Expiration
Set an expiration atomically with SET key value EX 3 NX to avoid dead locks, but if the business logic runs longer than the TTL the lock may be released prematurely.
public void testLock() {
Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", "111", 3, TimeUnit.MINUTES);
if (lock) {
// business logic
} else {
// retry
}
}Preventing Wrong Deletion with UUID
Store a unique UUID as the lock value; before deleting, compare the stored value with the UUID to ensure the lock belongs to the current client.
String uuid = UUID.randomUUID().toString();
Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.MINUTES);
if (lock) {
// business logic
if (StringUtils.equals(redisTemplate.opsForValue().get("lock"), uuid)) {
this.redisTemplate.delete("lock");
}
}Atomic Release with Lua
Lua scripts guarantee that the check‑and‑delete operation is atomic.
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);Re‑entrant Lock Using Redis Hash
Store the lock owner UUID and a counter in a hash. Increment the counter on each re‑entry and decrement on unlock; when the counter reaches zero, delete the hash.
if (redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[1], 1);
redis.call('expire', KEYS[1], ARGV[2]);
return 1;
else
return 0;
endAutomatic Renewal (Watch‑Dog)
A background thread periodically extends the TTL while the lock holder is still alive.
private void renewTime(String lockName, String uuid, Long expire) {
String script = "if(redis.call('hexists', KEYS[1], ARGV[1]) == 1) then redis.call('expire', KEYS[1], ARGV[2]); return 1; else return 0; end";
new Thread(() -> {
while (this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
Lists.newArrayList(lockName), uuid, expire.toString())) {
Thread.sleep(expire / 3);
}
}).start();
}RedLock Algorithm for Redis Cluster
In a clustered environment, acquire the lock on multiple independent Redis masters and consider the lock successful only if a majority of instances grant it, mitigating the risk of a master failure before replication.
Performance Comparison
Benchmarks using ab show that pure local locks work only within a single JVM, while the Redis‑based distributed lock maintains correctness across multiple service instances, albeit with lower throughput than a single‑node local lock.
Conclusion
By combining expiration, UUID verification, Lua‑script atomicity, re‑entrancy via hash, automatic renewal, and the RedLock algorithm, developers can build a reliable distributed lock on Redis that overcomes the shortcomings of local JVM locks in modern micro‑service architectures.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.
