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.

Architect
Architect
Architect
10 Hidden Pitfalls of Redis Distributed Locks and How to Avoid Them

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
end

7. 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend Developmentredisdistributed-lockredissonRedlock
Architect
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.