How to Implement Distributed Locks with Redis: SETNX vs RedLock

Redis enables distributed locking through either the atomic SETNX command with expiration for single-instance deployments or the RedLock algorithm for fault-tolerant coordination across multiple independent nodes.

The CyC2018/CS-Notes repository documents two robust strategies for implementing Redis distributed locks, ranging from simple single-server setups to complex multi-data-center architectures. These patterns leverage Redis's atomic command execution and Lua scripting capabilities to prevent race conditions and deadlocks in distributed systems.

Single-Instance Locks with SETNX

For scenarios where a single Redis instance is available and network partitions are unlikely, the SETNX (Set-If-Not-Exist) pattern provides a lightweight locking mechanism. As detailed in notes/分布式.md at line 55, this approach relies on atomic key creation with automatic expiration.

Atomic Lock Acquisition

The client generates a unique token (typically a UUID) and attempts to create the lock key using the atomic SET command with the NX (only if Not eXists) and PX (millisecond expiration) flags:

SET lock_key unique_token NX PX 30000

If the command returns success, the client owns the lock for the specified TTL (time-to-live). The PX parameter is critical—it ensures that if the client crashes before releasing the lock, Redis automatically deletes the key after expiration, preventing permanent deadlocks.

Safe Release with Lua Scripting

To prevent a stale client from accidentally deleting a lock held by another process, the release operation must verify ownership. According to the source code analysis in notes/分布式.md, you should use a Lua script that checks the stored value before deleting:

if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end

This script ensures the DEL operation only executes if the lock value matches the client's unique token.

Python Implementation

Here is a complete Python example using redis-py that implements the safe SETNX pattern:

import uuid, time, redis

r = redis.StrictRedis(host='localhost', port=6379, db=0)

LOCK_KEY = 'my_lock'
TOKEN = str(uuid.uuid4())
TTL_MS = 30000          # 30 seconds

def acquire():
    # SET NX PX is atomic

    return r.set(LOCK_KEY, TOKEN, nx=True, px=TTL_MS)

def release():
    # Lua script ensures we delete only our own lock

    script = """
    if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("DEL", KEYS[1])
    else
        return 0
    end
    """
    return r.eval(script, 1, LOCK_KEY, TOKEN)

if acquire():
    try:
        # critical section

        print('Lock acquired')
        time.sleep(5)
    finally:
        release()
else:
    print('Could not acquire lock')

High-Availability Locks with RedLock

When your system requires fault tolerance across data centers or cannot tolerate a single Redis instance as a point of failure, the RedLock algorithm provides a distributed consensus mechanism. As described in notes/分布式.md at line 61, RedLock coordinates lock acquisition across multiple independent Redis masters.

The RedLock Algorithm Requirements

RedLock requires N ≥ 3 independent Redis nodes (typically 5) with no replication or cluster management between them. The client attempts to acquire the lock on each node using the same SET key value NX PX ttl command. To succeed, the client must obtain the lock on at least a majority of nodes—specifically ⌈N/2⌉ + 1—within a specified timeout window.

Fault Tolerance Mechanism

If the client fails to achieve a majority, it immediately releases any partial locks it acquired using the same Lua verification script on each node where it succeeded. This prevents resource starvation and ensures that only complete, majority-validated locks are held. The algorithm tolerates single-node failures and network partitions while maintaining both safety (no two clients hold the lock simultaneously) and liveness (clients eventually acquire the lock).

Python Implementation

The following example uses the redlock-py library to implement the algorithm against three independent Redis instances:

from redlock import Redlock
import uuid

# Connect to three independent Redis instances

dlm = Redlock([
    {"host": "redis1.example.com", "port": 6379, "db": 0},
    {"host": "redis2.example.com", "port": 6379, "db": 0},
    {"host": "redis3.example.com", "port": 6379, "db": 0},
])

LOCK_KEY = "my_redlock"
TTL_MS = 10000

# Acquire lock on a majority of nodes

lock = dlm.lock(LOCK_KEY, TTL_MS)
if lock:
    try:
        print("RedLock acquired")
        # critical section ...

    finally:
        dlm.unlock(lock)
else:
    print("RedLock acquisition failed")

Summary

  • SETNX with expiration provides simple distributed locking for single-Redis deployments but creates a single point of failure if the Redis instance becomes unavailable.
  • RedLock requires 3+ independent Redis nodes and a majority quorum (⌈N/2⌉ + 1) to guarantee fault tolerance across network partitions and node failures.
  • Always use unique tokens (UUIDs) and atomic Lua scripts to verify lock ownership before deletion, preventing stale clients from interfering with active lock holders.
  • Both mechanisms rely on the NX and PX options of the SET command and automatic TTL expiration to ensure locks release eventually, even if clients crash mid-operation.

Frequently Asked Questions

What is the difference between SETNX and RedLock?

SETNX operates against a single Redis instance, making it simple to implement but vulnerable to downtime if that instance fails. RedLock distributes the lock across multiple independent Redis nodes and requires a majority quorum to acquire the lock, providing fault tolerance at the cost of increased complexity and latency. The choice depends on whether your architecture can tolerate the Redis server as a single point of failure.

How do you prevent a stale client from unlocking a newer lock holder?

Both mechanisms store a unique identifier (usually a UUID) as the lock value. When releasing the lock, a Lua script verifies that redis.call("GET", KEYS[1]) matches the client's token (ARGV[1]) before executing DEL. This check ensures only the original owner can release the lock, preventing race conditions where a delayed client might otherwise delete a valid lock held by another process.

Why does RedLock require multiple Redis nodes?

RedLock requires N ≥ 3 independent Redis nodes (often 5) to tolerate network partitions and node failures. By requiring a majority (⌈N/2⌉ + 1) of nodes to acknowledge the lock acquisition, the algorithm ensures that even if some nodes become unreachable, the system maintains safety properties and avoids split-brain scenarios where two clients believe they hold the same lock simultaneously.

What happens if a Redis lock client crashes mid-operation?

If a client crashes after acquiring a lock but before releasing it, the PX (millisecond) or EX (second) expiration parameter on the SET command ensures the key automatically deletes after the TTL expires. This prevents permanent deadlocks by guaranteeing the lock releases eventually, allowing other clients to acquire it once the timeout elapses. This automatic cleanup is referenced in notes/Redis.md at line 368 as a core safety feature of Redis-based locking.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →