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