Implementing a Distributed Redis Lock with Spring AOP and Automatic Renewal
This article explains how to protect time‑consuming business operations and critical data by designing a Redis‑based distributed lock using custom annotations, Spring AOP, and a ScheduledExecutorService that automatically extends the lock’s expiration to avoid premature release.
The business scenario involves long‑running operations that require exclusive access to shared data, necessitating a distributed lock to prevent concurrent modifications.
Using Redis as a central lock store, the design introduces a custom @RedisLockAnnotation to mark methods that need locking, an AOP aspect ( RedisLockAspect ) to intercept these methods, and a scheduled thread pool to periodically extend the lock’s TTL.
Lock Acquisition
When a method annotated with @RedisLockAnnotation is invoked, the aspect extracts the lock key, generates a unique UUID, and attempts to set the key in Redis with opsForValue().setIfAbsent . If successful, the lock’s expiration is set via expire , and the task information is stored in a concurrent queue for renewal.
Timeout Issue
If the protected method takes longer than the lock’s timeout, the key may expire early, allowing another thread to acquire the lock and cause data inconsistency.
Solution – Automatic Renewal
A ScheduledExecutorService runs every two seconds, scans the queue, and extends the lock’s expiration when the remaining time falls below one‑third of the original timeout. It also handles retry limits and interrupts the thread if the lock cannot be renewed.
/**
* 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(() -> {
// iterate over holderList, extend TTL if needed
}, 0, 2, TimeUnit.SECONDS);
}Annotation Definition
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RedisLockAnnotation {
int lockFiled() default 0;
int tryCount() default 3;
RedisLockTypeEnum typeEnum();
long lockTime() default 30;
}Aspect Implementation
@Around(value = "redisLockPC()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Method method = resolveMethod(pjp);
RedisLockAnnotation annotation = method.getAnnotation(RedisLockAnnotation.class);
String businessKey = ...;
String uniqueValue = UUID.randomUUID().toString();
boolean isSuccess = redisTemplate.opsForValue().setIfAbsent(businessKey, uniqueValue);
if (!isSuccess) {
throw new Exception("You can't do it, because another has get the lock =-=");
}
redisTemplate.expire(businessKey, annotation.lockTime(), TimeUnit.SECONDS);
// add to holderList, proceed, finally delete key
}Testing
A test endpoint annotated with @RedisLockAnnotation simulates a long‑running request using Thread.sleep . Logs show the lock being acquired, renewed, and finally released, while concurrent requests are blocked.
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) ...
2020-04-04 14:56:00.857 INFO ... : has some error
java.lang.InterruptedException: sleep interruptedIn summary, the article demonstrates how to combine Redis, Spring AOP, and a scheduled executor to create a reliable distributed lock with automatic renewal, ensuring that time‑consuming operations do not lead to data corruption.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.