Backend Development 13 min read

Understanding Distributed Locks and Implementations with Redis and Redisson

This article explains the concept of distributed locks, compares them with local JVM locks, outlines essential properties, demonstrates simple Redis-based lock implementations using SETNX and Lua scripts, discusses lock expiration and renewal, and introduces Redisson's advanced features such as automatic watchdog renewal and the Redlock algorithm.

IT Services Circle
IT Services Circle
IT Services Circle
Understanding Distributed Locks and Implementations with Redis and Redisson

Distributed Lock Introduction

In a single‑JVM environment, threads synchronize using local locks such as ReentrantLock or the synchronized keyword. When multiple services run in separate JVMs, local locks cannot guarantee mutual exclusion, so a distributed lock is required.

Basic Properties of a Distributed Lock

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

High availability : the lock service must remain available and eventually release the lock even if the client crashes.

Re‑entrancy : the same client can acquire the lock multiple times.

Simple Redis‑Based Lock

Redis provides the SETNX command (equivalent to Java's setIfAbsent ) to create a lock only if the key does not exist.

> SETNX lockKey uniqueValue
(integer) 1
> SETNX lockKey uniqueValue
(integer) 0

Releasing the lock is done with DEL :

> DEL lockKey
(integer) 1

To avoid accidental deletion, a Lua script can compare the stored value before deleting:

// release lock only if the stored value matches
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

Lock Expiration and Automatic Renewal

Setting an expiration prevents dead locks:

SET lockKey uniqueValue EX 3 NX

However, if the critical section exceeds the timeout, the lock may expire prematurely. Redisson solves this with a watchdog that automatically renews the lock.

Redisson Watchdog Mechanism

Redisson’s default watchdog timeout is 30 seconds (configurable via setLockWatchdogTimeout ).

// default 30 seconds, can be changed
private long lockWatchdogTimeout = 30 * 1000;
public Config setLockWatchdogTimeout(long lockWatchdogTimeout) {
    this.lockWatchdogTimeout = lockWatchdogTimeout;
    return this;
}
public long getLockWatchdogTimeout() {
    return lockWatchdogTimeout;
}

The renewal logic runs periodically (every internalLockLeaseTime/3 ) and uses an asynchronous Lua script to extend the TTL atomically:

private void renewExpiration() {
    // schedule next renewal
    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            CompletionStage
future = renewExpirationAsync(threadId);
            future.whenComplete((res, e) -> {
                if (e != null) {
                    log.error("Can't update lock " + getRawName() + " expiration", e);
                    EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                    return;
                }
                if (res) {
                    renewExpiration(); // recursive renewal
                } else {
                    cancelExpirationRenewal(null);
                }
            });
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
    ee.setTimeout(task);
}
protected CompletionStage
renewExpirationAsync(long threadId) {
    return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
        "return 1; " +
        "end; " +
        "return 0;",
        Collections.singletonList(getRawName()),
        internalLockLeaseTime, getLockName(threadId));
}

When a lock is acquired without an explicit lease time, Redisson automatically enables the watchdog:

// acquire lock with automatic renewal
RLock lock = redisson.getLock("lock");
lock.lock();
// business logic
...
lock.unlock();

Specifying a lease time disables the watchdog:

// manual lease time, no watchdog
lock.lock(10, TimeUnit.SECONDS);

Re‑entrant Distributed Locks

Re‑entrancy allows the same thread to acquire the lock multiple times. Java’s synchronized and ReentrantLock are examples. Redisson provides a re‑entrant lock ( RLock ) along with other lock types such as spin, fair, multi‑lock, red‑lock, and read‑write lock.

Redlock Algorithm for Redis Clusters

In a Redis cluster, a single node failure can cause lock loss. The Redlock algorithm acquires locks on a majority of independent Redis instances to achieve fault tolerance. However, it has performance drawbacks and safety concerns under clock drift, and many practitioners recommend using ZooKeeper for truly reliable distributed locking.

References

Redisson GitHub: https://github.com/redisson/redisson

Redisson 3.17.6 release: https://github.com/redisson/redisson/releases/tag/redisson-3.17.6

Redlock algorithm description: https://redis.io/topics/distlock

Martin Kleppmann’s critique: https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

JavaConcurrencyRedisDistributed LockRedissonRedlockwatchdog
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

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.