系统在高并发场景下,最有用的三个方法是缓存,限流,降级。
缓存就是其中之一,目前缓存基本上是用redis或者memcached。
redis和memcached的优势,劣势在哪里,这里就不细说了,各位看官自行研究。
对于一些不经常更新的数据,比如说热门文章等等类的数据, 做缓存能减少数据库的压力。
其实做缓存也简单,在查询地方判断有没有缓存,没有就读数据库,然后缓存结果,有就直接读缓存,返回结果。
这样做没问题,问题是需要开发人员去关心每个方法,都要写一遍判断,不够通用。
于是有人说,能不能像别的框架一样,加个注解,就自动对这个接口做缓存呢?
完全可以啊,今天介绍下用aop 注解 redis来实现统一的缓存功能。
首先我们可以定义一个用于缓存的注解,有缓存的时间,时间的单位属性。
代码语言:javascript复制/**
* 缓存数据
* @author yinjihuan
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
/**
* 过期时间
* @return
*/
public abstract long timeOut() default 0;
/**
* 过期单位
* @return
*/
public abstract TimeUnit timeUnit() default TimeUnit.HOURS;
}
然后呢就是写一个切面,切到所有接口,在执行业务方法前判断是否有缓存,然后进行对应的操作。下面贴出部分代码。
下面操作redis的地方都要捕获异常,防止redis连不上了,连不上就要执行业务的查询方法,不能返回空数据给用户。
代码语言:javascript复制@Around("execution(* com.cxytiandi.spring_redis.controller.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
//是否要缓存
boolean isCache = false;
//缓存时间
Long timeOut = 0L;
//缓存单位
TimeUnit timeUnit = null;
//访问的类对象
Class<?> cls = joinPoint.getTarget().getClass();
//正在执行的方法名
String methodName = joinPoint.getSignature().getName();
Map<String, Object> map = isCache(cls, methodName);
isCache = (boolean) map.get("isCache");
if(isCache){
timeOut = (long) map.get("timeOut");
timeUnit = (TimeUnit) map.get("timeUnit");
//获取执行方法的参数列表
Object[] args = joinPoint.getArgs();
//得到所有参数的值
StringBuffer params = new StringBuffer();
try {
getCallMethodParams(args, params);
} catch (Exception e) {
logger.error("", e);
}
String className = cls.getSimpleName();
//采用访问的类加上访问的方法名加上参数值作为唯一的key
String key = className "_" methodName "_" params;
logger.info("Cache key:" key);
try {
if(stringRedisTemplate.hasKey(key)){
try {
return queryCache(start, methodName, key);
} catch (Exception e) {
logger.error("获取redis的缓存数据异常", e);
}
} else {
Object result = joinPoint.proceed();
try {
setCache(timeOut, timeUnit, key, result);
} catch (Exception e) {
logger.error("缓存数据到redis异常", e);
}
return result;
}
} catch (Exception e) {
logger.error("", e);
}
}
return joinPoint.proceed();
}
不知道大家有没有看到这行代码,这边要产生一个唯一的key,用来标识某次查询请求
这里用到了类名 方法名然后再加上查询的参数值做为key, 比如说我查询用户张三的数据,缓存起来了,下次再查询张三的,才能把缓存的数据直接返回给用户,不然你查个赵四的也返回张三的那不就有问题了吗。
代码语言:javascript复制//采用访问的类加上访问的方法名加上参数值作为唯一的key
String key = className "_" methodName "_" params;
其实代码没多少,主要就是思路要理解清楚就ok。
用的地方就简单了,要缓存就加注解。
代码语言:javascript复制@RequestMapping("/users")
@Cache(timeOut=30, timeUnit=TimeUnit.SECONDS)
public ResponseData queryUsers() {
return ResponseData.ok(userService.queryUsers());
}
源码下载:http://cxytiandi.com/code/detail/29