Redisson realizes distributed lock

This time, the distributed lock is implemented based on annotation + AOP (the move is the same as that of switching multiple data sources based on annotation in the previous article). If you don't say much, let's go to the example directly:

First of all, user-defined notes: the general properties of locks need to be considered during design: keys, maximum waiting time, timeout, time unit.

package com.paic.phssp.springtest.redisson;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestLockable {
    String[] key() default "";

    long maximumWaiteTime() default 2000;

    long expirationTime() default 1000;

    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

Create a new abstract request interceptor, design pattern: decoration pattern, the parent class determines the overall process, and the specific details are given to the word class implementation, which is convenient for decoupling and expansion.

package com.paic.phssp.springtest.redisson;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

public abstract class AbstractRequestLockInterceptor {
    protected abstract Lock getLock(String key);

    protected abstract boolean tryLock(long waitTime, long leaseTime, TimeUnit unit, Lock lock) throws InterruptedException;

    private static final String[] removeSigs = new String[]{"#","{","}"};

    @Around("@annotation(RequestLockable)")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        //Get the method signature object of the connection point
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;

        Method method = methodSignature.getMethod();
        String methodName = signature.getName();

        //Get the target object where the connection point is located
        String targetName = point.getTarget().getClass().getName();

        //Get the input parameter list of the connection point method runtime
        Object[] arguments = point.getArgs();

        if (method != null && method.isAnnotationPresent(RequestLockable.class)) {
            RequestLockable requestLockable = method.getAnnotation(RequestLockable.class);

            String requestLockKey = getLockBySpellKey(method, targetName, methodName, requestLockable.key(), arguments);

            System.out.println(">>>>requestLockKey="+requestLockKey);

            Lock lock = this.getLock(requestLockKey);

            boolean isLock = this.tryLock(requestLockable.maximumWaiteTime(), requestLockable.expirationTime(),
                    requestLockable.timeUnit(), lock);
            if (isLock) {
                try {
                    return point.proceed();
                } finally {
                    //Release lock resource
                    lock.unlock();
                }
            } else {
                throw new RuntimeException("Failed to get lock resource");
            }
        }

        //Method at the connection point of the target object by reflection
        return point.proceed();
    }

    /**
     * Assemble lock key
     *
     * @param method
     * @param targetName Object name
     * @param methodName Method name
     * @param keys       Annotate key
     * @param arguments  Method parameter
     * @return
     */
    private String getLockBySpellKey(Method method, String targetName, String methodName, String[] keys, Object[] arguments) {
        StringBuilder lockKey = new StringBuilder();
        lockKey.append("lock.").append(targetName).append(".").append(methodName);

        if (keys != null) {
            //Joiner  Guava package
            //String keyStr = Joiner.on(".").skipNulls().join(keys);
            String keyStr = Arrays.stream(keys).filter(Objects::nonNull).collect(Collectors.joining("."));
            System.out.println("RequestLockable: keys="+keyStr);

            if (!StringUtils.isBlank(keyStr)) {
                LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
                String[] parameters = discoverer.getParameterNames(method);

                //use Spell Methods are prone to problems, so here are some restrictions
                keyStr = creatSpellExpressionStr(keyStr,parameters,removeSigs);

                int length = parameters.length;
                if(length > 0){
                    if(!hasParameters(keyStr,parameters)){
                        //Direct use without parameters keyStr
                        lockKey.append("#").append(keyStr);
                    }else{
                        //keyStr Include parameter name, if available Spell Expression
                        //use Spell The method is easy to cause problems, so it is processed here
                        keyStr = creatSpellExpressionStr(keyStr,parameters,removeSigs);
                        ExpressionParser parser = new SpelExpressionParser();
                        EvaluationContext context = new StandardEvaluationContext();
                        Map<String,Object> vMap = new HashMap<String,Object>();
                        for (int i = 0; i < length; i++) {
                            //key:Method parameter name,val:value
                            vMap.put(parameters[i],arguments[i]);
                        }
                        ((StandardEvaluationContext) context).setVariables(vMap);

                        //eg:#{#proKey}.asd#{#proKey}fa.#{#proId}#{#proKey}  -> product.asdproductfa.123product
                        Expression expression = parser.parseExpression(keyStr,new TemplateParserContext());
                        String keysValue = expression.getValue(context, String.class);
                        lockKey.append("#").append(keysValue);
                    }
                }
            }
        }
        return lockKey.toString();
    }

    private boolean hasParameters(String lockKey,String[] parameters){
        boolean hasFlag = false;
        for(String str : parameters){
            if(StringUtils.indexOf(lockKey,str) != -1){
                hasFlag = true;
                break;
            }
        }
        return hasFlag;
    }

    private String creatSpellExpressionStr(String lockKey,String[] parameters,String[] removeSigs){
        //Remove#{} equal characters
        for(String sig : removeSigs){
            lockKey = StringUtils.replace(lockKey,sig,"");
        }

        for(String str : parameters){
            String repStr = "#{#"+str+"}";
            lockKey = StringUtils.replace(lockKey,str,repStr);
        }
        return lockKey;
    }

}

Specific implementation of interception class:

package com.paic.phssp.springtest.redisson;

import org.aspectj.lang.annotation.Aspect;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

@Aspect
@Component
public class RedisRequestLockInterceptor extends AbstractRequestLockInterceptor {
    @Resource
    private Redisson redisson;

    @Override
    protected Lock getLock(String key) {
        return redisson.getLock(key);
    }

    @Override
    protected boolean tryLock(long waitTime, long leaseTime, TimeUnit unit, Lock lock) throws InterruptedException {
        return ((RLock) lock).tryLock(waitTime, leaseTime, unit);
    }
}
ProductService.java
 /**
     * Note: key = {"ාාා񖓿ාාාා񖓿񖓿ා񖔩Setup setup setup setup
     * @param proKey
     * @param proId
     */
    @RequestLockable(key={"#{#proKey}","#{#proId}"}) //When the parameters are refined to different values, a lock will not be shared unless they are the same. Note that
    //@RequestLockable(key={"#lock_key_prod"})
    public void anotationProd(String proKey,int proId){
        String productId = stringRedisTemplate.opsForValue().get(proKey);
        int sprodId = Integer.parseInt(productId);
        if (sprodId > 0) {
            stringRedisTemplate.opsForValue().set("product", --sprodId + "");
            System.out.println(">>>>>>"+Thread.currentThread().getName() + ",product:" + sprodId + "");
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Unit test (if you are lazy, don't start another engineering test, start multithreading...)

@Test
    public void testAnotationProd(){
        //Open 2 threads
        Thread thread1 = new Thread(()-> productService.anotationProd("product",1));
        Thread thread2 = new Thread(()-> productService.anotationProd("product",1));

        thread1.start();
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Scenario 1: annotate the method: @ requestlockable (key = {"{" {"{" {"{" {prokey} "," {"{" proid} "})

RequestLockable: keys=#{#proKey}.#{#proId}
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
RequestLockable: keys=#{#proKey}.#{#proId}
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
>>>>>>Thread-10,product:92
>>>>>>Thread-9,product:91

Scenario 2: comment on method: @ requestlockable (key = {"ාාlock_key_prod"})
RequestLockable: keys=#lock_key_prod
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#lock_key_prod
RequestLockable: keys=#lock_key_prod
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#lock_key_prod
>>>>>>Thread-9,product:90
>>>>>>Thread-10,product:89

Scenario 3: the method is uncommented, and two threads are reading and writing at the same time
>>>>>>Thread-9,product:88
>>>>>>Thread-10,product:88

Scenario 4: test another thread with the parameter propid = 2. It is found that two threads are reading and writing at the same time
RequestLockable: keys=#{#proKey}.#{#proId}
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.1
RequestLockable: keys=#{#proKey}.#{#proId}
>>>>requestLockKey=lock.com.paic.phssp.springtest.redisson.ProductService.anotationProd#product.2
>>>>>>Thread-9,product:87
>>>>>>Thread-10,product:87

Reference resources:

https://blog.csdn.net/qq_15427331/article/details/54630999

Tags: Java Apache

Posted on Sat, 30 Nov 2019 01:09:54 -0800 by n_wattam