Choosing the Right Redis Distributed Lock: V1‑V3.1, Redlock and Their Pitfalls
This article reviews the evolution of Redis‑based distributed lock implementations—from the simple V1.0 pattern to the more robust V3.1 and Redlock algorithm—highlighting their mechanisms, common pitfalls, and guidance on selecting the most suitable lock for a given scenario.
Various Redis Distributed Lock Versions
V1.0
tryLock(){
SETNX Key 1
EXPIRE Key Seconds
}
release(){
DELETE Key
}The simplest and most widely seen version adds an expiration to avoid permanent lock retention after a crash, but it suffers from two major issues: each command is a separate request, so a crash between them can leave the lock without an expiry, and many developers mistakenly release the lock unconditionally in a finally block.
Using a Lua script to combine SETNX and EXPIRE can mitigate the atomicity problem, yet a crash after only one command still leaves the lock without a timeout.
Incorrect unconditional release can cause a client to delete a lock it never acquired.
A workaround based on GETSET can address the release‑failure scenario.
V1.1 – Based on GETSET
tryLock(){
NewExpireTime = CurrentTimestamp + ExpireSeconds
if (!SET Key NewExpireTime Seconds NX) {
oldExpireTime = GET(Key)
if (oldExpireTime < CurrentTimestamp) {
NewExpireTime = CurrentTimestamp + ExpireSeconds
CurrentExpireTime = GETSET(Key, NewExpireTime)
if (CurrentExpireTime == oldExpireTime) {
return 1;
} else {
return 0;
}
}
}
return 0;
}
release(){
DELETE key
}This version removes the explicit EXPIRE command and relies on the stored timestamp to decide expiration. SETNX(Key, ExpireTime) attempts to acquire the lock.
If acquisition fails, GET(Key) is used to check whether the existing lock has expired. GETSET(Key, NewExpireTime) atomically updates the value.
If the value returned by GETSET matches the one read earlier, the lock is considered successfully acquired.
Problems: Under high contention the value may be overwritten repeatedly without any client actually obtaining the lock; also, a client that modifies the lock’s timestamp while another client holds it can extend the lock’s lifetime unintentionally.
V2.0 – Based on SETNX (Redis 2.6.12+)
tryLock(){
SET Key 1 Seconds NX
}
release(){
DELETE Key
}Since Redis 2.6.12, SET supports the NX flag with an expiration, making the operation atomic. However, scenarios such as a long GC pause can still cause two clients (C1 and C2) to hold the lock simultaneously, leading to data inconsistency.
V3.0 – Timestamp + Lua Release
tryLock(){
SET Key UnixTimestamp Seconds NX
}
release(){
EVAL "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 KEY UnixTimestamp
}The lock value is a Unix timestamp; the release script checks that the stored value matches the one set by the acquiring client, preventing a client from deleting a lock it does not own. Lua ensures the check‑and‑delete is atomic.
Potential issue: In extremely high‑concurrency scenarios (e.g., “red‑packet” bursts) timestamps may collide, and clock skew across nodes can cause duplicate timestamps.
V3.1 – Unique ID + Lua Release
tryLock(){
SET Key UniqId Seconds
}
release(){
EVAL "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 KEY UniqId
}After Redis 2.6.12, SET also supports the NX flag, allowing a unique identifier (e.g., a UUID) to replace the timestamp, eliminating clock‑related problems. This is currently regarded as the most reliable single‑instance Redis lock.
In a Redis cluster, however, asynchronous replication can still cause multiple clients to acquire the lock if the master crashes before data is replicated.
Distributed Redis Lock: Redlock
Redlock, proposed by Antirez, targets multi‑node Redis deployments. The algorithm works as follows:
Record the current time in milliseconds.
Attempt to acquire a lock on each of the N independent Redis nodes using a random value and a short TTL (e.g., PX 30000).
If a node is unavailable or the lock is already held, move to the next node.
Measure the total time spent acquiring locks. If the client succeeds on at least N/2 + 1 nodes and the elapsed time is less than the lock’s TTL, the lock is considered acquired.
Otherwise, the client releases any partial locks using the same Lua script.
When releasing, the client deletes the lock on all nodes.
Critics such as Martin Kleppmann argue that Redlock assumes synchronized clocks across nodes, which is unrealistic, and that its reliance on automatic expiration does not solve issues like long GC pauses. Antirez responded by defending the expiry model and describing how to handle pause‑induced simultaneous accesses.
Summary
Both single‑instance Redis locks (based on SETNX or unique IDs) and the Redlock algorithm aim to provide three essential properties:
Safety: No two clients should hold the lock at the same time.
Liveness (dead‑lock freedom): The lock must eventually be released, even if a client crashes or a network partition occurs.
Fault tolerance: As long as a majority of Redis nodes remain reachable, the lock can be correctly acquired and released.
Choosing the appropriate lock version depends on the required balance between efficiency and strict correctness. Simpler locks (e.g., V2.0) are suitable when only liveness matters, while V3.1 or Redlock are preferable for strict mutual exclusion in distributed environments.
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.
dbaplus Community
Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.
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.
