Redis Distributed Locks
Almost everyone new to distributed systems runs into this question:
When multiple service instances handle the same job at the same time, how do you prevent data from being processed twice?
For example:
- Users grabbing coupons
- Scheduled task execution
- Inventory deduction
- Order status updates
If the system runs as a single process, it's easy — a local lock (mutex) solves it. But with a microservice architecture or cluster deployment, the problem changes: the system may have 10 service instances, 100 workers, even multiple data centers. Local locks become completely useless, because different processes have no idea about each other's lock state.
This is where the concept of a distributed lock comes in. Its goal is simple:
In a distributed environment, guarantee that only one node can execute a given piece of logic at any moment.
Why can Redis serve as a distributed lock?
When implementing a distributed lock, many people's first instinct is the database, for example:
-- Mutual exclusion via database row locks
select ... for update
But database locks have problems: poor performance, coarse granularity, and heavy pressure under high concurrency. So people looked for a system better suited to locking — and Redis fits very well, because it naturally has three advantages.
1) Single-threaded model
Redis executes its core commands on a single thread — only one command runs at a time. So an operation like:
# NX: write only if the key doesn't exist — inherently mutually exclusive
SET key value NX
is an absolutely atomic operation, with no possibility of a race condition.
2) In-memory operations, extremely fast
All Redis data lives in memory. A lock operation is usually just one SET and one DEL, with latency typically in the tens of microseconds — far faster than a database.
3) Built-in expiration
Redis keys can carry a TTL, for example:
# NX: create only if absent; EX 10: auto-expire after 10 seconds
SET lock:order 123 NX EX 10
This way, even if the service crashes, the lock won't be stuck forever.
Releasing a Redis lock
You can't just DEL to release a lock, because this can happen:
Thread A acquires the lock
Thread A runs past the timeout
The lock expires
Thread B acquires the lock
Thread A finishes and deletes the lock
Now thread A has mistakenly deleted thread B's lock. The correct approach is to only delete the lock you actually hold, usually via a Lua script:
-- Compare the lock's value (unique identifier) first; delete only on match,
-- ensuring you only release your own lock
if redis.call("GET",KEYS[1]) == ARGV[1] then
return redis.call("DEL",KEYS[1])
else
return 0
end
Lua scripts execute atomically as a whole in Redis — no other command can slip in between the GET and the DEL — which is what makes the release safe.
Classic problems with Redis distributed locks
Although a Redis lock is simple, real systems hit plenty of pitfalls. The two most common are below.
1) Lock expiration
If the business logic runs longer than the TTL, the lock is released early, and other nodes may enter concurrently. The fix is automatic renewal (WatchDog). For example:
- Set the lock TTL to 10 seconds
- When the remaining validity enters its last 30%, renew once automatically, with random jitter added so that renewal requests don't all hammer Redis at the same instant
As long as the task is still running, the lock never expires.
2) Redis as a single point of failure
If Redis goes down, every lock is lost. Common solutions:
- Redis Sentinel
- Redis Cluster
- The RedLock algorithm
Redis's AP design versus the CP architecture of etcd and ZooKeeper
Redis is fundamentally an AP (availability-first) system: during network partitions or node failures, Redis prioritizes keeping the service available over keeping data strictly consistent. This introduces a risk — in extreme cases, two clients can both believe they hold the lock.
For example:
Client A acquires the lock on the master node
The master hasn't yet replicated it to the replica
The master suddenly crashes
The replica is promoted to the new master
The new master has no idea A ever took the lock, so client B can acquire it again — and now two clients hold the lock simultaneously. This is the theoretical consistency risk of Redis distributed locks.
By contrast, systems like etcd and ZooKeeper are built on the CP model (strong consistency), using the Raft or ZAB protocol internally to commit data only after a majority of nodes confirm it. Once a lock is acquired, the entire cluster agrees on it, and multiple clients can never hold the lock at once.
Hence a common rule of thumb in practice: for business-level locks (inventory, task control, and the like), a Redis distributed lock is usually good enough; but for scenarios demanding strong consistency — financial transactions, global schedulers, distributed coordination services — many systems opt for strongly consistent lock implementations on etcd / ZooKeeper.
Wrapping up
The core idea of a Redis distributed lock is dead simple: use Redis's atomic operations to achieve mutual exclusion. It comes down to three key points: atomic acquisition, safe release, and automatic expiration or renewal. In practice, a mature Redis lock also adds retry logic, WatchDog auto-renewal, Lua-based safe release, and a unique lock identifier. And you have to stay clear about its AP nature — good enough for business-level mutual exclusion, but for strong-consistency scenarios, look to etcd / ZooKeeper. Only with all of these pieces combined does it become a genuinely stable, reliable distributed locking system.
COMMENTS