用户登录
用户注册

分享至

ehcache hibernate 集群

  • 作者: 爱因斯坦诺维奇凯奇
  • 来源: 51数据库
  • 2020-10-29
EhCache里面有一个CacheManager类型,它负责管理cache。Cache里面存储着Element对象,Element必须是key-value对。Cache是实际物理实现的,在内存中或者磁盘。这些组件的逻辑表示就是下面即将要讨论的类。他们的方法提供了可编程的访问方式。

CacheManager
负责Cache的创建、访问、移除。

CacheManager创建
CacheManager支持两种创建模式:单例(Singleton mode)和实例(InstanceMode)。
在2.5之前的版本中,在同一个JVM中允许存在任意数量相同名字的CacheManager。每调用new CacheManager(...)一次,就会产生一个新的CacheManager实例,而不管已经存在多少个。调用CacheManager.create(...),则返回的是已经存在的那个配置对应的单例CacheManager,如果不存在,则创建一个。

2.5之后的版本,不允许在同一个JVM内存在多个具有相同名字的CacheManager。创建非单例实例的CacheManager()构造函数可能会打破这一规则,但是会抛出NPE异常。如果你的代码要在同一个JVM创建多个同名的实例,请使用静态方法CacheManager.create(),总是返回对应名的CacheManager(如果已经存在),否则创建一个



  1.技术背景:
????系统缓存是位于应用程序与物理数据源之间,用于临时存放复制数据的内存区域,目的是为减少应用程序对物理数据源访问的次数,从而提高应用程序的运行性能。缓存设想内存是有限的,缓存的时效性也是有限的,所以可以设定内存数量的大小可以执行失效算法,可以在内存满了的情况下,按照最少访问等算法将缓存直接移除或切换到硬盘上。
????Ehcache从Hibernate发展而来,逐渐涵盖了Cache界的全部功能,是目前发展势头最好的一个项目,具有快速、简单、低消耗、扩展性强、支持对象或序列化缓存,支持缓存或元素的失效,提供LRU、LFU和FIFO缓存策略,支持内存缓存和硬盘缓存和分布式缓存机制等特点。其中Cache的存储方式为内存或磁盘(ps:无须担心容量问题)
2.EhCahe的类层次介绍:
????主要分为三层,最上层是CacheManager,它是操作Ehcache的入口。可以通过CacheManager.getInstance()获得一个单子的CacheManager,或者通过CacheManager的构造函数创建一个新的CacheManager。每个CacheManger都管理多个Cache。每个Cache都以一种类Hash的方式,关联多个Element。Element就是我们用于存放缓存内容的地方。
3.环境搭建:
????很简单只需要将ehcache-2.1.0-distribution.tar.gz和ehcache-web-2.0.2-distribution.tar.gz挤压的jar包放入WEB-INF/lib下。
????再创建一个重要的配置文件ehcache.xml,可以从ehcache组件包中拷贝一个,也可以自己建立一个,需要放到classpath下,一般放于/WEB-INF/classed/ehcache.xml;具体的配置文件可以网上搜一下
4.实际运用
????一个网站的首页估计是被访问次数最多的,我们可以考虑给首页做一个页面缓存;
????缓存策略:应该是某个固定时间之内不变的,比如说2分钟更新一次,以应用结构page-filter-action-service-dao-db为例。
????位置:页面缓存做到尽量靠近客户的地方,就是在page和filter之间,这样的优点就是第一个用户请求后,页面被缓存,第二个用户在请求,走到filter这个请求就结束了,需要在走到action-service-dao-db,好处当然是服务器压力大大降低和客户端页面响应速度加快。
????首页页面缓存存活时间定为2分钟,也就是参数timeToLiveSeconds(缓存的存活时间)应该设置为120,同时timeToIdleSeconds(多长时间不访问缓存,就清楚该缓存)最好也设为2分钟或者小于2分钟。

接着我们来看一下SimplePageCachingFilter?的配置,

<filter>
<filter-name>indexCacheFilterfilter-name>
<filter-class>
net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter
<filter-class>
<filter>
<filter-mapping>
<filter-name>indexCacheFilterfilter-name>
<url-pattern>*index.actionurl-pattern>
<filter-mapping>

将上述代码加入到web.xml,那么当打开首页时,你会发现2分钟才会有一堆sql语句出现在控制台,也可以调整为5分钟,总之一切尽在掌控之中。

当然,如果你像缓存首页的部分内容时,你需要使用SimplePageFragmentCachingFilter这个filter,我看一下:
<filter>
<filter-name>indexCacheFilterfilter-name>
<filter-class>
net.sf.ehcache.constructs.web.filter.SimplePageFragmentCachingFilter
<filter-class>
filter>
<filter-mapping>
<filter-name>indexCacheFilterfilter-name>
<url-pattern>*/index_right.jsp<url-pattern>
<filter-mapping>

如此我们将jsp页面通过jsp:include到其他页面,这样就做到了页面局部缓存的效果,这一点貌似没有oscache的tag好用。

此外cachefilter中还有一个特性,就是gzip,也就是缓存中的元素是被压缩过的,如果客户端浏览器支持压缩的话,filter会直接返回压缩过的流,这样节省了带宽,把解压的工作交给了客户端浏览即可,当然如果客户端不支持gzip,那么filter会把缓存的元素拿出来解压后在返回给客户端浏览器(大多数爬虫是不支持gzip的,所以filter也会解压后在返回流)。
总之,Ehcache是一个非常轻量级的缓存实现,而且从1.2之后支持了集群,而且是hibernate默认的缓存provider,本文主要介绍Ehcahe对页面缓存的支持,但是它的功能远不止如此,要用好缓存,对J2ee中缓存的原理、适用范围、适用场景等等都需要比较深刻的理解,这样才能用好用对缓存。

为了大家通过实际例子加深了解与场景运用,在奉献一个实例:
*在Spring中运用EhCache
????适用任意一个现有开源Cache?Framework,要求可以Cache系统中service或者DAO层的get/find等方法返回结果,如果数据更新(适用了Create/update/delete),则刷新cache中相应的内容。
????根据需求,计划适用Spring?AOP+enCache来实现这个功能,采用ehCache原因之一就是Spring提供了enCache的支持,至于为何仅仅支持ehcache而不支持oscache和jbosscache就无从得知了。
????AOP少不了拦截器,先创建一个实现了MethodInterceptor接口的拦截器,用来拦截Service/DAO的方法调用,拦截到方法后,搜索该方法的结果在cache中是否存在,如果存在,返回cache中结果,如果不存在返回数据库查询结果,并将结果返回到缓存。
public?class?MethodCacheInterceptor?implements?MethodInterceptor,?InitializingBean
{
private?static?final?Log?logger?=?LogFactory.getLog(MethodCacheInterceptor.class);
private?Cache?cache;
public?void?setCache(Cache?cache)?{
this.cache?=?cache;
}
public?MethodCacheInterceptor()?{
super();
}
/**
*?拦截Service/DAO?的方法,并查找该结果是否存在,如果存在就返回cache?中的值,
*?否则,返回数据库查询结果,并将查询结果放入cache
*/
public?Object?invoke(MethodInvocation?invocation)?throws?Throwable?{
String?targetName?=?invocation.getThis().getClass().getName();
String?methodName?=?invocation.getMethod().getName();
Object[]?arguments?=?invocation.getArguments();
Object?result;
logger.debug("Find?object?from?cache?is?"?+?cache.getName());
String?cacheKey?=?getCacheKey(targetName,?methodName,?arguments);
Element?element?=?cache.get(cacheKey);
Page?13?of?26
if?(element?==?null)?{
logger.debug("Hold?up?method?,?Get?method?result?and?create?cache........!");
result?=?invocation.proceed();
element?=?new?Element(cacheKey,?(Serializable)?result);
cache.put(element);
}
return?element.getValue();
}
/**
*?获得cache?key?的方法,cache?key?是Cache?中一个Element?的唯一标识
*?cache?key?包括包名+类名+方法名,如com.co.cache.service.UserServiceImpl.getAllUser
*/
private?String?getCacheKey(String?targetName,?String?methodName,?Object[]?arguments)?{
StringBuffer?sb?=?new?StringBuffer();
sb.append(targetName).append(".").append(methodName);
if?((arguments?!=?null)?&&?(arguments.length?!=?0))?{
for?(int?i?=?0;?i?<?arguments.length;?i++)?{
sb.append(".").append(arguments[i]);
}
}
return?sb.toString();
}
/**
*?implement?InitializingBean,检查cache?是否为空
*/
public?void?afterPropertiesSet()?throws?Exception?{
Assert.notNull(cache,?"Need?a?cache.?Please?use?setCache(Cache)?create?it.");
}
}

上面的代码可以看到,在方法invoke中,完成了搜索cache/新建cache的功能
随后,再建立一个拦截器MethodCacheAfterAdvice,作用是在用户进行create/update/delete操作时来刷新、remove相关cache内容,这个拦截器需要实现AfterRetruningAdvice接口,将会在所拦截的方法执行后执行在afterReturning(object?arg0,Method?arg1,Object[]?arg2,object?arg3)方法中所预定的操作

public?class?MethodCacheAfterAdvice?implements?AfterReturningAdvice,?InitializingBean
{
private?static?final?Log?logger?=?LogFactory.getLog(MethodCacheAfterAdvice.class);
private?Cache?cache;
Page?15?of?26
public?void?setCache(Cache?cache)?{
this.cache?=?cache;
}
public?MethodCacheAfterAdvice()?{
super();
}
public?void?afterReturning(Object?arg0,?Method?arg1,?Object[]?arg2,?Object?arg3)?throws
Throwable?{
String?className?=?arg3.getClass().getName();
List?list?=?cache.getKeys();
for(int?i?=?0;i<list.size();i++){
String?cacheKey?=?String.valueOf(list.get(i));
if(cacheKey.startsWith(className)){
cache.remove(cacheKey);
logger.debug("remove?cache?"?+?cacheKey);
}
}
}
public?void?afterPropertiesSet()?throws?Exception?{
Assert.notNull(cache,?"Need?a?cache.?Please?use?setCache(Cache)?create?it.");
}
}

该方法获取目标class的全名,如:com.co.cache.test.TestServiceImpl,然后循环cache的key?list,刷新/remove?cache中所有和该class相关的element。

接着就是配置encache的属性,如最大缓存数量、cache刷新的时间等等。
<ehcache>
<diskStore?path="c:\\myapp\\cache"/>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache?name="DEFAULT_CACHE"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300000"
timeToLiveSeconds="600000"
overflowToDisk="true"
/>
</ehcache>

这里需要注意的是defaultCache定义了一个默认的cache,这个Cache不能删除,否则会抛出No?default?cache?is?configured异常。另外由于使用拦截器来刷新Cache内容,因此在定义cache生命周期时可以定义较大的数值,timeToIdleSeconds="30000000",timeToLiveSeconds="6000000",好像还不够大?

然后再将Cache和两个拦截器配置到Spring的配置文件cache.xml中即可,需要创建两个“切入点”,分别用于拦截不同方法名的方法。在配置application.xml并且导入cache.xml。这样一个简单的Spring+Encache框架就搭建完成。
软件
前端设计
程序设计
Java相关