Mastering Distributed Locks with Redis: Techniques, Commands, and Best Practices
This article explains the concept of distributed locks, outlines various implementation strategies, and dives deep into Redis-based solutions—including SETNX, GET, GETSET, EXPIRE, and Lua scripting—to achieve mutual exclusion, safety, reentrancy, and automatic lock release in high‑performance systems.
Understanding Distributed Locks
1.1 What Is a Distributed Lock
A distributed lock is a lock in a distributed system used to control access to shared resources, ensuring that only one thread can access the protected resource at a time.
1.2 Implementation Approaches
Database-based distributed lock
Zookeeper-based distributed lock
Redis-based distributed lock
This article focuses on the Redis implementation.
1.3 Desired Properties of Distributed Locks
Mutual exclusion: only one thread holds the lock at any moment.
Safety: acquiring and releasing the lock should be straightforward without deadlocks.
Expiration: the lock should automatically release after a timeout to avoid deadlock.
Reentrancy: the same thread can acquire the lock multiple times.
High performance: fast acquisition and release.
High availability: reliable lock acquisition and release.
1.4 Mutual Exclusion
1.3.1 Implementing Mutual Exclusion
1.3.1.1 SETNX Command
SETNX (set if not exists) sets a key only when it does not already exist. It returns 1 if the lock is acquired, 0 otherwise.
<code>SETNX lock.user_063105015 1 # Returns 1: lock acquired</code> <code>SETNX lock.user_063105015 1 # Returns 0: lock already held</code>1.3.1.2 GET Command
GET retrieves the value of a key, returning nil if the key does not exist.
<code>GET lock.user_063105015 # Returns "1"</code> <code>GET lock.user_123456789 # Returns (nil)</code>1.3.1.3 GETSET Command
GETSET atomically sets a new value and returns the previous value.
<code>GETSET lock.user_063105015 0 # Returns "1" (previous value)</code> <code>GETSET lock.user_063105015 1 # Returns "0"</code>1.3.1.4 Delete Command (Unlock)
<code>DEL lock.user_063105015 # Returns 1</code>Typical execution flow:
1.3.1.5 Lock Release on Exceptions
Locks may not be released if the service crashes or the application fails to execute the DEL command, leading to stale locks; therefore an automatic expiration mechanism is required.
1.3.2 Timeout Release
Setting an expiration time (EXPIRE) ensures the lock is automatically released after a predefined period.
<code>SETNX lock.user_063105015 1</code> <code>EXPIRE lock.user_063105015 120 # Auto‑release after 120 seconds</code>Redis provides an extended SET command that atomically sets a value and its expiration, guaranteeing both succeed or both fail.
<code>SET lock.user_063105015 1 NX PX 60000 # Acquire lock with 60 s TTL</code>NX – set only if the key does not exist.
PX 60000 – set expiration to 60 seconds.
1.3.3 Unique Lock Identification
To avoid releasing a lock owned by another process after a timeout, store a unique identifier (e.g., UUID) as the lock value and verify it before deletion.
<code>SET lock.user_063105015 10086 NX PX 60000 # Set lock with unique value</code> <code>GET lock.user_063105015 # Returns "10086"</code> <code>if (redis.get("lock.user_063105015").equals("10086")) {
redis.del("lock.user_063105015"); # Release only if value matches
}</code>1.3.4 Implementing Reentrant Locks
Reentrant locks allow the same thread to acquire the lock multiple times without deadlock. This can be achieved by storing a counter in a Redis hash keyed by a unique thread identifier.
Lua script example (KEYS[1] = lock key, ARGV[1] = lock timeout, ARGV[2] = thread identifier):
<code>if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);</code>Key commands used:
HINCRBY – increment hash field by a number.
PEXPIRE – set key expiration in milliseconds.
HEXISTS – check if a field exists in a hash.
PTTL – get remaining time‑to‑live of a key.
The script first checks if the lock exists; if not, it creates a hash with the thread identifier and sets the counter to 1. If the lock already exists and the identifier matches, it increments the counter and refreshes the TTL. Otherwise, it returns the remaining TTL.
Architecture & Thinking
🍭 Frontline tech director and chief architect at top-tier companies 🥝 Years of deep experience in internet, e‑commerce, social, and finance sectors 🌾 Committed to publishing high‑quality articles covering core technologies of leading internet firms, application architecture, and AI breakthroughs.
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.