Annotations elegantly implement Redisson distributed locks

1Foreword

In daily development, it is inevitable to encounter some concurrency scenarios. In order to ensure the consistency of interface execution, locking is usually used. Because the service is a distributed deployment mode, the local locks Reentrantlock and Synchnorized are put aside first. Redis’s The problem of setnx lock being unable to guarantee atomicity will be put aside for the time being, and I will go directly to the big move. Redisson is also the cache that I basically use in my recent development projects, and I also use its distributed lock mechanism.

2Redisson distributed lock general use

Regarding some basic concepts of Redisson, this chapter will not explain it in too much detail. Interested friends can learn about it by themselves. It mainly talks about the regular use of locking. Redisson distributed lock is based on Redis’s Rlock lock, which is implemented Lock interface under JavaJUC package.

Lock

public void getLock(){
    //Get the lock
    RLock lock = redisson.getLock("Lxlxxx_Lock");
    try {
        // 2. Lock
        lock.lock();

    } catch (InterruptedException e) {
        e.getStackTrace();
    } finally {
        // 3. Unlock
        lock.unlock();
        System.out.println("Finally, lock released successfully");
    }

getLock obtains the lock and lock.lock performs the locking. The problem that occurs is that lock waits until it cannot get the lock and enters a blocking state. Obviously this is not good.

TryLock

Returns a boolean type, which has the same meaning as Reentrantlock’s tryLock. Try to acquire the lock. If it is obtained, it will return true. If the acquisition fails, it will return false. It will not keep the thread that cannot obtain the lock in a waiting state. Returning false can continue to perform the following business. Logic, of course, the Ression lock also involves the watchDog watchdog mechanism. The main function is to renew the lock that is about to expire. The main purpose is to allow the limited time for the lock to be completed before the lock is released.

RLock lock = redisson.getLock(name);
try {

    if (lock.tryLock(2, 10, TimeUnit.SECONDS)) {
        //Execute business logic
    } else {
        System.out.println("Already exists");
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}finally {
//Determine whether the lock held by the current thread is in the locked state, and then release it in the locked state.
    if (this.redissonLock.isHeldByCurrentThread(lockName)) {
        this.redissonLock.unlock(lockName);
    }
}

3 Custom annotations to implement lock mechanism

Usually we inject the redisson instance into the method class, and then call the locking method to lock. If other business methods also need to be locked for execution, a lot of duplicate code will be generated. Therefore, using the AOP aspect method, you only need to pass annotations The method can be locked.

Custom annotations

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DistributedLock {
    String key() default "";

    int leaseTime() default 10;

    boolean autoRelease() default true;

    String errorDesc() default "The system is processing normally, please submit later";

    int waitTime() default 1;
}

Aspect class implementation

@Aspect
@Component
public class DistributedLockHandler {
    private static final Logger log = LoggerFactory.getLogger(DistributedLockHandler.class);
    @Autowired
    RedissonLock redissonLock;

    public DistributedLockHandler() {
    }

    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        String lockName = this.getRedisKey(joinPoint, distributedLock);
        int leaseTime = distributedLock.leaseTime();
        String errorDesc = distributedLock.errorDesc();
        int waitTime = distributedLock.waitTime();

        Object var8;
        try {
            boolean lock = this.redissonLock.tryLock(lockName, (long)leaseTime, (long)waitTime);
            if (!lock) {
                throw new RuntimeException(errorDesc);
            }

            var8 = joinPoint.proceed();
        } catch (Throwable var12) {
            log.error("Exception in executing business method", var12);
            throw var12;
        } finally {
            if (this.redissonLock.isHeldByCurrentThread(lockName)) {
                this.redissonLock.unlock(lockName);
            }

        }

        return var8;
    }


    /**
     * Get the locked key
     * @param joinPoint
     * @param distributedLock
     * @return
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
        String key = distributedLock.key();
        Object[] parameterValues = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = nameDiscoverer.getParameterNames(method);
        if (StringUtils.isEmpty(key)) {
            if (parameterNames != null & amp; & amp; parameterNames.length > 0) {
                StringBuffer sb = new StringBuffer();
                int i = 0;

                for(int len = parameterNames.length; i < len; + + i) {
                    sb.append(parameterNames[i]).append(" = ").append(parameterValues[i]);
                }

                key = sb.toString();
            } else {
                key = "redissionLock";
            }

            return key;
        } else {
            SpelExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(key);
            if (parameterNames != null & amp; & amp; parameterNames.length != 0) {
                EvaluationContext evaluationContext = new StandardEvaluationContext();

                for(int i = 0; i < parameterNames.length; + + i) {
                    evaluationContext.setVariable(parameterNames[i], parameterValues[i]);
                }

                try {
                    Object expressionValue = expression.getValue(evaluationContext);
                    return expressionValue != null & amp; & amp; !"".equals(expressionValue.toString()) ? expressionValue.toString() : key;
                } catch (Exception var13) {
                    return key;
                }
            } else {
                return key;
            }
        }
    }
}

Specific use

ac2d3b8ca952da61ce662418b4999f82.jpeg

Add custom annotations to the method header, the key parameter represents the key that needs to be locked, and errorDesc will prompt an error message if it fails to obtain the lock.

Here I started the project by modifying the port and started two services, namely 8460 and 8461.

6aa97a6d8d7cdcf31893fbe4bc23632b.jpeg
fd2daf8e022b478b97318961e8bebafb.jpeg

Call these two services through postman to simulate the scenario where two services acquire a lock at the same time. One service acquires the lock, and the other service fails to acquire the lock.

3d8cbb91fb90745317a01fe543899bfa.jpeg

You can see that the port 8460 service gets the lock first, and the 8461 service tryLock fails to get the lock, and the locking logic is implemented.

91608538a2b42e72c9d5343e65aad5db.jpeg
61c2d025ffdff4dc823f48e24c935fb2.jpeg

4Summary

The use scenarios of distributed locks still require more attention. According to the business scenario, if the amount of concurrency is not large, there is actually no need to add it. You may need to pay attention to concurrency when the mobile terminal operates more frequently. Currently, I am working on the b-side Projects can avoid repeated submissions through simple interface idempotence operations. Do not blindly lock, which will affect some performance.

Source: juejin.cn/post/7215142807861379109

Back-end exclusive technology group

To build a high-quality technical exchange community, HR personnel engaged in programming development and technical recruitment are welcome to join the group. Everyone is also welcome to share their own company’s internal information, help each other, and make progress together!

Speech in a civilized manner, focusing on communication technology, recommendation of positions, and industry discussion

Advertisers are not allowed to enter, and do not trust private messages to prevent being deceived.

fd5b6eb04a0a9e51cefd4dddaa93eb49.png

Add me as a friend and bring you into the group
The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Cloud native entry-level skills treeHomepageOverview 16,889 people are learning the system