redis做方法缓存
为什么要拿redis做缓存?
redis是一个完全基于内存、数据结构简单、采用单线程的工作方式(避免了不必要的上下文切换)、使用IO多路复用的一个key-value类型的数据库。查询速度要远比mysql这种关系型数据库要快得多。
系统绝大多数场景下都是读多写少,而mysql能够承受的并发量在每秒两三千(百度得到的数据)的时候就会面临宕机的风险了,并且查询速度极慢。
1、查询流程
在请求达到后端之后,对需要进行缓存的接口,会先去redis中找有无数据,没有的话会继续走正常的业务流程,然后将查询到的结果返回给客户端的同时也放在redis中一份,下次相同请求进来后,就可以直接从redis中拿到数据。
2、代码实现,举个栗子
2.1 自定义缓存注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RedisCache {
int expire() default 86400;//缓存多少秒,默认 24*60*60 1天
String key() default "";//缓存key为空时取方法名
}
2.2 对添加@RedisCache注解的方法进行缓存
@SuppressWarnings("unchecked")
@Around("@annotation(com.shao.cacheCompont.RedisCache)")
public Object RedisCache(final ProceedingJoinPoint jp) throws Throwable {
Method method = getMethod(jp);//获取注解上的方法
RedisCache cache = method.getAnnotation(RedisCache.class);
final String cacheKey = cache.key();
StringBuilder buf = new StringBuilder();
if (StringUtil.isNotBlank(cacheKey)) {
buf.append(cacheKey);
} else {
buf.append(jp.getSignature().getDeclaringTypeName()).append(".").append(jp.getSignature().getName());
}
Object[] args = jp.getArgs();
String key = "";
if (args == null || args.length == 0) {
key = buf.toString();
} else {
key = buf.append(".").append(JSON.toJSONString(args)).toString();
}
//!!!注意这里的HttpResult.class这里为我的controller的统一响应类型
Object o = JSON.parseObject((String) redisTemplate.opsForValue().get(key), HttpResult.class);
Object result = null;
if (null == o) {
result = jp.proceed(args);//缓存中查询不到则继续执行方法体
final int expire = cache.expire();
redisTemplate.opsForValue().set(key,JSON.toJSONString(result), expire, TimeUnit.SECONDS);
return result;
} else {
return o;//缓存中查询到结果则直接返回
}
}
private Method getMethod(ProceedingJoinPoint jp) {
MethodSignature signature = (MethodSignature) jp.getSignature();
Method runningMethod = signature.getMethod();
Class<?>[] parameterTypes = runningMethod.getParameterTypes();
Method method = null;
try {
method = jp.getTarget().getClass().getMethod(jp.getSignature().getName(), parameterTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return method;
}
2.3 使用
/**
* 功能描述: 查询所有影片
*/
@PostMapping("/selectAllFilm")
@RedisCache
//这里添加注解,对该接口进行缓存(注意这里的返回类型【HttpResult】要和缓存方法中的类型保持一致,否则会出类型转换问题)
public HttpResult selectAllFilm(@RequestBody SearchFilmVOSearch filmVO) {
PageInfo pageInfo = filmService.searchAllFilm(filmVO);
return HttpResult.ok(pageInfo);
}
2.4 测试
第一次请求该接口时,数据并没有加入缓存中,我们可以看到请求耗时为303ms
第二次请求,可以明显看到时间大大缩短到了9ms,响应速度提升了三十倍
3、数据同步问题
在进行缓存之后,相同的请求在缓存时间内是不会去读取数据库的,但是此时如果修改了数据库,则接口返回的数据就不能保证和数据库一致,因此在增、删、改时我们需要刷新缓存。
3.1 自定义刷新缓存注解,在增删改方法上添加该注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface UpdateCache {
/** 指定要刷新的key,可以进行模糊匹配 */
String key() default "";
}
3.2 在有@UpdateCache注解的方法上会刷新本类下的所有缓存
@Around("@annotation(com.shao.cacheCompont.UpdateCache)")
public Object UpdateCache(final ProceedingJoinPoint jp) throws Throwable {
Method method = getMethod(jp);
UpdateCache annotation = method.getAnnotation(UpdateCache.class);
final String cacheKey = annotation.key();
StringBuilder buf = new StringBuilder();
if (StringUtil.isNotBlank(cacheKey)) {
buf.append(cacheKey);
} else {
buf.append(jp.getSignature().getDeclaringTypeName()).append(".").append("*");
}
String key = buf.toString();
Set<String> keys = redisTemplate.keys(key);
if (keys != null && keys.size() != 0) {
keys.forEach(k->{
redisUtils.del(k);
});
}
return jp.proceed(jp.getArgs());
}