欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Redisson实现分布式锁(二)

程序员文章站 2022-04-28 13:15:04
本次基于注解+AOP实现分布式锁(招式与前文基于注解切换多数据源相同),话不多说,直接上样例: 首先自定义注解:设计时需要考虑锁的一般属性:keys,最大等待时间,超时时间,时间单位。 新建一个抽象请求拦截器,设计模式:装饰模式,父类决定整体流程,具体细节交给字类实现,便于解耦扩展。 具体实现拦截类 ......

本次基于注解+aop实现分布式锁(招式与前文基于注解切换多数据源相同),话不多说,直接上样例:

首先自定义注解:设计时需要考虑锁的一般属性:keys,最大等待时间,超时时间,时间单位。

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;
}

新建一个抽象请求拦截器,设计模式:装饰模式,父类决定整体流程,具体细节交给字类实现,便于解耦扩展。

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 {
        //获取连接点的方法签名对象
        signature signature = point.getsignature();
        methodsignature methodsignature = (methodsignature) signature;

        method method = methodsignature.getmethod();
        string methodname = signature.getname();

        //获取连接点所在的目标对象
        string targetname = point.gettarget().getclass().getname();

        //获取连接点方法运行时的入参列表
        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 {
                    //释放锁资源
                    lock.unlock();
                }
            } else {
                throw new runtimeexception("获取锁资源失败");
            }
        }

        //通过反射执行目标对象的连接点处的方法
        return point.proceed();
    }

    /**
     * 组装lock key
     *
     * @param method
     * @param targetname 对象名
     * @param methodname 方法名
     * @param keys       注解key
     * @param arguments  方法参数
     * @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包
            //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);

                //用spell方法容易出问题,所以这里加了一些限制
                keystr = creatspellexpressionstr(keystr,parameters,removesigs);

                int length = parameters.length;
                if(length > 0){
                    if(!hasparameters(keystr,parameters)){
                        //不包含参数直接用keystr
                        lockkey.append("#").append(keystr);
                    }else{
                        //keystr 是否包含参数名,如果包含可用spell表达式
                        //用spell方法容易出问题,所以这里加工下
                        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:方法参数名,val:值
                            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){
        //去掉#{}等字符
        for(string sig : removesigs){
            lockkey = stringutils.replace(lockkey,sig,"");
        }

        for(string str : parameters){
            string repstr = "#{#"+str+"}";
            lockkey = stringutils.replace(lockkey,str,repstr);
        }
        return lockkey;
    }

}

具体实现拦截类:

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
 /**
     * 注意:key={"#prokey","#proid"} 与参数名一致时走spell表达式
     * @param prokey
     * @param proid
     */
    @requestlockable(key={"#{#prokey}","#{#proid}"}) //细化到参数,参数值不同时,不共享一个锁,相同时才会共享,这点要注意
    //@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();
        }
    }

单元测试(比较懒,就不另外起工程测了,开多线程...)

@test
    public void testanotationprod(){
        //开启2个线程
        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();
        }
    }

场景1:方法加注释:@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

场景2:方法加注释:@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

场景3:方法不加注释,此时两个线程同时去读写了
>>>>>>thread-9,product:88
>>>>>>thread-10,product:88

场景4:测试另一个线程,参数proid=2,发现两个线程同时去读写了
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