发布网友 发布时间:2024-09-11 14:17
共1个回答
热心网友 时间:2024-10-20 06:56
一、背景介绍随着高并发场景的逐渐增多,各类系统中(尤其是类似秒杀系统这种对于并发度要求很高的场景下)对于分布式锁的需求逐渐增大。
而在分布式锁的代码实现中,往往会存在如下情况
publicvoidmethod(){//业务代码try{booleanb=redisClient.tryLock(key,value);if(b){//......逻辑判断}//获取锁后所要执行的业务代码}catch(Exceptione){log.error("error");thrownewException();}finally{if(redisClient.get(key).equals(value)){redisClient.tryRelease(key);}}//业务代码}大概写了段比较简单的代码,意思是在很多场景下,都是这样子在实现分布式锁的,具体redisClient是怎么实现的这个根据情况可以自己选择。
这段代码的问题在于,如果需要分布式锁的场景太多,那么整个系统中就会处处都存在这样的trycatchfinally的代码块,这样会使得整体代码显得十分臃肿,重复代码太多
那么怎么去解决这种使得代码过于臃肿的问题呢?
答案很简单:使用AOP技术,将加入分布式锁的部分以切面的方式剥离出业务代码中
具体怎么实现呢?
二、分布式锁注解+切面的简单实现首先我们实现一个注解,注解命名建议要直观,让人可以一眼看出该注解的作用
@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic@interfaceRedisDistributedLock{//锁定的资源的值,即存入redis的keyStringkey()default"default_key";//锁保持时间(以毫秒为单位)intkeepMills()default1*10*1000;}关于自定义注解的方式,本文不再赘述,有兴趣的可以自行去了解,
我们希望使用@RedisDistributedLock这个注解去实现分布式锁,即如下所示。
@RedisDistributedLock(key="key_example",keepMills=2*10*1000)publicvoidmethod(){//业务代码}即我们想在执行method方法之前,可以对key_example加锁,如果不设置key,则默认为default_key,释放锁的时间为20秒,如果不加设置,则默认为10秒
那么具体AOP切面的简单实现如下
@Aspect@Component@Slf4jpublicclassRedisDistributedLockAspect{@AutowiredprivateRedisClientredisClient;@Around(value="@annotation(redisDistributedLock)")publicObjectinvoke(ProceedingJoinPointpoint)throwsThrowable{Signaturesignature=point.getSignature();MethodSignaturemethodSignature=(MethodSignature)signature;MethodtargetMethod=methodSignature.getMethod();//获取方法上的注解RedisDistributedLockredisLock=targetMethod.getAnnotation(RedisDistributedLock.class);Stringkey=redisLock.key();if(StringUtils.isEmpty(key)){key=point.getTarget().getClass().getName()+"."+targetMethod.getName();}BooleanisLocked=redisClient.tryLock(key,"true",redisLock.keepMills());if(!isLocked){logger.debug("getlockfailed:"+key);returnnull;}logger.debug("getlocksuccess:"+key);try{returnproceedingJoinPoint.proceed();}catch(Exceptione){logger.error("executelockedmethodoccuredanexception",e);}finally{if(redisClient.get(key,String.class).equals(value)){redisClient.tryRelease(key);}}returnnull;}}三、动态的方法入参作为分布式锁的key那么通过这种方式就可以实现对于method()方法上锁了,但是这个样还是有点不太方便
如果我想对于method()方法传入的参数加锁而不是只能写死一个key该怎么办
如下:
@RedisDistributedLock(key="param",keepMills=2*10*1000)publicvoidmethod(Stringparam){//业务代码}如果我们可以动态的对于方法中传入的参数加锁,那么这个注解使用起来就更加的方便了
此处我们可以使用Spel表达式来进行处理,关于SPel表达式,请各位自己去查询资料,我这里只给出了最终的代码
@Aspect@Component@Slf4jpublicclassRedisDistributedLockAspect{@AutowiredprivateRedisClientredisClient;privatestaticLoggerlogger=LoggerFactory.getLogger(RedisDistributedLockAspect.class);@Around(value="@annotation(redisDistributedLock)")publicObjectinvoke(ProceedingJoinPointproceedingJoinPoint,RedisDistributedLockredisDistributedLock)throwsThrowable{Stringkey=redisDistributedLock.key();Object[]arg=proceedingJoinPoint.getArgs();Methodmethod=((MethodSignature)proceedingJoinPoint.getSignature()).getMethod();//获取被拦截方法参数名列表(使用Spring支持类库)LocalVariableTableParameterNameDiscovererlocalVariableTable=newLocalVariableTableParameterNameDiscoverer();String[]parameterNames=localVariableTable.getParameterNames(method);//使用SPEL进行key的解析ExpressionParserparser=newSpelExpressionParser();//SPEL上下文StandardEvaluationContextcontext=newStandardEvaluationContext();//把方法参数放入SPEL上下文中for(inti=0;i<parameterNames.length;i++){context.setVariable(parameterNames[i],arg[i]);}//使用变量方式传入业务动态数据if(valueOfKey.matches("^#.*.$")){valueOfKey=parser.parseExpression(key).getValue(context,String.class);}//使用UUID生成随机数作为value,避免出现锁被错误释放的问题Stringvalue=UUID.randomUUID().toString();BooleanisLocked=redisClient.tryLock(key,value,redisDistributedLock.keepMills());if(!isLocked){logger.debug("getlockfailed:"+key);returnnull;}logger.debug("getlocksuccess:"+key);try{returnproceedingJoinPoint.proceed();}catch(Exceptione){logger.error("executelockedmethodoccuredanexception",e);}finally{if(redisClient.get(key,String.class).equals(value)){redisClient.tryRelease(key);}}returnnull;}}这样子的话,在方法中只要以如下的方式,就可以对于方法中传入的参数加锁了
@RedisDistributedLock(key="#param",keepMills=2*10*1000)publicvoidmethod(Stringparam){//业务代码}