Avoid Common Pitfalls When Using Redis Distributed Locks

This article examines the hidden dangers of Redis distributed locks—including non‑atomic operations, forgotten releases, accidental unlocking of others, massive request failures, re‑entrancy, lock competition, timeout handling, and master‑slave replication issues—while providing practical Java code examples and mitigation strategies.

dbaplus Community
dbaplus Community
dbaplus Community
Avoid Common Pitfalls When Using Redis Distributed Locks

Introduction

Redis is often chosen for distributed locking because it is simple and efficient, but misuse can lead to serious problems. This guide outlines typical pitfalls and offers concrete solutions.

1. Non‑Atomic Locking

Using SETNX followed by EXPIRE is not atomic. If the expiration fails, the lock may never release, causing memory exhaustion.

if (jedis.setnx(lockKey, val) == 1) {
    jedis.expire(lockKey, timeout);
}

The atomic alternative is the SET command with NX and PX options:

String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
    return true;
}
return false;

2. Forgetting to Release the Lock

Even with an atomic SET, you must explicitly release the lock. A typical pattern is:

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

If the release fails, the expiration will eventually free the lock.

3. Releasing Someone Else’s Lock

In multi‑threaded scenarios a thread may delete a lock owned by another thread. The solution is to store a unique requestId when acquiring the lock and verify it before deletion:

if (jedis.get(lockKey).equals(requestId)) {
    jedis.del(lockKey);
    return true;
}
return false;

A Lua script can make this check‑and‑delete atomic:

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

4. Massive Failed Requests

When thousands of clients contend for a single lock, only one succeeds, leading to poor throughput (e.g., in flash‑sale scenarios). Using a spin‑lock with retries can improve success rates:

long start = System.currentTimeMillis();
while (true) {
    String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
    if ("OK".equals(result)) {
        // critical section
        return true;
    }
    if (System.currentTimeMillis() - start >= timeout) {
        return false;
    }
    Thread.sleep(50);
}

5. Re‑Entrancy Problem

Recursive methods that acquire the same lock will dead‑lock because the second acquisition fails. Use a re‑entrant lock provided by Redisson:

RLock lock = redisson.getLock(lockKey);
lock.lock(5, TimeUnit.SECONDS);
// recursive calls using the same lock instance
lock.unlock();

Redisson implements re‑entrancy with a Lua script that increments a counter for the same requestId and decrements it on unlock.

6. Lock Competition (Read‑Write Locks)

For workloads with many reads and few writes, a read‑write lock improves performance. Redisson offers RReadWriteLock:

RReadWriteLock rwLock = redisson.getReadWriteLock("readWriteLock");
RLock rLock = rwLock.readLock();
try {
    rLock.lock();
    // read operation
} finally {
    rLock.unlock();
}

RLock wLock = rwLock.writeLock();
try {
    wLock.lock();
    // write operation
} finally {
    wLock.unlock();
}

7. Lock Timeout

If a lock expires while business logic is still running, subsequent code executes without protection. The remedy is automatic lock renewal (watch‑dog). Redisson does this internally; a manual approach uses a scheduled task to refresh the TTL:

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        // refresh lock TTL
    }
}, 10000, TimeUnit.MILLISECONDS);

A Lua script can also extend the TTL atomically.

8. Master‑Slave Replication Issues

In a master‑slave setup, if the master crashes after acquiring a lock but before replicating it, the lock is lost on the new master, breaking mutual exclusion. Redisson’s RedissonRedLock implements the Redlock algorithm across multiple independent Redis instances to mitigate this risk.

// Pseudocode of Redlock acquisition
int successes = 0;
for (RedisNode node : nodes) {
    if (node.tryLock(key, ttl)) successes++;
    if (successes >= N/2 + 1) break;
}
if (successes >= N/2 + 1) {
    // lock acquired
} else {
    // rollback and fail
}

While Redlock improves safety, it adds latency and resource cost, so evaluate whether CP (e.g., Zookeeper) or AP (Redis) semantics better suit your application.

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.

Javaredisbest practicesredisson
dbaplus Community
Written by

dbaplus Community

Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.

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.