实现redis分布式锁

遇到个需要使用分布式锁的场景,觉得每个方法都去加锁麻烦,使用切面实现一下。记录一下实现方式。

1.  redis分布式锁  

     引用 基于redisTemplate的redis的分布式锁正确打开方式 文章代码:

@Component
@Slf4j
public class RedisDistributedLock {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    public static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    public boolean setLock(String key,String value, long expire) {
        try {
            RedisCallback<String> callback = (connection) -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                //String uuid = UUID.randomUUID().toString();
                return commands.set(key, value, "NX", "PX", expire);
            };
            String result = redisTemplate.execute(callback);

            return !Utils.isEmpty(result);
        } catch (Exception e) {
            log.error("set redis occured an exception", e);
        }
        return false;
    }

    public String get(String key) {
        try {
            RedisCallback<String> callback = (connection) -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                return commands.get(key);
            };
            String result = redisTemplate.execute(callback);
            return result;
        } catch (Exception e) {
            log.error("get redis occured an exception", e);
        }
        return "";
    }

    public boolean releaseLock(String key,String value) {
        // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
        try {
            List<String> keys = new ArrayList<>();
            keys.add(key);
            List<String> args = new ArrayList<>();
            args.add(value);

            // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
            // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
            RedisCallback<Long> callback = (connection) -> {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                }

                // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                }
                return 0L;
            };
            Long result = redisTemplate.execute(callback);

            return result != null && result > 0;
        } catch (Exception e) {
            log.error("release lock occured an exception", e);
        } finally {
            // 清除掉ThreadLocal中的数据,避免内存溢出
            //lockFlag.remove();
        }
        return false;
    }
}

2. 实现切面方式 (注解方式):

    a.  实现锁接口


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestLock {


    /**
     * key  注解使用方法上,传入redis的key
     */
    @NotNull
    String lockKey();
}

  b. 实现切面方式:

     第一种:反射机制实现:

@Aspect
@Component
@Slf4j
public class TestAspect {

    @Resource
    RedisDistributedLock redisDistributedLock;

	//已注解为切点  
    @Pointcut("@annotation(XXX.TestLock)")
    public void TestLock() {
    }

    @Around(value = "TestLock()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object result = null;

        Object target = joinPoint.getTarget();
        String methodName = joinPoint.getSignature().getName();
        // 获取注解
        Method method = getAccessibleMethodByName(target, methodName);
        TestLock TestLock = method.getAnnotation(TestLock.class);

        if(Utils.isBlank(TestLock)){
            return result;
        }
        //获取注解参数
        String key =  TestLock.lockKey();
        key = getRedisKey(key, joinPoint);
        String value = UUID.randomUUID().toString();
        long expireTime = 60000*3L;
		//设置锁
        boolean lock = redisDistributedLock.setLock(RedisKey.REDIS_KEY_LOCK + key,value, expireTime);
        try {
            if (!lock) {
                log.info("key:{}", key);
                return result;
            }
            result = joinPoint.proceed();
        }catch (Exception e) {
            log.error(key+":\\n\"" + e.getMessage());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }finally {
            redisDistributedLock.releaseLock(RedisKey.REDIS_KEY_LOCK + key,value);
        }
        return result;
    }

	//根据注解中的参数,获取方法参数中对应的属性值   ,如果对应属性中的值为空则返回注解中的参数key
    public String getRedisKey(String key,ProceedingJoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        if(Utils.isBlank(args)){
            return key;
        }
        Object ob1 = args[0];
        if(Utils.isBlank(args)){
            return key;
        }
        Object value = getValue(ob1, key);
        if(Utils.isBlank(value)){
            return key;
        }
        return value.toString();
    }

    /**
     * 获取对象对应的属性值
     */
    private Object getValue(Object class1,String key){
        Class<?> clazz1 = class1.getClass();
        Field[] field1 = clazz1.getDeclaredFields();
        try {
            for (Field aField1 : field1) {
                    aField1.setAccessible(true);
                    if (aField1.getName().equals(key)) {
                        return aField1.get(class1);
                    }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
        return null;
    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. 如向上转型到Object仍无法找到, 返回null. 只匹配函数名。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object...
     * args)
     */
    public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
        Validate.notNull(obj, "object can't be null");
        Validate.notBlank(methodName, "methodName can't be blank");

        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            Method[] methods = searchType.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    makeAccessible(method);
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(Method method) {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) {
            method.setAccessible(true);
        }
    }

}

第二种:使用表达式实现

@Aspect
@Component
@Slf4j
public class TestAspect {

    @Resource
    RedisDistributedLock redisDistributedLock;

	//已注解为切点  
    @Pointcut("@annotation(XXX.TestLock)")
    public void TestLock() {
    }

    @Around(value = "TestLock()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object result = null;

        Object target = joinPoint.getTarget();
        String methodName = joinPoint.getSignature().getName();
        // 获取注解
        Method method = getAccessibleMethodByName(target, methodName);
        TestLock testLock = method.getAnnotation(TestLock.class);

        if(Utils.isBlank(testLock)){
            return result;
        }
        //获取注解参数
        String key =  testLock.lockKey();
		//表达式方式实现
         if(!Utils.isBlank(joinPoint.getArgs())) {
            Expression expression = getExpression(joinPoint, testLock);
            Object expressionValue = expression.getValue(joinPoint.getArgs()[0]);
            if(!Utils.isBlank(expressionValue)) {
                key = expressionValue.toString();
            }
        }
        String value = UUID.randomUUID().toString();
        long expireTime = 60000*3L;
		//设置锁
        boolean lock = redisDistributedLock.setLock(RedisKey.REDIS_KEY_LOCK + key,value, expireTime);
        try {
            if (!lock) {
                log.info("key:{}", key);
                return result;
            }
            result = joinPoint.proceed();
        }catch (Exception e) {
            log.error(key+":\\n\"" + e.getMessage());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }finally {
            redisDistributedLock.releaseLock(RedisKey.REDIS_KEY_LOCK + key,value);
        }
        return result;
    }

    private static final ConcurrentHashMap<String, Expression> EL_EXPRESSION_CACHE = new ConcurrentHashMap<>();

    //表达式值获取
    private Expression getExpression(ProceedingJoinPoint joinPoint, TestLock idempotent) {

        String key = joinPoint.getSignature().getName();

        Expression exp = EL_EXPRESSION_CACHE.get(key);
        if (exp == null) {
            synchronized (EL_EXPRESSION_CACHE) {
                exp = EL_EXPRESSION_CACHE.get(key);
                if (exp == null) {
                    String idempotentIdEl = idempotent.lockKey();
                    ExpressionParser parser = new SpelExpressionParser();
                    exp = parser.parseExpression(idempotentIdEl);
                    EL_EXPRESSION_CACHE.put(key, exp);
                }
            }
        }
        return exp;
    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. 如向上转型到Object仍无法找到, 返回null. 只匹配函数名。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object...
     * args)
     */
    public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
        Validate.notNull(obj, "object can't be null");
        Validate.notBlank(methodName, "methodName can't be blank");

        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            Method[] methods = searchType.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    makeAccessible(method);
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(Method method) {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) {
            method.setAccessible(true);
        }
    }

}

 

3. 使用方式

  /**
    * 注解在方法上
    * systemUser 
    *
  **/
   @ScheduleLock(lockKey = "userId")
    public void Test(SystemUser systemUser) {
        createReportService.createPaymentTable();
    }


   //假设现SystemUser为下面所示
   public class SystemUser(){
       private String userId;
   }

  lockKey: 传入redis锁,该值可以直接是需要在redis加锁时候的key值,也可以设置为传入参数中的某个属性值,

 现假设systemUser中存在userId,并且userId为“123”,此时redis加锁时候的key值为“123”;

 假设systemUser不存在或者不存在userId的属性,此时redis加锁时候的key值为“userId”;

当传入参数为更复杂的对应时,反射获取参数的方式将不适用,用表达式方式可以获取,如下:

  /**
    * 注解在方法上
    * systemUser 
    *
  **/
   @ScheduleLock(lockKey = "systemUser1.userId")
    public void Test(SystemUser systemUser) {
        createReportService.createPaymentTable();
    }


   //假设现SystemUser为下面所示
   public class SystemUser(){
       private String userId;
        
       private SystemUser systemUser1; 
   }

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值