Backend Development 24 min read

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.

macrozheng
macrozheng
macrozheng
Mastering Redis Distributed Locks: From Basics to Advanced Redisson Practices

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

SETNX

command (SET if Not eXists) can acquire a lock, returning

1

on success and

0

on failure.

<code>&gt; SETNX lock:168 1
(integer) 1  # lock acquired

&gt; SETNX lock:168 2
(integer) 0  # lock acquisition failed</code>

Releasing a lock is done with

DEL

:

<code>&gt; DEL lock:168
(integer) 1</code>

Timeout Handling

Setting an expiration with

EXPIRE

after acquiring a lock prevents permanent dead locks, but the two commands are not atomic. Since Redis 2.6, the

SET

command supports atomic

NX

and

PX

options:

<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

RLock

instance 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

tryLock

with wait time,

lock

with 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/3

milliseconds). 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.

JavaRedisSpring BootDistributed LockRedissonRedlock
macrozheng
Written by

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.

0 followers
Reader feedback

How this landed with the community

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