Project: Distributed Lock
Introduction
In a distributed system, multiple application instances may compete for the same resource. A distributed lock prevents concurrent execution of a critical section by ensuring that only one process holds the lock at any time. This chapter builds a production-grade lock with Redis, then introduces Redisson, a Java library that handles edge cases such as lock renewal, reentrancy, and cluster topology automatically.
Prerequisites
- Completion of Spring Boot Integration
- Understanding of Redis
SETNXand key expiration (see Basic Data Types) - A Spring Boot project with
spring-boot-starter-data-redis
The Naive Lock
The simplest distributed lock uses SETNX (Set if Not eXists) to claim a key, plus EXPIRE to prevent a crashed client from holding the lock forever.
public boolean lock(String key, String token, long seconds) {
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(key, token, Duration.ofSeconds(seconds));
return Boolean.TRUE.equals(acquired);
}
public void unlock(String key, String token) {
String current = redisTemplate.opsForValue().get(key);
if (token.equals(current)) {
redisTemplate.delete(key);
}
}This works in happy-path scenarios, but it has serious flaws:
- No automatic renewal: If the critical section takes longer than the expiration, another client can steal the lock mid-operation.
- Not reentrant: The same thread cannot acquire the lock twice without deadlocking itself.
- Unsafe unlock: The check-and-delete is not atomic. Another client might acquire the lock between the
GETandDEL. - Clock skew: Reliance on local time is fragile.
A Safer Lock with Lua
Lua scripts execute atomically on the Redis server, eliminating the race condition in unlock.
Tip
Always Use a Unique Token
The token should be globally unique, typically a UUID plus a thread identifier. If a client crashes and its lock expires, a second client acquires the lock with a different token. The original client's late unlock call sees the mismatched token and safely does nothing.
Redisson: Production-Grade Locking
For production systems, Redisson implements the full locking contract: reentrancy, automatic renewal (watchdog), fairness options, and cluster awareness.
Dependency
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.24.0</version>
</dependency>Configuration
spring:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://localhost:6379"
connectionPoolSize: 64For Sentinel or Cluster, change the Redisson config block accordingly:
spring:
redis:
redisson:
config: |
sentinelServersConfig:
masterName: mymaster
sentinelAddresses:
- "redis://sentinel1:26379"
- "redis://sentinel2:26379"Using RLock
The Watchdog Mechanism
Redisson's most valuable feature is the watchdog. When you lock with a lease time of 30 seconds, Redisson spawns a background task that renews the lock every 10 seconds—as long as the JVM process that acquired the lock is still alive. If the process crashes, the watchdog stops renewing, and the lock expires naturally after the lease time.
Acquire lock (30s lease)
│
▼
Every 10s: watchdog extends lock back to 30s
│
▼
Unlock() called ──→ watchdog cancelled ──→ lock deletedIf you omit the lease time in tryLock, Redisson defaults to 30 seconds and enables the watchdog automatically.
Reentrant Locks
RLock is reentrant. The same thread can acquire the same lock multiple times, and it must unlock the same number of times to release it fully.
Lock Fairness
By default, Redisson locks are non-fair: the next acquirer is arbitrary. Under high contention, this can lead to thread starvation. Redisson provides a fair variant:
RLock fairLock = redissonClient.getFairLock("fairLock");Fair locks queue waiting threads in arrival order, which reduces throughput slightly but guarantees that every waiter eventually acquires the lock.
Read-Write Locks
When multiple threads read a resource but only one writes, a read-write lock improves concurrency:
A Complete Controller Example
FAQ
What is the Redlock algorithm, and do I need it?
Redlock is an algorithm for distributed locking across multiple independent Redis masters. It is designed for environments where a single Redis instance is a single point of failure. In practice, most teams use a single Redis with Sentinel or Cluster, combined with Redisson's watchdog, and find it sufficient. Redlock adds significant complexity; adopt it only after proving that simpler approaches fail your availability requirements.
Can I use Redis distributed locks across microservices?
Yes, as long as every service connects to the same Redis (or Redis Cluster). The lock key is a global namespace. Prefix keys with your service name to avoid collisions: "inventory:lock:stock:42" vs "payment:lock:invoice:99".
What happens if the database transaction rolls back but the lock was already released?
Release the lock only after the database transaction commits. In Spring, use @Transactional and release the lock in a finally block after the transactional method returns. If the transaction rolls back, the lock remains held until the lease expires, which prevents other processes from seeing inconsistent data.
Is SET key value NX EX atomic?
Yes. Since Redis 2.6.12, SET supports the NX and EX modifiers together in a single command. This replaces the older, non-atomic SETNX + EXPIRE pair. Always use the combined form:
SET lock:resource token NX EX 30