最新公告
  • 新注册用户请前往个人中心绑定邮箱以便接收相关凭证邮件!!!点击前往个人中心
  • AOP的应用之分布式锁

    大家在做分布式多节点等系统的开发中为了保证某些业务操作场景的原子性操作,一定会用到锁的概念,传统的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锁实现方式有很多,上面只是实现了一种最简单的锁,不够严谨,没有家失效时间等,大家使用的话可根据自己的应用场景进行相应的改造
    本站所有文章均由网友分享,仅用于参考学习用,请勿直接转载,如有侵权,请联系网站客服删除相关文章。若由于商用引起版权纠纷,一切责任均由使用者承担
    极客文库 » AOP的应用之分布式锁

    常见问题FAQ

    如果资源链接失效了怎么办?
    本站用户分享的所有资源都有自动备份机制,如果资源链接失效,请联系本站客服QQ:2580505920更新资源地址。
    如果用户分享的资源与描述不符怎么办?
    可以联系客服QQ:2580505920,如果要求合理可以安排退款或者退赞助积分。
    如何分享个人资源获取赞助积分或其他奖励?
    本站用户可以分享自己的资源,但是必须保证资源没有侵权行为。点击个人中心,根据操作填写并上传即可。资源所获收益完全归属上传者,每周可申请提现一次。
    如果您发现了本资源有侵权行为怎么办?
    及时联系客服QQ:2580505920,核实予以删除。

    参与讨论

    • 211会员总数(位)
    • 3737资源总数(个)
    • 0本周发布(个)
    • 0 今日发布(个)
    • 862稳定运行(天)

    欢迎加入「极客文库」,成为原创作者从这里开始!

    立即加入 了解更多
    成为赞助用户享有更多特权立即升级