• 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html
  • 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html

AOP的应用之分布式锁

技术杂谈 勤劳的小蚂蚁 3个月前 (02-02) 57次浏览 已收录 0个评论 扫描二维码

大家在做分布式多节点等系统的开发中为了保证某些业务操作场景的原子性操作,一定会用到锁的概念,传统的synchronized无法满足分布式多节点的系统,所以大家都会用Redis实现分布式锁,怎么实现我这里就先不多说了大家百度一下可以查到一大堆。
但还是要简单的说一下主要就是使用redis的setnx(key,value)方法配合del(key)方法,也就是在第一个请求进来的时候执行这个方法,会将一个key放到redis中,如果redis中该key已经存在了那么就返回0,否则返回1,这样当第一个请求处理完后调用del方法,后面的人就可以再进来了,ok用redis实现分布式锁大概就这个逻辑。
然后基于上面的分布式锁的逻辑,如果你要保证方法的原子性操作,那么就要写这样一串if else,多个地方用到的话冗余代码就太多而且代码看上去不美观,那么如何简化呢?这里考虑用到了aop的方法拦截器技术结合自定义注解来实现,下面直接看代码。
先定义两个注解
@Target({ElementType.TYPE, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface NeedLock {    
   StringfieldKey();  

}

/**
* @author chenbin.sun
* 动态key需要配置的注解,只有配置了该注解才能够开启动态key功能
*/
@Documented
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface KeyParam {

 /**
  * 如果动态参数在command对象中,那么就需要设置columns的值为command对象中的属性名可以为多个,否则不需要设置该值
  * <p>例1:public void test(@KeyParam({“id”}) MemberCommand member)
  * <p>例2:public void test(@KeyParam({“id”,”loginName”}) MemberCommand member)
  * <p>例3:public void test(@KeyParam String memberId)
  * @return
  */
 String[]columns() default {};
}

然后编写具体方法,这里实现了aop的方法拦截器,这种效果类似于aop的环绕通知
/**
* @author chenbin.sun
* @description分布式系统的方法进行统一加锁解锁(使用了aop的环绕)
* <p> 使用该方法进行加锁,必须在要加锁的方法上使用@NeedLock注解,如果要使用动态key的话需要配合@KeyParam注解
*/
@Service(“lockMethodHelper”)
publicclassLockMethodHelperimplementsMethodInterceptor{
 
 privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(LockMethodHelper.class);
 
 private Long status = 0L;
 
 publicstaticfinal String BEFORE_KEY = “LOCK_METHOD_”;
 
 @Autowired
 private LotteryRedisManager lotteryRedisManager;
 
 @Override
 public Object invoke(MethodInvocation invocation)throws Throwable {
   Method method = invocation.getMethod();
   Object[] args = invocation.getArguments();
   
   // 执行方法前
   boolean before = before(method, args);
   
   // 加锁失败
   if (!before) {
     returnnew ResultReturnCommand(ResultReturnCodeEnum.LOTTERY_REDIS_LOCK_FAIL);
     //return new Object();
   }
   
   Object result = null;
   try {
     
     // 通过反射机制调用目标方法
     result = invocation.proceed();
   } catch (Exception e) {
     LOGGER.error(“执行目标方法抛出异常”,e);
     returnnew ResultReturnCommand(ResultReturnCodeEnum.LOTTERY_REDIS_UNLOCK_FAIL);
   } finally {
     
     // 执行方法后解锁
     afterReturning(method, args);
   }
   return result;
 }

 /**
  * 方法执行前的加锁操作
  * @param method
  * @param args
  * @return
  * @throws Throwable
  */
 publicbooleanbefore(Method method, Object[] args)throws Throwable {
   
   // 构造锁key
   String key = buildRedisKey(method, args);
   if (null == key) {
     returntrue;
   }
   Long status = lotteryRedisManager.setnx(key, key);
   if (status.equals(status)) {
     returnfalse;
   }
   returntrue;
 }
 
 /**
  * 方法执行后的解锁操作
  * @param method
  * @param args
  * @return
  * @throws Throwable
  */
 publicbooleanafterReturning(Method method, Object[] args)throws Throwable {
   
   // 构造锁key
   String key = buildRedisKey(method, args);
   if (null == key) {
     returntrue;
   }
   lotteryRedisManager.delKeyAndValue(key);
   returntrue;
 }

 /**
  * 构造锁key
  * @param method
  * @param args
  * @return
  * @throws NoSuchFieldException
  * @throws IllegalAccessException
  */
 private String buildRedisKey(Method method, Object[] args)
     throws NoSuchFieldException, IllegalAccessException {
   NeedLock needLock = method.getAnnotation(NeedLock.class);
   if (null == needLock) {
     returnnull;
   }
   
   // 锁的前半部分key
   String key = BEFORE_KEY + needLock.fieldKey();
   
   // 迭代全部参数的注解,根据使用KeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上
   Annotation[][] parameterAnnotations = method.getParameterAnnotations();
   for (int i = 0; i < parameterAnnotations.length; i++) {
     
     // 循环该参数全部注解
     for (Annotation annotation : parameterAnnotations[i]) {
       
       // 当前参数的注解不包含keyparam
       if(!annotation.annotationType().isInstance(KeyParam.class)){
         continue;
       }
       
       // 当前参数的注解包含keyparam,获取注解配置的值
       String[] columns = ((KeyParam)annotation).columns();
       if (columns.length == 0) {
         
         // 普通数据类型直接拼接
         if (null == args[i]) {
           LOGGER.error(“动态参数不能为null!”);
           thrownew RuntimeException(“动态参数不能为null!”);
         }
         key += args[i];
       }else{
         
         // keyparam的columns值不为null,所以当前参数应该是对象类型
         for (int j = 0; j < columns.length; j++) {
           Class<? extends Object> clasz = args[i].getClass();
           Field declaredField = clasz.getDeclaredField(columns[j]);
           declaredField.setAccessible(true);
           Object value = declaredField.get(clasz);
           key += value;
         }
       }
       
     }
   }
   return key;
 }

}

怎么使用呢?看下面这个例子
@Override
 @NeedLock(fieldKey = LotteryActivityConstants.REDIS_LOCK_MARK)
 public ResultReturnCommand create(@KeyParamString code, @KeyParam Long memLaunchId) {

   // 对活动做相关校验
   ResultReturnCommand resultReturnCommand = lotteryActivityDetailManager.checkLotteryActivity(code);

   if (!LotteryActivityConstants.SUCCESS_CODE.equals(resultReturnCommand.getCode())) {
     return resultReturnCommand;
   }

   // 1根据code查询是否有对应的活动信息
   LotteryActivityCommand lotteryActivityCommand = (LotteryActivityCommand) resultReturnCommand.getData();

   // 2根据活动ID 和发起者ID查询对应的信息
   LotteryMemLaunch lotteryMemLaunch = lotteryMemLaunchDao.findLotteryMemLaunchBycodeActivityIdAndMemberId(lotteryActivityCommand.getId(), memLaunchId);

   // 5校验是否瞒足发起活动的条件
   if (!LotteryValidator.checkEffective(lotteryMemLaunch)) {
     returnnew ResultReturnCommand(ResultReturnCodeEnum.ACTIVITY_NOT_FINISHED);
   }

   // 3插入发起活动记录
   LotteryMemLaunch ret = savelotteryMemLaunch(lotteryActivityCommand, memLaunchId);

   returnnew ResultReturnCommand(ResultReturnCodeEnum.SUCCESS, ret);

 }
只要在方法上加上@NeedLock,然后给个锁的key就可以了,如果你的锁的key是动态的,那么在方法的参数上结合使用@KeyParam注解就可以了,详细使用方法请看最上面,注解的注释即可。
要真正使用这个东西还有一点必不可少的东西就是要在spring的配置中配置aop的拦截范围,配置如下
<!– 统一加锁aop方法 –>
 <aop:config>
     <aop:pointcutexpression=“execution(* com.chenbin.sun.plugin.lottery.red.packet.manager..*(..))”id=“lockMethodPointcut”/>
     <aop:advisoradvice-ref=“lockMethodHelper”pointcut-ref=“lockMethodPointcut”/>
 </aop:config>
本文只是描述一个思想,具体的redis锁实现方式有很多,上面只是实现了一种最简单的锁,不够严谨,没有家失效时间等,大家使用的话可根据自己的应用场景进行相应的改造

丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:AOP的应用之分布式锁
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

您必须 登录 才能发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00