Implement mutual exclusion across distributed processes using SET key value NX PX timeout for atomic lock acquisition with automatic expiration.
Distributed systems often need mutual exclusion to prevent race conditions when multiple processes access shared resources. This pattern covers single-instance locks, lock release with owner verification, and lock extension.
Redis locks use the SET command with special options:
SET resource:lock <token> NX PX 30000
This command:
- Sets the key only if it doesn't exist (NX)
- Sets a 30-second expiration (PX 30000)
- Stores a unique token to identify the lock owner
If the command returns OK, the lock was acquired. If it returns nil, another client holds the lock.
NX (Not Exists): Ensures only one client can acquire the lock. If two clients try simultaneously, only one succeeds.
PX (Expiration): Prevents deadlocks. If the lock holder crashes without releasing the lock, it automatically expires. Without expiration, a crashed client would hold the lock forever.
Token: A random value (typically a UUID) that identifies this specific lock acquisition. This is crucial for safe release.
Never release a lock with a simple DEL command:
DEL resource:lock # DANGEROUS!
This risks deleting another client's lock. Consider this scenario:
Use a Lua script that checks the token before deleting:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
Execute with:
EVAL <script> 1 resource:lock abc-token-123
The lock is only deleted if the token matches, preventing accidental deletion of another client's lock.
SET resource:lock <token> NX PX 30000When lock acquisition fails, clients have several options:
For operations that may take longer than expected, extend the lock before it expires:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("pexpire", KEYS[1], ARGV[2])
else
return 0
end
This resets the TTL only if you still own the lock.
Modern alternative (Redis 6.2+): Use SET with GET and KEEPTTL options for atomic read-modify operations:
SET resource:lock <new_token> XX GET
The XX ensures the key exists, and GET returns the old value. Your application verifies the returned token matches before considering the lock extended. However, the Lua script approach remains cleaner for conditional TTL updates.
Single-instance Redis locking has an important limitation: if the Redis master fails after a client acquires a lock but before the lock is replicated to a replica, the lock may be lost during failover. Two clients could then believe they hold the same lock.
For applications requiring stronger guarantees, consider: - The Redlock algorithm (acquiring locks from multiple independent Redis instances) - Using a consensus system like etcd or ZooKeeper - Accepting the rare possibility of lock violations and designing for idempotency
No expiration: Lock held forever if client crashes.
SET lock token NX # Missing PX - deadlock risk!
Simple DEL for release: May delete another client's lock.
Non-atomic acquire: Using separate SETNX and EXPIRE commands creates a race condition.
SETNX lock token # If crash here...
EXPIRE lock 30 # ...lock has no expiration!
Always use the combined SET command with NX and PX.