About elegantly solving the Redis cache avalanche problem

As an in-memory database, redis is always unavoidable to have various problems. This article focuses on one of the problems: cache avalanche. And give some solutions.

About the cache avalanche problem

Cache avalanche means that a large number of caches expire at the same time, resulting in a large number of requests to request the database. This will cause our database to be overstressed or even crash

solution

It is mainly realized by adding shared lock + double detection lock. Of course, it is basically based on component development, so I am going to use annotation development to remove a lot of duplicate code! ! !
Pom and yml omit ten thousand words

Define an annotation @Miss

The main function is to get the input parameter name and value on the method

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

Secondly in defining the aop aspect

No more explanation here. After all, Du Niang has a lot of posts about 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();
        }

    }

The tools and methods used, the main packaging parameters

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;
    }

So far we can use this component happily

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

Regarding this, I use Java's Semaphore to save trouble. There is no need to use middleware to limit the current for a small bug. This may solve a bug and bring more bugs.

Guess you like

Origin blog.csdn.net/cj181jie/article/details/107839101