To implement distributed locking in a Redis cluster environment, we leverage Redisson, a Java-based client that simplifies working with Redis in distributed systems.
Environment Setup
| Component | Version |
|---|---|
| Spring Boot | 2.0.3.RELEASE |
| Spring Cloud | Finchley.RELEASE |
| Redis | 4.0.11 |
| JDK | 1.8.x |
Maven Dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.5.4</version>
</dependency>
</dependencies>
Redisson Overview
Redisson is a Redis-based In-Memory Data Grid for Java built on top of Netty. It provides distributed implementations of common Java utilities such as locks, atomic counters, and executors—enabling coordination across multiple JVMs in a cluster.
It supports use cases like distributed caching, session management, task scheduling, and more. Configuration is required before usage, similar to setting up a Jedis connection pool.
Configuration Approach
Three configuration styles are available:
- Pure Java: All settings defined programmatically (used here).
- XML-based Spring: Legacy and verbose; not recommended.
- External config file: Parameters loaded from YAML/properties files.
Redisson Client Initialization
import org.redisson.Redisson;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class RedissonManager {
private static Config config = new Config();
private static RedissonClient client = null;
private static final String ATOMIC_COUNTER_KEY = "genId_";
public static void initialize() {
try {
config.useClusterServers()
.setScanInterval(200_000)
.setMasterConnectionPoolSize(10_000)
.setSlaveConnectionPoolSize(10_000)
.setIdleConnectionTimeout(10_000)
.setConnectTimeout(30_000)
.setTimeout(3_000)
.setRetryInterval(3_000)
.addNodeAddress(
"redis://127.0.0.1:7000",
"redis://127.0.0.1:7001",
"redis://127.0.0.1:7002",
"redis://127.0.0.1:7003",
"redis://127.0.0.1:7004",
"redis://127.0.0.1:7005"
);
client = Redisson.create(config);
RAtomicLong counter = client.getAtomicLong(ATOMIC_COUNTER_KEY);
counter.set(0); // reset counter
} catch (Exception e) {
e.printStackTrace();
}
}
public static RedissonClient getClient() {
if (client == null) {
initialize();
}
return client;
}
public static long getNextId() {
return getClient().getAtomicLong(ATOMIC_COUNTER_KEY).incrementAndGet();
}
}
Distributed Lock Utility
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class DistributedLock {
private static final Logger logger = LoggerFactory.getLogger(DistributedLock.class);
private static final RedissonClient redisson = RedissonManager.getClient();
public void acquire(String lockKey) {
RLock lock = redisson.getLock(lockKey);
lock.lock(2, TimeUnit.SECONDS); // auto-unlock after 2s
System.out.println("Acquired lock: " + Thread.currentThread().getName());
}
public void release(String lockKey) {
RLock lock = redisson.getLock(lockKey);
lock.unlock();
System.out.println("Released lock: " + Thread.currentThread().getName());
}
}
Integration Test
@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan({"com.annotaion", "cn.springcloud", "com.config", "com.redislock"})
public class Application implements ApplicationRunner {
@Autowired
private DistributedLock lockUtil;
private static final ExecutorService executor = Executors.newFixedThreadPool(5);
private static final CyclicBarrier barrier = new CyclicBarrier(5);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(ApplicationArguments args) {
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
try {
System.out.println(Thread.currentThread().getName() + " waiting at barrier");
barrier.await();
System.out.println(Thread.currentThread().getName() + " proceeding");
String resource = "test123";
lockUtil.acquire(resource);
Thread.sleep(1000); // simulate work
System.out.println(Thread.currentThread().getName() + " processing...");
lockUtil.release(resource);
System.out.println(Thread.currentThread().getName() + " done");
} catch (Exception e) {
e.printStackTrace();
}
});
}
executor.shutdown();
long id = RedissonManager.getNextId();
System.out.println("Final atomic ID from Redis: " + id);
}
}
Key Observations
- In a single Redis instance, failure causes total lock unavailability—requiring fallback strategies.
- Lock expiration must exceed task duration to avoid premature release and race conditions.
- Redis Cluster offers higher availability: partial node failures don’t break locking.
- If a failed node restarts quickly and uses persistence, the risk of duplicate lock ownership decreases.
- Redisson abstracts differences between standalone and clustered Redis locking mechanisms, ensuring consistent behavior.