首页 > 代码库 > 《java系统性能调优》--2.缓存

《java系统性能调优》--2.缓存

上一节,简单介绍了如何发现性能瓶颈。从这节开始,我会和大家分享我在项目中做的一些性能调优工作。这个系列没有什么顺序可言,觉得什么重要,就说说什么。


这节,我们聊缓存。


最开始接触缓存这个词,是学习硬件知识的时候,cpu有缓存,而且还分一级缓存,二级缓存,三级缓存。、


记得曾经的曾经老师提了一个很有意思的问题。

问:电脑为什么要有一级缓存,二级缓存……,而且还要有内存,还要有硬盘?

如果你面对这个问题,你怎么回答?

先来看我们的正文,最后再解释。


我们要聊当然不是硬件意义上的缓存,而是应用程序与应用程序之间,或者应用程序与物理资源之间的缓存。目的是,减少数据访问的频次,从而提高性能。

缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。

最近做的TGB项目,我们做了两方面处理:


第一、开启Hibernate二级缓存;


我们系统使用的是JPA,没关系,Hibernate就是JPA的一种实现。我们恰恰是采用了这种实现。

Hibernate提供了两级缓存,第一级是Session的缓存。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。第一级缓存是必需的,不允许而且事实上也无法比卸除。在第一级缓存中,持久化类的每个实例都具有唯一的OID。

第二级缓存是一个可插拔的的缓存插件,它是由SessionFactory负责管理。由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此第二级缓存是进程范围或者集群范围的缓存。这个缓存中存放的对象的松散数据。第二级对象有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。缓存适配器用于把具体的缓存实现软件与Hibernate集成。第二级缓存是可选的,可以在每个类或每个集合的粒度上配置第二级缓存。

中间提到几个术语,不解释,百度百科吧。

Hibernate的二级缓存策略的一般过程如下:

  1、 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。

  2、把获得的所有数据对象根据ID放入到第二级缓存中。

  3、当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。

  4、删除、更新、增加数据的时候,同时更新缓存。

  Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。

  

Hibernate的Query缓存策略的过程如下:


  1、Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。

  2、Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。

  3、Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。

用了这么长时间,感觉查询缓存作用不大,命中率太低。适合在查询条件固定的地方添加。

如何开启?太简单,不讲。


第二、在远程调用客户端加了一层缓存,在服务端加了一层缓存。


使用AOP,我们写了一些Interceptor,缓存一些经常使用的查询条件,即对应的数据。

思路很简单:

1、给EJB的实现类添加拦截器

2、拦截器判断查询方法的返回值是否已在缓存中,如果在则直接返回,否则调用EJB的方法,并将返回值放到缓存中,然后将结果返回

你可能要问,数据同步的问题,你是如何解决的?

EJB执行了增删改方法后,缓存中的数据就变成了脏数据,需要清空缓存

同样是用一个拦截器来解决,不同的是,这个拦截器是添加在EJB增删改方法上的

牵扯到跨系统调用,A系统如何告诉B系统,你需要的数据已经改了,你要清理自己本地的缓存了呢?

1、在系统A创建一个JMS Topic,起名CacheTopic

2、当系统A执行增删改方法后,向CacheTopic中发一条消息,告诉他,数据改了,要清缓存了。

3、在系统B在自己的服务器中部署Message Driven Bean,监听CacheTopic中的消息,收到消息后清空本地缓存


另外,对于特别常用的Webservice调用,我们会把其代理对象缓存起来,不用每次都去解析wsdl,动态生成。


回到文章开头的那个问题。

我的回答是:提高数据交换速度等等一堆。

但老师就一句话:性价比,这就是商业。

仔细琢磨一下,发现自己还是太嫩了,如果仅仅是为了提高速度,干脆都用一级缓存算了,为什么不用?性价比,钱!

同样的道理,既然缓存可以提高效率,我们把系统所有用到查询的地方都用上缓存吧。错,缓存如果乱用,事半功倍。有些数据经常改动,有些数据八百年也不查一回,有些数据跟钱有关系,不容有任何闪失,等等,这些数据,加缓存,纯粹是费力不讨好。很少修改的数据,允许偶尔并发或者不会被并发访问(修改)的数据,一些不是很重要的数据,等等,就可以尝试加缓存。

开篇之所以说这个小故事,是为了提醒大家不要进入思维里的想当然,要权衡利弊看得失。


性能上有说不完的话题,下节,我们接着聊。