在上篇《spring + ehcache + redis两级缓存实战篇(1)》中,最后遗留了两个问题给大家思考:
第一个是访问10次本地EhCache 强制访问一次redis 使得激活数据或更新数据,这样会不会更好一些呢?
第二个是使用spring @Cacheable注解缓存方法时,将list参数的地址作为key存储,是否会有问题?
针对上面两个问题,我们主要来探讨如何解决。JUST DO IT! GO!
在上篇TODO处我们标记了问题可能要改进的地方。代码是最好的语言,就不做过多解释了。主要是Element是对value的一个封装,附加了一些状态信息。
EhRedisCache.java
private int activeCount = 10;//默认十次
@Override
public ValueWrapper get(Object key) {
Element value = ehCache.get(key);
LOG.info("Cache L1 (ehcache) :{}={}",key,value);
if (value!=null) {
//TODO 访问10次EhCache 强制访问一次redis 使得数据不失效
if(value.getHitCount() < activeCount){
return (value != null ? new SimpleValueWrapper(value.getObjectValue()) : null);
}else{
value.resetAccessStatistics();
}
}
final String keyStr = key.toString();
Object objectValue = redisTemplate.execute(new RedisCallback
getHitCount:缓存命中次数统计,resetAccessStatistics:统计次数重置为0。
前面讲过缓存的监控和Debug是困难的,难以排查是缓存的一个弱点。
思考:我们如今是两级缓存,是否也可以将Element做下扩展,使得用户可知缓存对象是来自L1或L2?L1和 L2中存活时间,缓存队列中KV的情况?被L1或L2的访问次数等等。最终做成一个可视化的缓存对象监控呢?
在上篇FIXME处我们标记了问题可能要改进的地方。通过spring注解的方式,在需要缓存的方法上注解@Cacheable即可,构建缓存时默认是根据接口输入参数作为key,返回值作为value。
由于EL表达式无法遍历List中T进行每个对象的处理,那么,我们如何对List中T的对象进行遍历处理?反射动态获取泛型?no,反射获取效率问题。 直接对T进行序列化?key无法灵活定义。 So,此处我新建一个ListKeyParam接口,通过实现该接口getKey来自定义key。
ListKeyParam.java
public interface ListKeyParam {
Object getKey();
}
User.java
public class User implements Serializable, ListKeyParam{
@Override
public Object getKey() {
return id+":"+userName;
}
//other codes…
}
BusinessCacheKeyGenerator.java
public Object generate(Object target, Method method, Object... params) {
if (Void.class == method.getReturnType()) {
LOG.error("无返回值的方法不可缓存 {}", method.getName());
return null;
}
Object result = null;
StringBuilder sb = new StringBuilder();
sb.append(method.getDeclaringClass().getSimpleName());
sb.append("/").append(method.getName()).append("/");
if (params.length > 0) {
for (Object param : params) {
if (param == null) {
sb.append(NULL_PARAM_KEY).append("/");
continue;
}
if (String.class.isAssignableFrom(param.getClass())
|| Number.class.isAssignableFrom(param.getClass())) {
sb.append(param).append("/");
continue;
}
if (Date.class.isAssignableFrom(param.getClass())) {
Date date = (Date) param;
sb.append(date.getTime()).append("/");
continue;
}
//list.toString是可行不妥的,不同机器,相同集合的地址不一样,降低缓存命中
// if (List.class.isAssignableFrom(param.getClass())) {
// sb.append(param.toString()).append("/");
// continue;
// }
//TODO List参数类型处理
if(List.class.isAssignableFrom(param.getClass())){
@SuppressWarnings("unchecked")
List
将key进行md5加密,可缩短节省内存空间。
spEL表达式
http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/expressions.html
http://download.csdn.net/detail/liaoyulin0609/9580598