Implementing Distributed Locks with Redis: Problems and Solutions

This article explains how Redis can be used to implement distributed locks, outlines common pitfalls such as non‑atomic operations, lock expiration, incorrect unlocking, lack of re‑entrancy and waiting mechanisms, and presents Lua scripts, Java examples, and cluster‑level considerations to mitigate these issues.

Top Architecture Tech Stack
Top Architecture Tech Stack
Top Architecture Tech Stack
Implementing Distributed Locks with Redis: Problems and Solutions

Introduction

Distributed locks are a common interview topic in large tech companies. When modifying existing data in a clustered system, concurrent updates can cause data loss because read‑modify‑write is not atomic. Local locks work only on a single server, so a distributed lock is required to ensure consistency across multiple nodes.

Implementation

Redis locks mainly rely on the SETNX command.

Lock command: SETNX key value – sets the key only if it does not exist.

Unlock command: DEL key – deletes the key to release the lock.

Lock timeout: EXPIRE key timeout – sets an expiration to avoid permanent lock.

Lock/unlock pseudocode:

if (setnx(key, 1) == 1){
    expire(key, 30)
    try {
        //TODO business logic
    } finally {
        del(key)
    }
}

1. SETNX and EXPIRE are not atomic

If SETNX succeeds but the server crashes before EXPIRE runs, the lock becomes a dead lock. A Lua script can combine the two operations atomically:

if (redis.call('setnx', KEYS[1], ARGV[1]) < 1)
    then return 0;
end;
redis.call('expire', KEYS[1], tonumber(ARGV[2]));
return 1;

// usage example
EVAL "if (redis.call('setnx',KEYS[1],ARGV[1]) < 1) then return 0; end; redis.call('expire',KEYS[1],tonumber(ARGV[2])); return 1;" 1 key value 100

2. Incorrect unlock

If thread A acquires the lock with a 30‑second timeout but runs longer, the lock expires and thread B acquires it; when A later calls DEL, it unintentionally releases B's lock. Storing a unique identifier (e.g., a UUID) as the lock value and verifying it before deletion solves the problem:

// lock
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
SET key uuid NX EX 30
// unlock
if (redis.call('get', KEYS[1]) == ARGV[1])
    return redis.call('del', KEYS[1])
else
    return 0

3. Timeout unlock causing concurrency

When a lock expires while the original holder is still working, both the original and a new holder may execute concurrently. Solutions include setting a sufficiently long timeout or implementing an automatic renewal (watchdog) thread.

4. Non‑reentrancy

A non‑reentrant lock cannot be acquired again by the same thread. Re‑entrancy can be achieved by counting lock acquisitions. A Java example using ThreadLocal to store a counter:

private static ThreadLocal<Map<String, Integer>> LOCKERS = ThreadLocal.withInitial(HashMap::new);
// lock
public boolean lock(String key) {
  Map<String, Integer> lockers = LOCKERS.get();
  if (lockers.containsKey(key)) {
    lockers.put(key, lockers.get(key) + 1);
    return true;
  } else {
    if (SET key uuid NX EX 30) {
      lockers.put(key, 1);
      return true;
    }
  }
  return false;
}
// unlock
public void unlock(String key) {
  Map<String, Integer> lockers = LOCKERS.get();
  if (lockers.getOrDefault(key, 0) <= 1) {
    lockers.remove(key);
    DEL key
  } else {
    lockers.put(key, lockers.get(key) - 1);
  }
}

Alternatively, Redis hash structures can store both the lock identifier and a re‑entrancy count, as shown in the Redisson‑style Lua script below:

// if lock_key does not exist
if (redis.call('exists', KEYS[1]) == 0) then
    redis.call('hset', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
// if lock_key exists and the thread identifier matches
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
// lock acquisition failed, return remaining TTL
return redis.call('pttl', KEYS[1]);

5. Unable to wait for lock release

All the commands above return immediately, so a client cannot block until the lock is released. Two approaches are common:

Polling: repeatedly attempt to acquire the lock with a sleep interval until success or a timeout.

Publish/Subscribe: subscribe to a lock‑release channel and receive a notification when the lock becomes available.

Redis also provides the Redlock algorithm for distributed locking, but its usage is controversial and is omitted here.

Cluster Considerations

1. Master‑Slave Failover

Redis is usually deployed in a master‑slave configuration for high availability. If the master crashes after a client has acquired a lock but before the command is replicated, the new master (previous slave) will not have the lock, allowing another client to acquire it and causing duplicate ownership.

2. Split‑Brain

Network partitions can cause the master to become isolated from its slaves and Sentinel nodes, leading to multiple masters. Clients connected to different masters may each think they hold the same lock, resulting in concurrent execution.

Conclusion

Redis is known for high performance, but implementing distributed locks with it introduces several challenges such as atomicity, expiration handling, re‑entrancy, and cluster‑level anomalies. Redis locks should be viewed as a mitigation technique; for full correctness, database‑level concurrency controls are still required.

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.

Javaconcurrencyredisdistributed-lockLua
Top Architecture Tech Stack
Written by

Top Architecture Tech Stack

Sharing Java and Python tech insights, with occasional practical development tool tips.

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.