• Redis+AOP实现一个可通用的分布式锁——改进


    前言

    上一次利用Redis分布式锁解决了一个并发问题:
    上篇:利用Redis分布式锁解决集群服务器定时任务重复执行问题
    代码可以直接从上篇文章中拿到,本篇文章仅对上次文章内容做进一步改进

    主要思想是:利用AOP面向切面的编程思想,将加锁部分抽象成一个切面,并利用自定义注解。

    但是有不足的地方:

    1. 如下是上篇文章中提到的CacheLock注解的参数,一般情况下,锁都会有等待时间waitTime默认为0不太合适,且默认都需要异常抛出会更好。
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CacheLock {
        String lockedKey() default "";   //redis锁key的前缀
        long expireTime() default 10;    //key在redis里存在的时间 单位:秒
        boolean release() default true; //释放在方法执行完成之后释放锁
        long waitTime() default 0; //获取锁的最大等待时间,单位:秒,默认不等待,0即为快速失败
        boolean throwException() default false;//是否抛出异常 默认不抛出
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 其次,默认的redis锁的前缀,是固定的字符串,不具备方法可以根据入参信息,来指定key。
      如下代码是注解的具体使用,如果需要按照入参name来作为key锁定,则按照之前的实现是不支持的。
    //测试服务接口
    public interface TestService {
        void testAspect(String name);
    }
    
    //测试服务类
    @Slf4j
    @Service
    public class TestServiceImpl implements TestService {
    	
        @Override
        @CacheLock(lockedKey = "CacheLockAspectTest", expireTime = 10)
        public void testAspect(String name) {
            log.info("任务:"+ name +"方法获取到锁了!时间:"+ new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("任务:"+ name +"方法执行完成了!"+"时间:"+ new Date());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    那么现在就是主要对实现根据指定的方法入参作为锁的key,进一步实现分布式锁。
    同样我希望它具备一些灵活性,那么也需要借助注解,只有方法入参前有该注解,那么就会把这个入参作为key值。

    方案改进

    我希望我的注解在使用的时候更加灵活和简单,让多的情况是这样(伪代码):
    我的redis分布式锁的key为:
    key=前缀+方法名称+入参

    @Service
    public class TestServiceImpl implements TestService {
    	
        @Override
        @CacheLock
        public void testAspect(@CacheLockKey String name) {
            log.info("任务:"+ name +"方法获取到锁了!时间:"+ new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("任务:"+ name +"方法执行完成了!"+"时间:"+ new Date());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    具体内容:

    (1)首先就是需要实现CacheLockKey 注解:

    /**
     * 分布式锁key 作用在方法入参上,则会拼接为key值
     */
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CacheLockKey {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (2)在切面方法中,获取全部入参信息,并找到带有该注解的参数,将其拼接为key值

    改的点:
    在这里插入图片描述
    代码:

    //获取指定的lockedKey参数,如果没有,则直接取方法名称作为key值的前缀
    StringBuilder lockKey = new StringBuilder(cacheMethod.getAnnotation(CacheLock.class).lockedKey());
            if(StringUtils.isBlank(lockKey.toString())){
                lockKey.append(cacheMethod.getName()).append("-");
            }
    
            //参数注解,1维是参数,2维是注解
            Object[] params = pjp.getArgs();
            Annotation[][] annotations = cacheMethod.getParameterAnnotations();
            for (int i = 0; i < annotations.length; i++) {
                Object param = params[i];
                Annotation[] paramAnn = annotations[i];
                //参数为空,直接下一个参数
                if(param == null || paramAnn.length == 0){
                    continue;
                }
                for (Annotation annotation : paramAnn) {
                    //这里判断当前注解是否为CacheLockKey.class
                    if(annotation.annotationType().equals(CacheLockKey.class)){
                        lockKey.append(param).append("-");
                        break;
                    }
                }
            }
    
            log.info("lockKey:{}", lockKey.toString());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    (3)稍微改进一下CacheLock注解的默认参数

    /**
     * 分布式锁注解信息
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CacheLock {
        String lockedKey() default "";   //redis锁key的前缀
        long expireTime() default 10;    //key在redis里存在的时间 单位:秒
        boolean release() default true; //释放在方法执行完成之后释放锁
        long waitTime() default 10; //获取锁的最大等待时间,单位:秒,默认为10s,如果为0则未获取到锁直接失败
        boolean throwException() default true;//是否抛出异常 默认抛出
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    思考与总结

    实际上,以上内容还有可以改进的点:
    1.仅把方法名称和入参信息作为key值,会不会存在重复?
    2.入参作为key但是入参为空,怎么处理?
    3.如果key的锁定设定的时长失效了,方法还未执行完成,怎么办?

  • 相关阅读:
    代码复现——gradle项目
    Maven高级-学习笔记01【Maven分模块构建】
    项目(智慧教室)第五部分,Zigbee采集控制功能实现
    java计算机毕业设计WEB儿童运动馆业务信息系统MyBatis+系统+LW文档+源码+调试部署
    健康舒适的超满意照明体验!SUKER书客SKY护眼台灯测评
    想要精通算法和SQL的成长之路 - 删除重复的电子邮箱(SQL)
    工程地质实习-工程地质 题集
    4 Paimon数据湖之Hive Catalog的使用
    民安智库(专业市场调查公司)开展老人体检消费者调查
    项目风险管理必备内容总结
  • 原文地址:https://blog.csdn.net/Dan1374219106/article/details/127874237