美文网首页
Spring aop优雅实现redis分布式锁 aop应用red

Spring aop优雅实现redis分布式锁 aop应用red

作者: 我瞎了听不见 | 来源:发表于2019-08-03 11:05 被阅读0次

    redis分布式锁切面实现

    # 说明

    网上找了一部分aop实现分布式锁的设计,感觉都不是特别好用,就自己写了一份。可以满足绝大部分分布式锁需求,请直接看代码,方法名很明了,注释也很明了啦,希望您看到不足的地方或者有不同见解的,还请再评论里回复,我会十分高兴和您探讨,十分感谢。

    # @DistributedLock

    注解定义

    ```java

    import org.springframework.core.annotation.AliasFor;

    import java.lang.annotation.Documented;

    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;

    /**

    * @author Zl

    * @date 2019/8/2

    * @since

    */

    @Target(ElementType.METHOD)

    @Retention(RetentionPolicy.RUNTIME)

    @Documented

    public @interface DistributedLock {

        /**

        * 过期时间

        * 时间按时间单位换算

        * @return

        */

        long expire() default 3;

        /**

        * 等待时长

        * 时间按时间单位换算

        * 当为0时,不等待 默认不等待

        *

        * @return

        */

        long waitTime() default 0;

        /**

        * 时间单位 默认为秒

        *

        * @return

        */

        TimeUnit timeUnit() default TimeUnit.SECONDS;

        /**

        * springEl表达式

        * 为空时取方法名称锁方法

        *

        * @return

        */

        String key() default "";

        /**

        * 定义lock作用域,避免key重复

        * 为空时取类完整包名

        *

        * @return

        */

        String lockName() default "";

        /**

        * 异常i18n编码定义

        * 用于获取失败后做异常信息抛出

        * @return

        */

        @AliasFor("errorCode")

        String value();

        @AliasFor("value")

        String errorCode() default "";

    }

    ```

    # RedisLockAspect

    redis分布式锁切面处理

    ```java

    import lombok.extern.slf4j.Slf4j;

    import org.apache.commons.lang3.StringUtils;

    import org.aspectj.lang.ProceedingJoinPoint;

    import org.aspectj.lang.annotation.Around;

    import org.aspectj.lang.annotation.Aspect;

    import org.aspectj.lang.annotation.Pointcut;

    import org.aspectj.lang.reflect.MethodSignature;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.expression.EvaluationContext;

    import org.springframework.expression.ExpressionParser;

    import org.springframework.expression.spel.standard.SpelExpressionParser;

    import org.springframework.expression.spel.support.StandardEvaluationContext;

    import org.springframework.stereotype.Component;

    import java.lang.reflect.Method;

    import java.util.concurrent.TimeUnit;

    /**

    * @author Zl

    * @date 2019/8/2

    * @since

    */

    @Slf4j

    @Aspect

    @Component

    public class RedisLockAspect {

        private ExpressionParser parser = new SpelExpressionParser();

        @Autowired

        private RedisLockUtils redisLockUtils;

        @Pointcut("@annotation(com.ztesoft.zsmart.nros.base.annotation.DistributedLock)")

        public void pointCut() {

        }

        @Around("pointCut()")

        public Object around(ProceedingJoinPoint point) throws Throwable {

            MethodSignature signature = (MethodSignature) point.getSignature();

            Method method = signature.getMethod();

            String className = point.getTarget().getClass().getName();

            Object[] args = point.getArgs();

            String[] paramNames = signature.getParameterNames();

            //参数写入SpringEl域中

            EvaluationContext context = new StandardEvaluationContext();

            for (int i = 0; i < args.length; i++) {

                context.setVariable(paramNames[i], args[i]);

            }

            //获取切面注解

            DistributedLock lock = method.getAnnotation(DistributedLock.class);

            TimeUnit timeUnit = lock.timeUnit();

            //redis key过期时间

            long expire = timeUnit.toMillis(lock.expire());

            //获取锁等待时间

            long waitTime = timeUnit.toMillis(lock.waitTime());

            //业务i18n异常编码

            String errorCode = lock.value();

            //key为空时锁方法,否则按SpringEl表达式取值

            String key = StringUtils.isEmpty(lock.key()) ? method.getName() : parser.parseExpression(lock.key()).getValue

                    (context, String.class);

            //作用域为空时取className

            String lockName = StringUtils.isEmpty(lock.lockName()) ? className : lock.lockName();

            //构造redisKey

            String redisKey = lockName + "#" + key;

            try {

                if (redisLockUtils.setLock(redisKey, expire, waitTime)) {

                    log.info("获取分布式锁成功,class={},method={},key={}", className, method, redisKey);

                    //执行方法

                    return point.proceed();

                }

            }

            catch (Exception e) {

                log.error("获取分布式锁错误,class={},method={},key={}", className, method, redisKey);

                ExceptionHandler.publish(errorCode, "", e);

            }

            finally {

                redisLockUtils.releaseLock(redisKey);

            }

            log.info("获取分布式锁失败,class={},method={},key={}", className, method, redisKey);

            //失败处理逻辑 此处抛出异常

            ExceptionHandler.publish(errorCode);

            return null;

        }

    }

    ```

    # RedisLockUtils

    与redis的交互域

    ```java

    import io.lettuce.core.SetArgs;

    import io.lettuce.core.api.async.RedisAsyncCommands;

    import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;

    import lombok.extern.slf4j.Slf4j;

    import org.apache.commons.lang3.StringUtils;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.data.redis.connection.RedisConnection;

    import org.springframework.data.redis.connection.ReturnType;

    import org.springframework.data.redis.core.RedisCallback;

    import org.springframework.data.redis.core.RedisTemplate;

    import org.springframework.data.redis.core.script.DefaultRedisScript;

    import org.springframework.data.redis.serializer.RedisSerializer;

    import org.springframework.stereotype.Component;

    import java.util.Optional;

    /**

    * redis 分布式锁Utils

    *

    * @author Zl

    * @date 2019/8/2

    * @since

    */

    @Slf4j

    @Component

    public class RedisLockUtils {

        private static final DefaultRedisScript<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 ");

            DefaultRedisScript<String> script = new DefaultRedisScript<>();

            script.setScriptText(sb.toString());

            UNLOCK_LUA = script;

        }

        private static final String LOCK_VALUE = "1";

        private static final String LOCK_HEARD = "lock:";

        @Autowired

        private RedisTemplate<String, String> redisTemplate;

        public boolean setLock(String key, String value, long expire) {

            if (StringUtils.isBlank(value)) {

                value = LOCK_VALUE;

            }

            return setNx(buildKey(key), value, expire);

        }

        public boolean setLock(String key, long expire) {

            return setNx(key, null, expire);

        }

        public boolean setLock(String key, long expire, long waitTime) {

            return setLock(key, null, expire, waitTime);

        }

        public boolean setLock(String key, String value, long expire, long waitTime) {

            if (waitTime == 0L) {

                return setLock(key, value, expire);

            }

            long start = System.currentTimeMillis();

            while (true) {

                //检测是否超时

                if (System.currentTimeMillis() - start > waitTime) {

                    return false;

                }

                if (setLock(key, value, expire)) {

                    return Boolean.TRUE;

                }

            }

        }

        public Optional<String> getLockValue(String key) {

            String o = redisTemplate.opsForValue().get(buildKey(key));

            return Optional.ofNullable(o);

        }

        public boolean releaseLock(String key) {

            return releaseLock(key, LOCK_VALUE);

        }

        public boolean releaseLock(String key, String value) {

            try {

                Object execute = redisTemplate.execute(

                        (RedisConnection connection) -> connection.eval(

                                UNLOCK_LUA.getScriptAsString().getBytes(),

                                ReturnType.INTEGER,

                                1,

                                buildKey(key).getBytes(),

                                value.getBytes())

                );

                return execute.equals(1L);

            } catch (Exception e) {

                log.error("release lock occured an exception", e);

            } finally {

            }

            return false;

        }

        /**

        * @param key        key值

        * @param value      value值

        * @param expiredTime 毫秒

        * @return

        */

        private boolean setNx(String key, String value, long expiredTime) {

            Boolean resultBoolean = null;

            try {

                resultBoolean = redisTemplate.execute((RedisCallback<Boolean>) connection -> {

                    Object nativeConnection = connection.getNativeConnection();

                    String redisResult = "";

                    @SuppressWarnings("unchecked")

                    RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();

                    //lettuce连接包下序列化键值,否知无法用默认的ByteArrayCodec解析

                    byte[] keyByte = stringRedisSerializer.serialize(key);

                    byte[] valueByte = stringRedisSerializer.serialize(value);

                    // lettuce连接包下 redis 单机模式setnx

                    if (nativeConnection instanceof RedisAsyncCommands) {

                        RedisAsyncCommands commands = (RedisAsyncCommands) nativeConnection;

                        //同步方法执行、setnx禁止异步

                        redisResult = commands

                                .getStatefulConnection()

                                .sync()

                                .set(keyByte, valueByte, SetArgs.Builder.nx().px(expiredTime));

                    }

                    // lettuce连接包下 redis 集群模式setnx

                    if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {

                        RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;

                        redisResult = clusterAsyncCommands

                                .getStatefulConnection()

                                .sync()

                                .set(keyByte, keyByte, SetArgs.Builder.nx().px(expiredTime));

                    }

                    //返回加锁结果

                    return "OK".equalsIgnoreCase(redisResult);

                });

            } catch (Exception e) {

                e.printStackTrace();

            }

            return resultBoolean != null && resultBoolean;

        }

        private String buildKey(String key) {

            return LOCK_HEARD + key;

        }

    }

    ```

    # 应用

    ```java

        /**

        * 锁className+#+test

        */

        @DistributedLock("*CENTER-100001")

        public void test(){}

        /**

        * 锁className+#+test1

        * key过期设置为100毫秒

        */

        @DistributedLock(value = "*CENTER-100001",expire = 100,timeUnit = TimeUnit.MILLISECONDS)

        public void test1(){}

        /**

        * 锁className+#+test2

        * 轮询10秒获取

        */

        @DistributedLock(value = "*CENTER-100001",waitTime = 10)

        public void test2(){}

        /**

        * 锁testNamespace+#+test3

        */

        @DistributedLock(value = "*CENTER-100001",lockName = "testNamespace")

        public void test3(){}

        /**

        * 锁className+#+id

        */

        @DistributedLock(value = "*CENTER-100001",key = "#id")

        public void test(String id){}

        /**

        * 锁className+#+id

        */

        @DistributedLock(value = "*CENTER-100001",key = "#o.id")

        public void test(Object o){}

    ```

    如上,value与errorCode 按具体项目修改实现,有固定放回格式的可以采用返回错误返回值,这里时抛出异常信息构造i18nMassage。也可以考虑el表达式取值后加上方法名。

    相关文章

      网友评论

          本文标题:Spring aop优雅实现redis分布式锁 aop应用red

          本文链接:https://www.haomeiwen.com/subject/atfjdctx.html