Distributed Locks
A distributed lock lets multiple processes or servers agree that only one of them may access a shared resource at a time. Redis makes this straightforward with a single atomic command — no separate watch or transaction needed.
Acquiring the lock
Section titled “Acquiring the lock”Use SET lock:resource token NX PX 30000 to acquire a lock:
- NX — only set the key if it does not already exist (atomic acquire)
- PX 30000 — auto-release after 30 000 ms even if the holder crashes
- token — a unique value (UUID) that only the lock holder knows; you will need it to release safely
127.0.0.1:6379> SET lock:payment "token-uuid-abc" NX PX 30000OK127.0.0.1:6379> TTL lock:payment(integer) 29127.0.0.1:6379> SET lock:payment "token-uuid-xyz" NX PX 30000(nil)The first call succeeds and returns OK. The TTL confirms the auto-expiry is in place. A second attempt by a different holder returns (nil) — the key already exists, so NX prevents overwriting it.
SET lock:payment "token-uuid-abc" NX PX 30000
TTL lock:payment
SET lock:payment "token-uuid-xyz" NX PX 30000Releasing the lock safely
Section titled “Releasing the lock safely”The wrong way: DEL lock:payment — this deletes the key unconditionally. If your lock TTL expired and another worker already acquired it, you just deleted their lock. This causes a race condition.
The right way: only delete if the stored token matches yours. Because a plain GET followed by a conditional DEL is two commands (not atomic), you must use a Lua script — Redis executes Lua scripts atomically:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1])else return 0endHere is the same logic inlined into an EVAL call:
127.0.0.1:6379> EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) end return 0" 1 lock:payment "token-uuid-abc"(integer) 1127.0.0.1:6379> GET lock:payment(nil)127.0.0.1:6379> EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) end return 0" 1 lock:payment "wrong-token"(integer) 0The first EVAL matches the correct token, deletes the key, and returns 1. After deletion, GET returns (nil). The second EVAL with a wrong token returns 0 — the key is untouched.
SET lock:payment "token-uuid-abc" NX PX 30000
EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) end return 0" 1 lock:payment "token-uuid-abc"
GET lock:payment
SET lock:payment "token-uuid-abc" NX PX 30000
EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) end return 0" 1 lock:payment "wrong-token"Redlock for multi-node setups
Section titled “Redlock for multi-node setups”For a single Redis node, SET NX PX is sufficient. For high availability across multiple Redis nodes — to survive a node failure without falsely releasing a lock — use the Redlock algorithm: try to acquire the lock on N independent nodes and consider it held only when a majority (N/2+1) confirm the acquisition within a time bound. Libraries like ioredis-redlock (Node.js) implement this algorithm so you do not have to coordinate the quorum logic yourself.