How to Implement a Robust Redis Distributed Lock with Spring AOP and Auto‑Renewal
This article explains the design and implementation of a Redis‑based distributed lock using Spring AOP, covering annotation creation, pointcut definition, lock acquisition, automatic renewal via a scheduled thread pool, error handling, and testing to ensure safe concurrent access to critical business data.
Business Background
Some business requests involve time‑consuming operations that require locking to prevent concurrent modifications and to protect database integrity.
Analysis Process
Redis is used as a distributed lock store, centralising lock state and solving the problem of JVM isolation in a cluster.
Design Flow
Create a custom annotation @interface with input parameters.
Add an AOP pointcut to scan the specific annotation.
Define an @Aspect bean to intercept the target method.
Use ProceedingJoinPoint to wrap method execution.
Lock before execution and delete the key after the task completes.
Core steps: lock, unlock, and renewLock Acquisition
The implementation uses RedisTemplate.opsForValue().setIfAbsent to set a key only if it does not exist, assigning a random UUID as the value.
After acquiring the lock, an expiration time is set so the key is automatically released when it expires.
Only the first request that successfully sets the key proceeds; subsequent requests fail and exit.
Timeout Issue
If the intercepted method takes longer than the lock timeout, the key may be released prematurely, allowing another thread to acquire the lock and cause data inconsistency.
Solution: Add a Renewal Mechanism
Task Not Completed, Lock Not Released
A ScheduledExecutorService runs every 2 seconds, scanning a queue of tasks and extending the expiration time when the remaining lifetime falls below one‑third of the total 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 Solution
The final design includes four core steps:
Intercept @RedisLock annotation and obtain parameters.
Perform lock operation.
Execute renewal logic.
Release the lock after business execution.
Practical Implementation
The RedisLockAspect class is divided into three parts:
Pointcut Definition
@Pointcut("@annotation(cn.sevenyuan.demo.aop.lock.RedisLockAnnotation)")
public void redisLockPC() {}Around Advice (Lock & Unlock)
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Method method = resolveMethod(pjp);
RedisLockAnnotation annotation = method.getAnnotation(RedisLockAnnotation.class);
String businessKey = annotation.typeEnum().getUniqueKey(pjp.getArgs()[annotation.lockFiled()].toString());
String uniqueValue = UUID.randomUUID().toString();
Object result = null;
try {
boolean isSuccess = redisTemplate.opsForValue().setIfAbsent(businessKey, uniqueValue);
if (!isSuccess) {
throw new Exception("You can't do it, because another has got the lock");
}
redisTemplate.expire(businessKey, annotation.lockTime(), TimeUnit.SECONDS);
holderList.add(new RedisLockDefinitionHolder(businessKey, annotation.lockTime(), System.currentTimeMillis(), Thread.currentThread(), annotation.tryCount()));
result = pjp.proceed();
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException("You had been interrupted");
}
} catch (InterruptedException e) {
log.error("Interrupt exception, rollback transaction", e);
throw new Exception("Interrupt exception, please send request again");
} catch (Exception e) {
log.error("has some error, please check again", e);
} finally {
redisTemplate.delete(businessKey);
log.info("release the lock, businessKey is [" + businessKey + "]");
}
return result;
}Renewal Operation
private static final ConcurrentLinkedQueue<RedisLockDefinitionHolder> holderList = new ConcurrentLinkedQueue<>();
private static final ScheduledExecutorService SCHEDULER = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());
static {
SCHEDULER.scheduleAtFixedRate(() -> {
Iterator<RedisLockDefinitionHolder> 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);
log.info("businessKey : [" + holder.getBusinessKey() + "], try count : " + holder.getCurrentCount());
holder.setCurrentCount(holder.getCurrentCount() + 1);
}
}
}, 0, 2, TimeUnit.SECONDS);
}Testing
A sample controller method is annotated with @RedisLockAnnotation and simulates a long‑running task using Thread.sleep. The logs show lock acquisition, renewal attempts, and eventual interruption when the retry limit is exceeded.
2020-04-04 14:55:50.864 INFO ... : 睡眠执行前
2020-04-04 14:55:52.855 INFO ... : businessKey : [Business1:1024], try count : 0
... (subsequent renewal logs) ...
java.lang.InterruptedException: sleep interruptedConclusion
For time‑consuming business operations, a distributed lock prevents concurrent modifications and ensures data correctness. The article demonstrates annotation‑driven lock management, AOP interception, automatic renewal via a scheduled thread pool, and proper lock release, providing a practical reference for implementing reliable distributed locks in Java back‑ends.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
