Mastering Redis Distributed Locks: From Basics to Advanced Redisson Practices
This comprehensive guide explains the principles, pitfalls, and best practices of implementing Redis distributed locks, covering basic SETNX usage, lock timeout handling, re‑entrant locks, Redlock algorithm debates, and practical Redisson integration with Spring Boot.
Distributed Lock Introduction
Redis distributed locks control concurrent access to shared resources by ensuring that only one client can hold a lock at any given time.
When to Use a Distributed Lock
When multiple processes need exclusive access to a resource.
When lock acquisition and release code placement matters.
When avoiding dead locks and ensuring fault tolerance.
When setting appropriate expiration times.
When preventing other threads from releasing a lock they didn't acquire.
When implementing re‑entrant locks.
When dealing with master‑slave replication safety.
Understanding the Redlock algorithm.
Basic Commands
The
SETNXcommand (SET if Not eXists) can acquire a lock, returning
1on success and
0on failure.
<code>> SETNX lock:168 1
(integer) 1 # lock acquired
> SETNX lock:168 2
(integer) 0 # lock acquisition failed</code>Releasing a lock is done with
DEL:
<code>> DEL lock:168
(integer) 1</code>Timeout Handling
Setting an expiration with
EXPIREafter acquiring a lock prevents permanent dead locks, but the two commands are not atomic. Since Redis 2.6, the
SETcommand supports atomic
NXand
PXoptions:
<code>SET resource_name random_value NX PX 30000</code>This ensures the lock is set only if the key does not exist and automatically expires after 30 seconds.
Ensuring the Owner Releases the Lock
Store a unique identifier as the lock value. When releasing, compare the stored value with the identifier before deleting. This logic can be made atomic with a Lua script:
<code>if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end</code>Re‑entrant Locks
Redisson implements re‑entrant locks using a Redis hash where the field stores the lock count for each client identifier. Increment on lock acquisition and decrement on release; delete the hash when the count reaches zero.
<code>if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hincrby', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return 1
end
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 1
end
return 0</code>Unlocking decrements the counter and deletes the key when it reaches zero:
<code>if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then
return nil
end
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1)
if (counter > 0) then
return 0
else
redis.call('del', KEYS[1])
return 1
end</code>Master‑Slave Replication Issues
Asynchronous replication can cause a lock to be lost if the master crashes before propagating the lock to slaves. The Redlock algorithm mitigates this by requiring a majority of independent Redis nodes to grant the lock.
Redlock Debate
Critics argue Redlock is heavy, may suffer from clock drift, and lacks fencing tokens, while its creator contends that modest clock skew is acceptable and that the algorithm still detects most failures.
Redisson Integration (Spring Boot)
Add the Redisson starter dependency and configure Redis connection properties. Obtain a
RLockinstance and use it as follows:
<code>RLock lock = redisson.getLock("myLock");
try {
lock.lock(); // blocks until lock is acquired
// business logic
} finally {
lock.unlock();
}</code>Variants include
tryLockwith wait time,
lockwith lease time, and automatic watchdog renewal (default 30 s, configurable via
Config.lockWatchdogTimeout).
Watchdog Mechanism
If no explicit lease time is set, Redisson schedules a renewal task that extends the lock before it expires (every
lockWatchdogTimeout/3milliseconds). This prevents dead locks caused by client crashes.
Source Code Insight
The lock acquisition path calls
tryAcquireAsync, which either uses the provided lease time or the internal watchdog lease. Renewal is performed by executing a Lua script that checks the lock's existence and calls
PEXPIRE.
<code>if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
end
return 0;</code>Conclusion
Understanding the nuances of Redis distributed locks—from basic SETNX usage to advanced Redisson features and the trade‑offs of Redlock—helps build reliable, high‑concurrency systems.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.