10 Hidden Pitfalls of Redis Distributed Locks and How to Avoid Them
This article dissects ten common traps when implementing Redis distributed locks—such as non‑atomic setnx/expire, missing expirations, lock release ordering, non‑re‑entrancy, master‑slave replication issues, and the Redlock algorithm—while providing concrete code examples and practical remedies.
In high‑traffic scenarios such as flash‑sale seckill, developers often use Redis as a distributed lock to prevent overselling, but naïve implementations contain many hidden traps.
1. Non‑atomic setnx + expire
The common pattern uses SETNX to acquire the lock and then EXPIRE to set a timeout. Because the two commands are executed separately, a crash between them leaves the key without an expiration, turning the lock into a “immortal” lock that blocks all other clients.
if (jedis.setnx(lock_key, lock_value) == 1) {
jedis.expire(lock_key, timeout);
doBusiness();
}2. Overwritten by another client (setnx + value = expire‑time)
One workaround stores the absolute expiration timestamp as the value. When SETNX fails, the client reads the stored timestamp and, if it is already past the current time, attempts to replace it with GETSET. If several clients race to call GETSET, only one succeeds, but the winning client may inherit a timestamp that another client later overwrites, breaking safety.
long expireTime = System.currentTimeMillis() + timeout;
String expireTimeStr = String.valueOf(expireTime);
if (jedis.setnx(lock_key, expireTimeStr) == 1) {
return true;
}
String oldExpireTimeStr = jedis.get(lock_key);
if (oldExpireTimeStr != null && Long.parseLong(oldExpireTimeStr) < System.currentTimeMillis()) {
String oldValueStr = jedis.getSet(lock_key, expireTimeStr);
if (oldValueStr != null && oldValueStr.equals(oldExpireTimeStr)) {
return true;
}
}
return false;3. Forgetting to set expiration
If the EXPIRE step is omitted and the process crashes before the finally block runs, the lock remains forever, causing a deadlock.
4. Forgetting to release the lock after business logic
Even when a timeout is set, failing to call unlock forces the lock to live until the timeout expires, reducing throughput.
5. Releasing a lock owned by another thread
When thread A’s lock expires and thread B acquires it, A may still execute unlock and unintentionally delete B’s lock.
6. Unlock operation is not atomic
Checking the lock value and then deleting it are two separate commands. If the lock expires between the check and the delete, a client may delete a lock that it does not own. The usual remedy is a Lua script that performs the check‑and‑delete atomically:
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end7. Lock expiration before business finishes
When the timeout is shorter than the business execution time, Redis automatically releases the lock while the work is still running. Extending the timeout manually is error‑prone; Redisson’s watchdog thread automatically renews the TTL every 10 seconds while the lock is held.
8. Using Redis lock together with @Transactional
If the lock is acquired inside a Spring transaction, the lock may be released before the transaction commits, allowing another thread to read stale data. The correct order is to acquire the lock **before** the transaction starts.
9. Re‑entrant lock requirement
Standard Redis locks are non‑re‑entrant: the same thread cannot acquire the same lock twice. For scenarios that need re‑entrancy, either implement a lock that tracks owner ID and recursion count (similar to Java’s ReentrantLock) or use Redisson, which provides a built‑in re‑entrant lock.
10. Master‑slave replication pitfalls and Redlock
In a clustered Redis deployment, a client may acquire a lock on the master node, but before the key replicates to the slave the master crashes. The slave is promoted, and another client can acquire the same key, breaking mutual exclusion. Antirez’s Redlock algorithm mitigates this by requiring a majority of independent Redis masters (e.g., 5 masters) to grant the lock within a bounded time. The algorithm steps are:
Record the current time in milliseconds.
Attempt to acquire the lock on each master with a short network timeout (e.g., 50 ms for a 10 s lock).
Count how many masters granted the lock; if at least N/2 + 1 (e.g., 3 out of 5) succeeded and the total elapsed time is less than the lock TTL, consider the lock acquired.
If acquisition fails, release the lock on all masters to avoid stray locks.
By following these guidelines and using proven libraries such as Redisson, developers can avoid the most common Redis distributed‑lock failures.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
