–
body
1
Some business requests are time-consuming operations that need to be locked to prevent subsequent concurrent operations, and at the same time operate on the data of the database, which needs to avoid affecting the previous business.
2
Using Redis as a distributed lock, the status of the lock is placed on Redis for unified maintenance, which solves the problem of non-interoperability of the stand-alone JVM information in the cluster, specifies the order of operations, and protects the user’s data correctly.
Sort out the design process
New Note @interface, set the input parameter flag in the annotation
Increase AOP tangents to scan specific annotations
Establish @Aspect facet tasks, register beans, and intercept specific methods
The specific method parameter, ProceedingJoinPoint, intercepts the method pjp.proceed() before and after
The lock is made before the point is cut, and the key is deleted after the task is executed
Core steps: Lock, unlock and renew
Lock
Using RedisTemplate’s opsForValue.setIfAbsent method, to determine whether there is a key, set a random number UUID.random().toString, and generate a random number as a value.
After the lock is acquired from the redis, set the expire expiration time on the key and automatically release the lock after expiration.
According to this design, only the first request that successfully sets the key can perform subsequent data operations, and subsequent requests will fail to end because the resource cannot be obtained 🔐.
Timeout issue
Worrying that the method executed by the pjp.proceed() tangent is too time-consuming, causing the key in Redis to be released earlier due to a timeout.
For example, if Thread A acquires the lock first, the proceed method takes time, the lock timeout is exceeded, and the lock is released when it expires, at which point another thread B successfully acquires the Redis lock, and both threads operate on the same batch of data at the same time, resulting in inaccurate data.
Solution: Add a “Continuation”
The task is not completed and the lock is not released:
Maintain a timed thread pool ScheduledExecutorService, every 2s to scan the queued Task, to determine whether the failure time is coming, the formula is: [Failure time] < = [current time] + [Failure interval (one-third timeout)]
3
After the above analysis, this scheme is designed:
Having already said the overall process, here are a few core steps:
Intercept the annotation @RedisLock to obtain the necessary parameters
Lock operation
Renewal operation
End the business and release the lock
4
I have also sorted out the use of AOP before, you can refer to it
Related property class configuration
Business property enumeration settings
The task queue holds the parameters
Sets the name of the annotation that was blocked
Core slice interception operations
RedisLockAspect.java The class is divided into three parts to describe its specific role
Pointcut settings
Around before and after locking and releasing locks
The previous steps defined the tangents we want to intercept, the next step is to do some custom operations before and after the tangents:
The above process is briefly summarized:
Parse the annotation parameters to get the annotation values and the parameter values on the method
Redis locks and sets a timeout period
Add this Task information to the “delay” queue, renew, and release the lock in advance
A thread interrupt flag has been added
The request ends and the lock is released at last
Renewal operation
Here, ScheduledExecutorService is used, which maintains a thread that constantly judges tasks in the task queue and increases the timeout:
This code is used to implement the idea of the dotted box in the design diagram, avoiding a time-consuming request that causes the lock to be released early.
Added “thread interrupt” **Thread#interrupt here, hoping to make the thread interrupt after exceeding the number of retries** (without rigorous testing, just for reference hahahahaha)
However, we recommend that if you encounter such time-consuming requests, you can still find the root cause, analyze the time-consuming path, and perform business optimization or other processing to avoid these time-consuming operations.
So remember to log more often, and analyze the problem a little faster. Record the project log, one note is done
5
In one entry method, the annotation is used, and then time-consuming requests are simulated in the business, using Thread#sleep
When used, add the annotation to the method, and then set the corresponding parameters, according to the typeEnum can distinguish between multiple services, limiting the service to be operated at the same time.
Test Results:
What I’m testing here is a scenario where too many retries and failures can be achieved if you reduce the sleep time, and you can make the business execute normally.
If you request both, you will find the following error message:
This means that our lock 🔐 does take effect, avoiding duplicate requests.
6
For time-consuming business and core data, you can’t let duplicate requests manipulate the data at the same time to avoid incorrect data, so you need to use distributed locks to protect them.
Let’s sort out the design process:
New Note @interface, set the input parameter flag in the annotation
Increase AOP tangents to scan specific annotations
Establish @Aspect facet tasks, register beans, and intercept specific methods
The specific method parameter, ProceedingJoinPoint, intercepts the method pjp.proceed() before and after
The lock is made before the point is cut, and the key is deleted after the task is executed
This study is through the code design of Review partners, from which to understand the specific implementation of distributed locks, modeled on his design, re-wrote a simplified version of business processing. For the “continuation” operation that was not considered before, the daemon thread is used here to regularly judge and extend the timeout period, avoiding the early release of locks.
Therefore, three knowledge points were reviewed at the same time:
1. AOP implementation and common methods
2. The use of the scheduled thread pool ScheduledExecutorService and the meaning of the parameters
3, the meaning and usage of thread Thread#interrupt (this is very interesting, you can learn it in depth)