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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Distributed Locks with Redisson in Spring Boot 2.7

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

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();
    }
}

Asynchronous API

RLock rLock = redisson.getLock(key);
rLock.lock();
RFuture<Boolean> lockFuture = rLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
lockFuture.whenComplete((res, exception) -> {
    // todo
    rLock.unlockAsync();
});

For full details, refer to the official Redisson documentation.

2. Practical Example

2.1 Dependency Management

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.18.0</version>
</dependency>

2.2 Configuration File

spring:
  redis:
    redisson:
      file: classpath:redisson.yaml

The redisson.yaml file contains all Redisson settings, for example:

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"

2.3 Core Class Definition

Custom Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DLock {
    /** distributed lock key */
    String value() default "";
}

The value attribute defines the lock key; if empty, a key is generated from the class and method parameters.

Aspect Definition

@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();
    }
}

Testing

Test method using SpEL to generate a lock key:

@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);
}

JUnit test that spawns multiple threads to invoke create concurrently:

@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(); }
}

When all threads use the same parameters, they acquire the lock sequentially, producing timestamps like:

Sequential execution output
Sequential execution output

When each thread uses a different key (different request parameters), all threads run concurrently, and the Redis keys differ, as shown below:

Redis keys for different parameters
Redis keys for different parameters

Note: The aspect does not handle exceptions that may arise during SpEL evaluation.

This completes the article.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaredisSpring Bootdistributed-lockaspectjredisson
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

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.