关于优雅的解决Redis缓存雪崩问题

作为一个内存数据库,redis也总是免不了有各种各样的问题,这篇文章主要是针对其中一个问题进行讲解:缓存雪崩。并给出一些解决方案。

关于缓存雪崩问题

缓存雪崩是指大量的缓存在同一时间过期,导致大量请求请求数据库这样会导致我们的数据库压力过大,甚至崩溃

解决方案

主要是加共享锁+双重检测锁来实现的,当然现在基本都是基于组件开发所以我准备使用注解开发,去除一大票子重复代码!!!
pom与yml省略一万字

定义一个注解@Miss

主要作用是获取方法上的入参名称以及值

/**
 * CodeNOOB
 *
 * @date 2020/8/6 13:10
 */
//此注解只能用在方法上,也就是注解的使用范围
@Target(ElementType.METHOD)
// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Retention(RetentionPolicy.RUNTIME)
public @interface Miss {
    
    
    /**
     * 主要为了区分是那张表的,并且默认为空串
     */
    String value() default "";
}

其次在定义aop切面

这里不再解释毕竟度娘上一大堆对于aop的帖子

 //切入点表达式,某个路径下的,或者被注解修饰的方法,返回值必须void
    @Pointcut("@annotation(com.louis.springboot.demo.aop.Miss)")
    private void miss() {
    
    
    }

    @Around("miss()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
    
    
        log.info("=======进入切面=======");
        // 限流类似于令牌筒
        Semaphore semaphore = new Semaphore(50);
        String value = "";
        Map<String, Object> args = null;
        StringBuffer sb = new StringBuffer();
        //获取方法签名也就是被注解修饰的方法的名称以及参数
        Signature signature = pjp.getSignature();
        //获取方法对象
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        //查看当前方法是否被注解修饰
        if (targetMethod.isAnnotationPresent(Miss.class)) {
    
    
            //获取注解上的值
            value = ((Miss) targetMethod.getAnnotation(Miss.class)).value();
            args = getFieldsName(pjp);
        }
        //拼接redis需要的key
        for (String parameterName : args.keySet()) {
    
    
            sb.append(parameterName).append(":").append(args.get(parameterName)).append(":");
        }


        //如果缓存中有则直接返回
        Object obj = redisTemplate.opsForValue().get(value + sb.toString());
        if (obj != null) {
    
    
            System.out.println("缓存生效");
            return obj;
        }

        //获取一个令牌    实则是共享锁
        semaphore.acquire();
        try {
    
    
            //使用双重检测锁为了速度快点
            obj = redisTemplate.opsForValue().get(value + sb.toString());
            if (obj != null) {
    
    
                System.out.println("缓存生效");
                return obj;
            }
            //没有则放行并且将对饮的值写入缓存中
            Object val = pjp.proceed();
            //设定key,值,过期五秒,修饰词
            redisTemplate.opsForValue().set(value + sb.toString(), val, 5, TimeUnit.SECONDS);
            return val;
        } finally {
    
    
            semaphore.release();
        }

    }

使用到的工具方法,主要封装参数

private Map getFieldsName(ProceedingJoinPoint joinPoint) throws ClassNotFoundException, NoSuchMethodException {
    
    
        String classType = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        // 参数值
        Object[] args = joinPoint.getArgs();
        Class<?>[] classes = new Class[args.length];
        for (int k = 0; k < args.length; k++) {
    
    
            //判断到底拿到的参数是基础类型还是pojo类型
            if (!args[k].getClass().isPrimitive()) {
    
    
                // 获取的是封装类型而不是基础类型
                String result = args[k].getClass().getName();
                Class s = map.get(result);
                classes[k] = s == null ? args[k].getClass() : s;
            }
        }
        //用于获取参数名称
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        // 获取指定的方法,第二个参数可以不传,但是为了防止有重载的现象,还是需要传入参数的类型
        Method method = Class.forName(classType).getMethod(methodName, classes);
        // 参数名
        String[] parameterNames = pnd.getParameterNames(method);
        // 通过map封装参数和参数值
        HashMap<String, Object> paramMap = new HashMap();
        for (int i = 0; i < parameterNames.length; i++) {
    
    
            paramMap.put(parameterNames[i], args[i]);
        }
        return paramMap;
    }

至此我们就可以愉快的使用此组件了

	@Miss(value = "missTest")
    @GetMapping("missTest")
    public Object test(Integer id){
    
    
        System.out.println("无缓存进入方法");
        
        return new User("测试","123");
    }

关于此处我使用java的Semaphore ,主要省事,没必要为了一个小bug在使用中间件来限流,这样有可能解决了一个bug又来了更多的bug

猜你喜欢

转载自blog.csdn.net/cj181jie/article/details/107839101