7 Distributed Lock Implementations and Real‑World Pitfalls

The article explains why local locks fail in multi‑machine deployments, defines the three essential properties of a correct distributed lock, walks through seven Redis‑based lock solutions with code samples, highlights common production pitfalls, and provides a decision tree for selecting the right approach.

Programmer1970
Programmer1970
Programmer1970
7 Distributed Lock Implementations and Real‑World Pitfalls

Why Distributed Locks?

In monolithic Java, synchronized or ReentrantLock works because memory is shared. When the system is split across many machines, JVM memory is isolated, so local locks cannot enforce mutual exclusion.

Distributed lock principle : move the lock marker to a shared store (Redis, ZooKeeper, Etcd) so that all processes can see it.

Mutual exclusion : only one client holds the lock at any time.

Dead‑lock avoidance : the lock is released automatically if the client crashes.

Fault tolerance : the system remains operational if the lock service fails.

Seven Redis‑Based Lock Schemes

Scheme 1: SETNX + EXPIRE (historical)

Boolean locked = redis.setnx("lock:order", "thread-1");
if (locked) {
    redis.expire("lock:order", 30);
    // business logic …
    redis.del("lock:order");
}

Fatal issue : SETNX and EXPIRE are separate commands; if the process crashes between them the lock never expires, causing a dead lock.

Scheme 2: SETNX + value storing expiration time

long expires = System.currentTimeMillis() + 30000;
if (redis.setnx("lock:order", String.valueOf(expires)) == 1) {
    // lock acquired
}

Problems

Relies on clock synchronization; drift breaks the lock.

No unique identifier; another client may delete the lock unintentionally.

Concurrent getSet calls can overwrite the expiration.

Production pitfall : an 8‑second clock skew between two servers caused two threads to hold the same lock simultaneously, resulting in 200 oversold orders.

Scheme 3: Lua script (atomic SETNX + EXPIRE)

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

Redis executes Lua scripts single‑threaded, guaranteeing atomicity.

Pros : solves atomicity problem of Scheme 1.

Cons : value still lacks a unique identifier, so accidental deletion remains possible.

Scheme 4: SET EX PX NX (Redis 2.6.12+)

SET lock:order "uuid-thread-1" NX PX 30000

Parameters NX – set only if the key does not exist. PX – expiration in milliseconds. value – unique identifier (UUID + thread ID).

This single command provides atomic acquisition with server‑side TTL and a verifiable value.

Scheme 5: SET EX PX NX + verify unique value on release

Lock acquisition uses Scheme 4. Unlocking uses a Lua script that checks the stored value before deleting:

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

Result : only the owner can release the lock, eliminating accidental deletions.

Production pitfall: without value verification, a timed‑out lock held by thread B was deleted by thread A after A finished its business, corrupting data.

Scheme 6: Redisson framework (production‑grade default)

Redisson implements the previous safeguards and adds:

Re‑entrancy : a hash records the re‑entry count (+1 on lock, –1 on unlock).

Watchdog (auto‑renewal) : default 30 s TTL, refreshed every 10 s while the lock is held.

Atomic release : internal Lua script ensures only the owner can unlock.

Blocking wait : publish‑subscribe wakes waiting threads when the lock is released.

RLock lock = redisson.getLock("stockLock");
lock.lock(30, TimeUnit.SECONDS);
try {
    // business logic
} finally {
    lock.unlock();
}

One line of code replaces dozens of manual steps.

Scheme 7: RedLock (multi‑node disaster‑recovery)

Single‑node Redis can lose locks during master‑slave failover. RedLock acquires the lock on N independent Redis nodes and requires a majority to succeed:

1. Record start time T1
2. Request lock on 5 nodes (timeout << lock TTL)
3. If (T2‑T1 < lock TTL) && (successful nodes > N/2) → lock acquired
4. Otherwise release on all nodes

Key constraints

Node count must be odd (3/5/7).

Lock acquisition time must be < lock TTL.

Successful nodes must exceed N/2.

Production pitfall: network jitter caused lock acquisition to exceed the TTL, leading to many failures in latency‑sensitive scenarios.

Seven Real‑World Pitfalls

Atomicity break : SETNX followed by a crash leaves the lock forever – fix with SET NX PX or Lua.

Clock drift : client‑side expiration can release the lock early – use server‑side TTL.

Accidental deletion : A’s timeout lets B acquire the lock, but A later deletes it – store a UUID and verify on release.

Business timeout without renewal : a 30 s lock expires while a 50 s task runs – Redisson’s watchdog auto‑renews.

Master‑slave failover loss : lock data not replicated – adopt RedLock multi‑node strategy.

Non‑re‑entrant dead lock : same thread cannot reacquire – Redisson records re‑entry count in a hash.

Connection pool exhaustion : high concurrency saturates Redis connections – configure pool size and use pipelining.

Selection Decision Tree

Need strong consistency (finance/payments)?
 → ZooKeeper / Etcd (CP, sacrifice performance for safety)

Need high performance (flash sale/inventory)?
 → Redis + Redisson (AP, performance first)

Single‑node sufficient?
 → SET key value NX PX + Lua release (Scheme 4 + 5)

Multi‑node disaster recovery?
 → RedLock or Redisson Cluster

Final Three Rules

Lock acquisition and expiration must be atomic.

Before releasing, verify that you are the lock owner.

Business may exceed lock TTL; the lock must support renewal.

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.

Redisfault tolerancedistributed lockRedissonRedLockJava concurrency
Programmer1970
Written by

Programmer1970

Formerly called 'Code to 35'. Add our main WeChat ID to access a wealth of shared resources (algorithms, interview prep, tech stacks: Java, Python, Go, big data). We mainly share serious development techniques, focusing on output-driven input. Occasionally we post life snippets and gossip. Our aim is to attract precise traffic and test advertising opportunities.

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.