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)