Topic 11.16
Distributed Locking
In one line
Mutual exclusion across machines: Redis SETNX, leases, fencing tokens, and the 'lock ≠ atomicity' lesson.
Think of it like this
A single shared bathroom key for an office with multiple floors. Whoever holds the key has exclusive access, but everyone on every floor needs to agree on using the exact same key so two people can't both think they have it.
Key ideas
- 01
Single-instance locks (synchronized) don't work — two pods can execute the 'same' critical section.
- 02
Redis SETNX + TTL = a simple distributed lock; MUST carry a lease/expiry so a crashed holder releases it.
- 03
The hard part is not acquiring — it's knowing WHO owns it: fencing tokens (a monotonically increasing token each holder gets; the resource rejects stale tokens) prevent the slow-holder replay.
- 04
Redlock (multi-node consensus lock) is contested; most production needs a good single-Redis lock + fencing or a database advisory lock.
- 05
Alternatives: Postgres advisory locks (FOR UPDATE, pg_try_advisory_lock), Zookeeper/etcd for strong leadership.
- 06
Interview: 'SETNX with a lease, fencing token on the critical resource, and I never put a cross-entity invariant inside a lock'.
Java / Spring map
- →
Spring Integration RedisLockRegistry; Redisson; SET NX PX via RedisTemplate; compare-and-set checks in the resource.
Code & diagrams
The acquire-with-lease skeleton and the fencing idea.
public class RedisLock {
private final StringRedisTemplate redis;
private static final long LEASE_MS = 10_000;
// returns a fencing token (monotonic) if acquired
public Long acquire(String name) {
for (int attempt = 0; attempt < 5; attempt++) {
long token = System.nanoTime(); // or a Redis INCR counter
Boolean ok = redis.opsForValue()
// SET resource token NX PX lease → atomic compare-and-set-with-ttl
.setIfAbsent("lock:" + name, Long.toString(token), Duration.ofMillis(LEASE_MS));
if (Boolean.TRUE.equals(ok)) return token;
try { Thread.sleep(50 + attempt * 50L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
throw new IllegalStateException("lock timeout: " + name);
}
public void release(String name, long token) {
// only release if we still own it (Lua for atomicity):
// if GET lock:name == token then DEL lock:name end
}
}
// The RESOURCE must check the fencing token is fresher than any it saw —
// that is what makes a stale holder a rejected holder.Explain without notes
A holder is GC-paused for 12s past its lease. Who has the lock now, and how does fencing stop the stale write?
Practice
Design 'double-spend protection' with a distributed lock + fencing token for a gift-card redemption.
Trade-offs
- ↔
Locks serialize a section and add lease tuning; when possible prefer idempotency/optimistic versioning to locking.
Run it in production
You've designed it. Now build, operate, and break the same idea hands-on in the DevOps courses:
Completion checklist
My distributed 'locks' carry leases, fencing tokens, and I know when to avoid them.