10 Hidden Pitfalls of Using Redis Distributed Locks (and How to Avoid Them)

This article examines ten common traps when implementing Redis distributed locks—ranging from non‑atomic setnx/expire operations and missing expirations to lock release ordering, re‑entrancy, master‑slave replication issues, and the Redlock algorithm—providing concrete code examples, explanations, and best‑practice solutions.

ITPUB
ITPUB
ITPUB
10 Hidden Pitfalls of Using Redis Distributed Locks (and How to Avoid Them)

Introduction

In high‑concurrency scenarios such as flash‑sale systems, developers often rely on Redis distributed locks to prevent overselling, but many subtle pitfalls can compromise safety and correctness. The following sections detail ten typical problems, illustrate them with Java/Jedis code, and suggest reliable fixes.

1. Non‑atomic setnx + expire

Using SETNX to acquire a lock and then EXPIRE to set its TTL is not atomic. If the process crashes after SETNX but before EXPIRE, the lock becomes permanent and blocks other threads.

if (jedis.setnx(lock_key, lock_value) == 1) {
    jedis.expire(lock_key, timeout);
    doBusiness // business logic
}

2. Overwrite by another client (setnx with expiration time as value)

A workaround stores the expiration timestamp as the value of SETNX. When acquiring the lock, the client compares the stored timestamp with the current time to decide whether the lock has expired.

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;

This approach still suffers when multiple clients race to call GETSET; the lock’s expiration may be overwritten by another client.

3. Forgetting to set expiration

If a lock is created without an expiration and the process crashes before releasing it, the lock remains forever, causing a deadlock. Always set a TTL when using SETNX or the extended SET command.

4. Forgetting to release the lock after business processing

Even when a TTL is set, relying solely on expiration is inefficient. The lock should be explicitly released as soon as the protected business logic finishes.

if (jedis.set(lockKey, requestId, "NX", "PX", expireTime) == 1) {
    doBusiness // business logic
    return true;
}
return false;

The correct pattern wraps the lock acquisition in a try block and releases the lock in a finally clause.

try {
    if (jedis.set(lockKey, requestId, "NX", "PX", expireTime) == 1) {
        doBusiness // business logic
        return true;
    }
    return false;
} finally {
    unlock(lockKey);
}

5. Lock released by another thread (A releases B’s lock)

In a scenario where thread A acquires a lock with a 3‑second TTL, the lock expires before A finishes its long‑running task. Thread B then acquires the same lock. When A finally releases the lock, it unintentionally removes B’s lock.

The fix is to store a unique requestId with the lock and, upon release, verify that the stored value matches the requester’s ID.

try {
    if (jedis.set(lockKey, requestId, "NX", "PX", expireTime) == 1) {
        doBusiness // business logic
        return true;
    }
    return false;
} finally {
    if (requestId.equals(jedis.get(lockKey))) {
        unlock(lockKey);
    }
}

6. Non‑atomic unlock

Checking the lock owner and deleting the key are two separate operations, which can race with another client that has already acquired the lock after expiration. Use a Lua script to perform 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 expires before business completes

If the TTL expires while the protected operation is still running, other threads may acquire the lock prematurely, leading to inconsistent state. One solution is to run a watchdog thread that periodically extends the TTL while the lock holder is still active. The Redisson client implements this “watch‑dog” mechanism automatically.

Redisson watch‑dog diagram
Redisson watch‑dog diagram

8. Interaction with @Transactional

When a Spring @Transactional method acquires a Redis lock, the lock may be released before the transaction commits because Spring opens the transaction before the method execution and commits after it returns. This can cause other threads to acquire the lock while the original transaction is still pending.

The correct approach is to acquire the Redis lock **before** the transaction starts, ensuring the lock remains held for the entire transaction duration.

9. Re‑entrancy

Standard Redis locks are non‑re‑entrant: a thread that already holds the lock will block if it tries to acquire it again. Some use‑cases require re‑entrancy, which can be achieved by tracking the owning thread ID and a lock‑count, similar to Java’s ReentrantLock. Redisson provides a built‑in re‑entrant lock implementation.

10. Master‑slave replication pitfalls

In a Redis cluster, a client may acquire a lock on a master node, but the lock key might not yet be replicated to slaves. If the master fails before replication, a slave is promoted, and another client can acquire the same lock, breaking mutual exclusion.

Antirez’s Redlock algorithm mitigates this risk by requiring a majority of independent Redis masters (e.g., 3 out of 5) to grant the lock within a bounded time.

Redlock algorithm steps

Record the current time in milliseconds.

Attempt to acquire the lock on each master sequentially, using a client‑side timeout shorter than the lock’s TTL.

Consider the lock successful only if a majority (N/2+1) of masters grant the lock and the total acquisition time is less than the TTL.

If acquisition fails, release the lock on all masters (even those that did not grant it) to avoid stray locks.

References

Redis distributed lock failure scenarios – https://blog.csdn.net/he247052163/article/details/119413877

Redis re‑entrant lock implementation – https://www.cnblogs.com/x-kq/p/14801527.html

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.

BackendjavaredisredissonPitfalls
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

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.