打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Java Cache 实践

1一本正经地讲什么是Cache

1、缓存的定义

不同情境下缓存的含义不太一样,我把它分为两类:

  • 因为取原始数据结果的代价比较大,把它们(使用频繁)存贮到一临时地方以代价更小的方式获得;

  • 凡是速度相差较大的两者交互时,用于协调两者处理数据速度差异的结构,均可称之为Cache。

2、Cache使用情境

  • 操作系统Cache:CPU和主内存之间有Cache,内存和硬盘之间也有Cache

  • HTTP Cache:304大家应该不会陌生 :)

  • 静态化页面

  • CDN缓存

  • 后台应用Cache(本文提及的就是此类Cache)

3、后台应用Cache大概分以下三种模式

  • 本地Cache:Guava Cache,EhCache(两者都是很成熟的JVM级别缓存)

  • 单机版的远程Cache:一套远程的Cache服务

  • 分布式集群Cache:比如Redis,Memcached

4、使用Cache的好处

  • 提升性能:将相应数据存储起来以避免数据的重复创建、处理和传输,可有效提高性能;

  • 增强稳定:提供数据的服务可能会意外停止;Cache可以在一定时间内仍提供对最终用户的支持。

2努力复现Cache实践

1、自定义Cache实现

cache-sequence:

简单的实现:

上面的例子在并发情况下可能会多次调service,这违背了我们避免重复取原始数据的初衷,所以还有下面的写法:

虽然上面我们解决了并发缓存的问题,但更多的关于Cache的大小、元素的回收等功能均未实现,下面提及的Google Guava Cache对这些功能都有很不错的实现。

2、Guava Cache 简介

Guava Cache作为Google工具库中的一部分,我们自然不能期待Guava对Cache有特别完善的实现,但Guava中的Cache可以在项目的前期为了实现简单而引入。

上面说到ConcurrentHashMap与Guava Cache,他们看起来类似,但Guava Cache还有其他的特性,可以看看Guava CacheBuilder的注释:

看到这些特性,你或许已经在想如何去实现了,咳-咳,但这不是我们今天的重点:),下面演示一下它的简单使用:

这两种方法都实现了上面cache-sequence的逻辑:从缓存中取key的值,如果Cache中存在,则返回值,反之,调用service来获取值,然后存到Cache中。但不同的在于Cacheloader的定义比较宽泛,是针对整个Cache定义的,即当你明确了键关联的值的加载方法时使用这种方式是很合适的,而Callable的方式较为灵活,允许你在get的时 候指定。

更详细的Cache定义

项目前期引入Guava Cache可以很方便高效的实现服务Cache功能,然而我建议你再往前走一步:引入缓存抽象。因为在项目后期我们可能需迁移到其他缓存方案,譬如Redis,假设你没有对Cache的使用做抽象,每个使用Cache的地都需要修改可能会让你怀疑人生 :)。在怀疑人生和买后悔药的十字路口间,我选择注解驱动的Spring Cache...

3、Spring Cache

看官网文档你会发现这东西和Spring transaction使用惊人的类似,你是对的,因为它也是基于动态生成的proxy代理机制实现的。Ok,下面我们来复习下Spring Cache吧(以下是最基本的Spring Cache使):

Service:

CacheConfig:(下面使用Java config,xml-based方式官网很详细)

Test:

PS:其他的注解我就不一一演示了,详见Spring官网(http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html)。

4、在分布式应用中使用Redis

当我们做分布式应时,JVM级别的Cache已经不太适合了,分布式Cache才是正道,下面我们来看看基于key-value内存存储的Redis。

从Redis官网(https://redis.io/)可以简单了解到它有以下特点:

  • 持丰富的数据结构和命令

  • 支持数据持久化(硬盘上)

  • 支持事务

  • 值最大可到512M

  • 支持集群部署(3.0开始自带)

当然本文也不是来介绍Redis如何好用的,言归如何使用Redis来完善我们的缓存功能吧。

采用注解驱动的Spring Cache来实现缓存功能优势就凸显了,我们不需要改Cache的调用地方,如下只需替换掉CacheManager、Cache对象即可:

5、踩坑中进击,使用Cache注意事项

Spring Cache基于proxy的Spring AOP带来嵌套调用的问题

嵌套调用如下:

我们使用Spring Aop进行代理,当执行proxy类a的方法时,会进行拦截,紧接着逻辑走到target类上a方法,再调用target类的b方法。它调用的是target类上的b方法, 而不是proxy类的b方法。而且,针对方法b的横切逻辑,只植入到了proxy类上的方法b中,所以嵌套调用的方法中没有我们的增强代码以致AOP失效。

简单介绍下两种解决嵌套调用问题的方案:(当然你可以在使用AOP编码时就绕开嵌套调用)

方案A:构建一个Spring BeanPostProcessor在目标类中注入被代理的对象,在嵌套调用处使用代理对象,具体代码如下:

BeanPostProcessor

BeanSelfAware

Service

方案B:通过ThreadLocal暴露Aop代对象,然后在嵌套调用的地方使用它,具体如下:

开启AspectJAutoProxy的exposeProxy:

在嵌套调用的地方使用AopContext获取当前的代理:

两种方案比较:

BeanSelfProcessor的方案需要在每个嵌套调的地方编码,而且有代码侵入;AopContext的方案需要额外去暴露当前的AOP proxy,正如Api docs上说的这样做会耗费一定的性能,而且同样会有代码侵入;

综上两种都有代码侵入问题,如果你考虑性能采用BeanSelfProcessor,考虑编码更简单采用AopContext。

其他的一些问题比如:

  • 数据库中不存在的数据的处理,可以在缓存中放一个无效的对象表明“数据为空”来提高缓存命中率;

  • 必须设置缓存的过期时间,否则缓存对象将永不过期;

  • 上线缓存后Cache异常缓存的对象不正确,可以使用在上线缓存前添加一些Cache管理功能来清除错误缓存;

    • 如线上程序没有处理异常情况把空对象缓存了,后续所有的请求都只能拿到异常的空对象;

    • 这种时候如果没有一些缓存管理方案,我们只好启紧急重启应用这样相对麻烦的方案了。

  • 正在加载中... (更多的问题在前方等待着我们ㄟ(▔,▔)ㄏ)

3总结

对于单实例应用Spring Cache + Guava Cache是个很实用的Cache 案,这套方案同样适用于项目的前期高效的实现Cache功能,后面切换缓存方案也相对容易;由于分布式项目的需要可以切换到Spring Cache + Redis的方案。

对于应用Cache我还在探索实践之中,缓存涉及的知识非常多,本文只是浅尝辄止,而且限于文章篇幅,很多东西本文只是一带而过,热切欢迎家的指导 :)。

4参考内容

http://docs.spring.io/spring/docs/current/spring-framework-

reference/html/cache.html

http://docs.spring.io/spring-data/redis/docs/current/reference/

html/#redis:support:cache-abstraction

http://ifeve.com/google-guava-cachesexplained/

http://jinnianshilongnian.iteye.com/blog/1487235

http://www.infoq.com/cn/news/2015/09/cache-problems

https://zh.wikipedia.org/wiki/%E7%BC%93%E5%AD%98

http://blog.jobbole.com/30940/

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
『互联网架构』软件架构
Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解
关于缓存的一些重要概念(Redis 前置菜)
Spring 思维导图,让 Spring 不再难懂(cache篇)
Spring Boot Cache 配合 Redis 使用时,向数据库添加新记录成功后,相应更新缓存的正确方法?
单品页统一服务系统架构未公开细节
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服