前言

上一次利用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. 其次,默认的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());
    }
}

那么现在就是主要对实现根据指定的方法入参作为锁的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)首先就是需要实现CacheLockKey 注解:

/**
 * 分布式锁key 作用在方法入参上,则会拼接为key值
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheLockKey {

}

(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());

(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.仅把方法名称和入参信息作为key值,会不会存在重复?
2.入参作为key但是入参为空,怎么处理?
3.如果key的锁定设定的时长失效了,方法还未执行完成,怎么办?

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐