Mastering Distributed Locks with Redisson in Spring Boot 2.7
This guide explains how to implement a robust, re‑entrant distributed lock using Redisson in a Spring Boot 2.7 application, covering basic and asynchronous APIs, Maven dependency setup, YAML configuration, custom annotation, AspectJ implementation, and comprehensive test cases.
1. Introduction
Redisson is a Redis Java client that adds an in‑memory data grid, offering a simple way to use Redis for distributed locks. It works on high‑performance asynchronous, lock‑free Java Redis client and Netty.
JDK and Redis Support
JDK: 1.8~21 Redis: 3.0~7.2
Redisson Distributed Lock
Redisson provides a re‑entrant lock that implements java.util.concurrent.locks.Lock . It uses a Pub/Sub channel to notify waiting threads and a watchdog to extend the lock TTL while the holder is alive (default 30 seconds, configurable via lockWatchdogTimeout ).
Basic Usage
<code>RLock lock = redisson.getLock("myLock");
lock.lock(); // acquire lock
// acquire lock and auto‑unlock after 10 seconds
lock.lock(10, TimeUnit.SECONDS);
// wait up to 100 seconds to acquire lock, auto‑unlock after 10 seconds
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
// todo
} finally {
lock.unlock();
}
}</code>Asynchronous API
<code>RLock rLock = redisson.getLock(key);
rLock.lock();
RFuture<Boolean> lockFuture = rLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
lockFuture.whenComplete((res, exception) -> {
// todo
rLock.unlockAsync();
});</code>For full details, refer to the official Redisson documentation.
2. Practical Example
2.1 Dependency Management
<code><dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.18.0</version>
</dependency></code>2.2 Configuration File
<code>spring:
redis:
redisson:
file: classpath:redisson.yaml</code>The redisson.yaml file contains all Redisson settings, for example:
<code>singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password: xxxooo
subscriptionsPerConnection: 5
clientName: null
address: "redis://127.0.0.1:6379"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 24
connectionPoolSize: 64
database: 14
dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.Kryo5Codec> {}
transportMode: "NIO"</code>2.3 Core Class Definition
Custom Annotation
<code>@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DLock {
/** distributed lock key */
String value() default "";
}</code>The value attribute defines the lock key; if empty, a key is generated from the class and method parameters.
Aspect Definition
<code>@Component
@Aspect
public class DistributedLockAspect {
private final RedissonClient redisson;
public DistributedLockAspect(RedissonClient redisson) {
this.redisson = redisson;
}
@Pointcut("@annotation(lock)")
private void lockpc(DLock lock) {}
@Around("lockpc(lock)")
public Object lockAround(ProceedingJoinPoint pjp, DLock lock) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Object[] args = pjp.getArgs();
String key = lock.value();
if ("".equals(key)) {
key = configKey(signature.getDeclaringType(), method)
.replaceAll("[^a-zA-Z0-9]", "");
} else {
StandardEvaluationContext context = new StandardEvaluationContext();
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(key);
key = expression.getValue(context, String.class);
}
RLock rLock = redisson.getLock(key);
rLock.lock();
try {
return pjp.proceed();
} finally {
rLock.unlock();
}
}
private String configKey(Class<?> targetType, Method method) {
StringBuilder builder = new StringBuilder();
builder.append(targetType.getSimpleName());
builder.append('#').append(method.getName()).append('(');
for (Class<?> param : method.getParameterTypes()) {
builder.append(param.getSimpleName()).append(',');
}
if (method.getParameterTypes().length > 0) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.append(')').toString();
}
}
</code>Testing
Test method using SpEL to generate a lock key:
<code>@DLock("'person:' + #person.id + ':' + #cateId")
public void create(Person person, Integer cateId) {
System.out.printf("Current thread: %s%n", Thread.currentThread().getName());
// todo
sleep(5);
}</code>JUnit test that spawns multiple threads to invoke create concurrently:
<code>@Test
public void testCreate() {
int size = 5;
CountDownLatch cdl = new CountDownLatch(size);
CountDownLatch nums = new CountDownLatch(size);
Thread[] threads = new Thread[size];
for (int i = 0; i < size; i++) {
final int n = i;
threads[i] = new Thread(() -> {
try { nums.countDown(); nums.await(); } catch (InterruptedException e) { e.printStackTrace(); }
Person person = new Person();
person.setId(2L);
person.setName("张三");
lockService.create(person, 666);
cdl.countDown();
}, "T - " + n);
}
for (Thread t : threads) { t.start(); }
try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); }
}
</code>When all threads use the same parameters, they acquire the lock sequentially, producing timestamps like:
When each thread uses a different key (different request parameters), all threads run concurrently, and the Redis keys differ, as shown below:
Note: The aspect does not handle exceptions that may arise during SpEL evaluation.
This completes the article.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.