Backend Development 12 min read

Implementing a Redis Distributed Lock with Spring AOP and Automatic Renewal

This article explains how to implement a Redis-based distributed lock in Spring Boot using custom annotations, AOP interception, and a scheduled executor to automatically renew lock expiration, providing a complete design, code examples, and testing guidance for handling long-running operations safely.

Top Architect
Top Architect
Top Architect
Implementing a Redis Distributed Lock with Spring AOP and Automatic Renewal

Business background : Certain business requests involve time‑consuming operations that require locking to prevent concurrent modifications and ensure data integrity.

Analysis : Using Redis as a distributed lock centralizes lock state across the cluster, allowing JVMs to coordinate without direct communication.

Design process :

Create a custom annotation @RedisLockAnnotation to mark methods that need locking.

Define an AOP pointcut that scans for this annotation.

Implement an @Aspect class to intercept the method, acquire the lock, set expiration, and release the lock after execution.

Use ProceedingJoinPoint to wrap the target method call.

Perform lock acquisition before the method and delete the key after completion.

Lock acquisition : The aspect uses RedisTemplate.opsForValue().setIfAbsent with a UUID value and sets an expiration time. Only the first request that successfully sets the key proceeds; others fail.

Timeout problem : If the intercepted method runs longer than the lock’s TTL, the key may expire early, causing another thread to acquire the lock and lead to data inconsistency.

Solution – automatic renewal : A ScheduledExecutorService runs every 2 seconds, scans a concurrent queue of lock holders, and extends the TTL when the remaining time falls below one‑third of the original timeout.

/**
 * Thread pool, each JVM uses one thread to maintain keyAliveTime, scheduled execution.
 */
private static final ScheduledExecutorService SCHEDULER =
    new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());
static {
    SCHEDULER.scheduleAtFixedRate(() -> {
        // do something to extend time
    }, 0, 2, TimeUnit.SECONDS);
}

Design scheme : The aspect registers the lock holder into a queue, records business key, lock time, last modify time, thread reference, and retry counters. The scheduled task checks each holder, removes expired entries, interrupts threads that exceed retry limits, and extends the lock when needed.

// Scan task queue
private static ConcurrentLinkedQueue
holderList = new ConcurrentLinkedQueue();
/** Thread pool, maintain keyAliveTime */
private static final ScheduledExecutorService SCHEDULER = new ScheduledThreadPoolExecutor(1,
    new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());
SCHEDULER.scheduleAtFixedRate(() -> {
    Iterator
iterator = holderList.iterator();
    while (iterator.hasNext()) {
        RedisLockDefinitionHolder holder = iterator.next();
        if (holder == null) { iterator.remove(); continue; }
        if (redisTemplate.opsForValue().get(holder.getBusinessKey()) == null) { iterator.remove(); continue; }
        if (holder.getCurrentCount() > holder.getTryCount()) {
            holder.getCurrentTread().interrupt();
            iterator.remove();
            continue;
        }
        long curTime = System.currentTimeMillis();
        boolean shouldExtend = (holder.getLastModifyTime() + holder.getModifyPeriod()) <= curTime;
        if (shouldExtend) {
            holder.setLastModifyTime(curTime);
            redisTemplate.expire(holder.getBusinessKey(), holder.getLockTime(), TimeUnit.SECONDS);
            holder.setCurrentCount(holder.getCurrentCount() + 1);
        }
    }
}, 0, 2, TimeUnit.SECONDS);

Testing : A sample controller method annotated with @RedisLockAnnotation sleeps for 10 seconds while the lock timeout is 3 seconds. Logs show the scheduled task extending the lock and eventually interrupting the thread after the retry limit, demonstrating the renewal mechanism.

@GetMapping("/testRedisLock")
@RedisLockAnnotation(typeEnum = RedisLockTypeEnum.ONE, lockTime = 3)
public Book testRedisLock(@RequestParam("userId") Long userId) {
    try {
        log.info("睡眠执行前");
        Thread.sleep(10000);
        log.info("睡眠执行后");
    } catch (Exception e) {
        log.info("has some error", e);
    }
    return null;
}

Conclusion : For long‑running business logic, a Redis distributed lock combined with AOP interception and a scheduled renewal thread prevents duplicate processing and ensures data consistency, while also handling timeout and retry scenarios.

JavaconcurrencyRedisDistributed LockSpring AOPScheduledExecutorService
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.