XXXX项目是目前在实际工作中正在做的事情,该项目是一个大型系统的
内容管理内核,负责最核心的
Meta data的集中管理,
性能有较高的要求,设计初期就要求能够
支持cluster。 XXXX项目缓存方案总结 XXXX项目是目前在实际工作中正在做的事情,该项目是一个大型系统的
内容管理内核,负责最核心的
Meta data的集中管理,
性能有较高的要求,设计初期就要求能够
支持cluster。项目使用Hibernate 3.2,针对开发过程中对于各种缓存的不同看法,撰写了本文。重点在于澄清一些Hibernate的缓存细节,纠正一些
错误的缓存
用法。 一、Hibernate的二级缓存 如果开启了二级缓存,Hibernate在执行任何一次
查询的之后,都会把得到的结果集放到缓存中,缓存结构可以看作是一个hash table,key是
数据库记录的id,value是id对应的pojo对象。当
用户根据id
查询对象的时候(load、iterator
方法),会首先在缓存中查找,如果没有找到再发起
数据库查询。但是如果使用hql发起
查询(find,query
方法)则不会利用二级缓存,而是直接从
数据库获得数据,但是它会把得到的数据放到二级缓存备用。也就是说,基于hql的
查询,对二级缓存是只写不读的。 针对二级缓存的工作原理,采用iterator取代list来提高二级缓存命中率的想法是不可行的。Iterator的工作方式是根据检索条件从
数据库中选取所有目标数据的id,然后用这些id一个一个的到二级缓存里面做检索,如果找到就直接加载,找不到就向
数据库做
查询。因此假如iterator检索100条数据的话,最好情况是100%全部命中,最坏情况是0%命中,执行101条
sql把所有数据选出来。而list虽然不利用缓存,但是它只会发起1条
sql取得所有数据。在合理利用
分页查询的情况下,list整体效率高于iterator。 二级缓存的失效机制由Hibernate控制,当某条数据被
修改之后,Hibernate会根据它的id去做缓存失效操作。基于此机制,如果数据表不是被Hibernate独占(比如同时使用jdbc或者ado等),那么二级缓存无法得到有效控制。 由于Hibernate的缓存接口很灵活,cache provider可以方便的切换,因此
支持cluster环境不是大问题,通过使用swarmcache、jboss cache等
支持分布式的缓存方案,可以实现。但是问题在于: 1、 分布式缓存本身成本偏高(比如使用同步复制模式的jboss cache) 2、 分布式环境通常对事务控制有较高要求,而目前的开源缓存方案对事务缓存(transaction cache)
支持得不够好。当jta事务发生会滚,缓存的最后更新结果很难预料。这一点会带来很大的部署成本,甚至得不偿失。 结论:XXXX不应把Hibernate二级缓存作为优化的主要手段,一般情况下建议不要使用。 原因如下: 1、 XXXX的DAO类大部分是从1.0
升级过来,由于1.0采用的是Hibernate 2.1,所以在批量
删除数据的时候采用了native
sql的方式。虽然XXXX2.0已经完全
升级到Hibernate 3.2,
支持Hibernate原生的批量删改,但是由于Hibernate批量操作的
性能不如
sql,而且为了兼容1.0的dao类,所以很多地方保留了
sql操作。哪些数据表是单纯被Hibernate独占无法
统计,而且随着将来业务的发展可能会有很大变数。因此不宜采用二级缓存。 2、 针对系统业务来说,基于id检索的二级缓存命中率极为有限,hql被大量采用,二级缓存对
性能的提升很有限。 3、 Hibernate 3.0在做批量
修改、批量更新的时候,是不会同步更新二级缓存的,该问题在Hibernate 3.2中是否仍然存在尚不确定。 二、Hibernate的
查询缓存
查询缓存的实现机制与二级缓存基本一致,最大的差异在于放入缓存中的key是
查询的语句,value是
查询之后得到的结果集的id列表。表面看来这样的方案似乎能
解决hql利用缓存的问题,但是需要注意的是,构成key的是:hql
生成的
sql、
sql的参数、排序、
分页信息等。也就是说如果你的hql有小小的差异,比如第一条hql取1-50条数据,第二条hql取20-60条数据,那么Hibernate会认为这是两个完全不同的key,无法重复利用缓存。因此利用率也不高。 另外一个需要注意的问题是,
查询缓存和二级缓存是有关联关系的,他们不是完全独立的两套东西。假如一个
查询条件hql_1,第一次被执行的时候,它会从
数据库取得数据,然后把
查询条件作为key,把返回数据的所有id列表作为value(请注意仅仅是id)放到
查询缓存中,同时整个结果集放到class缓存(也就是二级缓存),key是id,value是pojo对象。当你再次执行hql_1,它会从缓存中得到id列表,然后根据这些列表一个一个的到class缓存里面去找pojo对象,如果找不到就向
数据库发起
查询。也就是说,如果二级缓存配置了超时时间(或者发呆时间),就有可能出现
查询缓存命中了,获得了id列表,但是class里面相应的pojo已经因为超时(或发呆)被失效,Hibernate就会根据id清单,一个一个的去向
数据库查询,有多少个id,就执行多少个
sql。该情况将导致
性能下降严重。
查询缓存的失效机制也由Hibernate控制,数据进入缓存时会有一个timestamp,它和数据表的timestamp对应。当Hibernate环境内发生save、update等操作时,会更新被操作数据表的timestamp。
用户在
获取缓存的时候,一旦命中就会检查它的timestamp是否和数据表的timestamp匹配,如果不,缓存会被失效。因此
查询缓存的失效控制是以数据表为粒度的,只要数据表中任何一条记录发生一点
修改,整个表相关的所有
查询缓存就都无效了。因此
查询缓存的命中率可能会很低。 结论:XXXX不应把Hibernate二级缓存作为优化的主要手段,一般情况下建议不要使用。 原因如下: 1、 XXXX的上层业务中检索条件都比较复杂,尤其是涉及多表操作的地方。很少出现重复执行一个排序、
分页、参数一致的
查询,因此命中率很难提高。 2、
查询缓存必须配合二级缓存一起使用,否则极易出现1+N的情况,否则
性能不升反降 3、 使用
查询缓存必须在执行
查询之前
显示调用Query.setCacheable(true)才能激活缓存,这势必会对已有的Hibernate封装类带来问题。 总结 详细分析Hibernate的二级缓存和
查询缓存之后,针对XXXX项目的具体情况做出结论,在底层使用通用缓存方案的想法基本上是不可取的。比较好的做法是在高层次中(业务逻辑层面),针对具体的业务逻辑状况手动使用数据缓存,不仅可以完全控制缓存的生命周期,还可以针对业务具体调整缓存方案提交命中率。Cluster中的缓存同步可以完全交给缓存本身的同步机制来完成。比如开源缓存swarmcache采用invalidate的机制,可以根据
用户指定的策略,在需要的时候向网络中的其他swarmcache节点发送失效消息,这一机制和XXXX1.0中已经采用的MappingCache的同步方案基本一致。建议采用。
原文链接:https://www.f2er.com/javaschema/286733.html