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.