Ehcache




前言:设计一套缓存框架需要关注的要素

本文来源:RayChase  的《设计一套缓存框架需要关注的要素

最近关注了一些缓存框架的特性和实现,包括OSCache、JCS、Ehcache、Memcached等等,公司的两个缓存框架,以及一个标准JSR 107(JCache),发现一些诸多类同的方面。如果你不够熟悉以上,不妨先看看这两篇文章:《OSCache框架源码解析》和《Ehcache详细解读》,再看下面的内容也许会有更多想法。之后再思考,如果要自己去实现一套缓存框架,需要考虑哪些东西?

1、为哪些数据做缓存?

  1. 模型对象,这在业务逻辑层面最常见。
  2. 数据库查询结果集。
  3. 页面缓存、页面片段缓存。
  4. 运算结果集,尤其对于幂等性服务。
  5. 外部接口查询结果。

2、缓存框架的核心:

缓存生命周期管理,很多重要特性都是围绕它来展开的。

举例:

3、重要特性,这些特性不一定全部要具备,但是多数都要包含:

  • 一致性选择。缓存框架的设计必须首先考虑这一点。通常我们见到的缓存框架都是最终一致性的,允许获取数据有一定的延迟窗口。一致性关系到缓存的生命周期,是缓存的核心理念之一。
  • 分级存储。也和缓存生命周期密切相关。至少应包括内存和磁盘两级存储,有些缓存框架包含组网内部节点的分级等等,允许用户管理缓存数据在不同级别存储中的跃迁。分级存储还包括对存储数据的管理,以提高数据获取的效率;包括跃迁策略的定制,比如在某一级满足怎样的超时策略可以发生向下跃迁。
  • 规约配置,默认配置。可以支持XML、properties、DSL编程等等多种配置方式,但是最重要的是,要提供一个默认配置,允许用户在简单配置或者零配置的情况下使用缓存。
  • 集群、分布式,这意味着一定的伸缩性。包括内部通信协议选择,比如节点之间使用JMS、RMI或RESTful方式通信等等;包括节点热部署和节点发现能力,这通常都使用组播消息来实现;包括集群的方式,是Server-Client群、消息总线方式还是节点对等,等等。
  • 定制扩展性。尤其是淘汰算法、事件监听、持久化策略等等,都要允许用户方便地自定义。

4、相对较次要的特性:

  • 统计能力。包括各级缓存命中情况统计,生命周期长度统计。
  • 批量接口、异步接口。包括缓存分组能力。
  • 缓存数据存储校验。
  • Web支持。特指Web容器中,对于页面存储的额外支持。
  • 免锁数据处理。
  • 缓存状态监控。
  • 无侵入式拦截,注解编程支持。
  • 运行时参数调整。

……

5、核心模型应该包括哪些?

  • CacheManager:模型管理对象,可以是多实例的,也可以是单实例的。
  • Cache:通过CacheManager创建出来的缓存容器,内部包含了真正的缓存承载体,至少开放add/remove/flush等接口。
  • CacheMap:真正的缓存承载体,大致上都是一个Map,各种类型的Map。
  • CacheEntity:缓存条目,相当于CacheMap里面的每一条Entry。
  • CacheEvent:缓存事件,比如CacheEntity的创建、更新、删除等等。
  • CacheEventListener:缓存事件相应的监听器。
  • CacheEvictionAlgorithm:缓存淘汰算法,常见的有LRU、LFU、FIFO等等。

--------------------------------------------------------------------------------------------------------------------------------

 

任何一个缓存框架,它要解决什么样的问题?

数据的访问、存取、计算太慢、太不稳定、太消耗资源,同时,这样的操作存在重复性。因此希望有这样一种中间媒介,放置在其间,只保存自己关心的数据,而不关心具体数据逻辑内容,对于重复性的操作给出响应。对于数据和服务的使用者,它是透明的。

从请求和数据流向的角度看,一个完整的缓存框架应该包括这样几个部分:

  • 操作捕获
  • 缓存数据存储
  • 缓存数据读取
  • 缓存数据流动

因此缓存框架的功能都是围绕数据展开的,它的核心就是缓存数据的整个生命周期。

但是其中每一项都可以拆分和解耦成许多部分,以缓存数据存储为例,可以拆分成:

  • key生成
  • value封装、元数据封装
  • 索引生成
  • 文件结构生成
  • 序列化、反序列化
  • 淘汰算法
  • 过期检查
  • 存储数据预处理
  • 持久化媒介

……





一:Ehcache详细解读

本文来源:RayChase  的《Ehcache详细解读

一、简介

本文来源:letmedown的《缓存之EHCache(一)

非常简单,而且易用。

    ehcache 是一个非常轻量级的缓存实现,而且从1.2 之后就支持了集群,而且是hibernate 默认的缓存provider。ehcache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

ehcache可以直接使用。也可以和Hibernate对象/关系框架结合使用。还可以做Servlet缓存。

Cache 存储方式 :内存或磁盘。

官方网站:http://ehcache.sourceforge.net/

主要特征:

1. 快速.

2. 简单.

3. 多种缓存策略

4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题

5. 缓存数据会在虚拟机重启的过程中写入磁盘

6. 可以通过RMI、可插入API等方式进行分布式缓存

7. 具有缓存和缓存管理器的侦听接口

8. 支持多缓存管理器实例,以及一个实例的多个缓存区域

9. 提供Hibernate的缓存实现

10. 等等


Ehcache 是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,最初知道它,是从Hibernate的缓存开始的。网上中文的EhCache材料以简单介绍和配置方法居多,如果你有这方面的问题,请自行google;对于API,官网上介绍已经非常清楚,请参见官网;但是很少见到特性说明和对实现原理的分析,因此在这篇文章里面,我会详细介绍和分析EhCache的特性,加上一些自己的理解和思考,希望对缓存感兴趣的朋友有所收获。

一、特性一览,来自官网,简单翻译一下:

1、快速轻量


过去几年,诸多测试表明Ehcache是最快的Java缓存之一。
Ehcache的线程机制是为大型高并发系统设计的。
大量性能测试用例保证Ehcache在不同版本间性能表现得一致性。
很多用户都不知道他们正在用Ehcache,因为不需要什么特别的配置。
API易于使用,这就很容易部署上线和运行。
很小的jar包,Ehcache 2.2.3才668kb。
最小的依赖:唯一的依赖就是SLF4J了。

2、伸缩性


缓存在内存和磁盘存储可以伸缩到数G,Ehcache为大数据存储做过优化。
大内存的情况下,所有进程可以支持数百G的吞吐。
为高并发和大型多CPU服务器做优化。
线程安全和性能总是一对矛盾,Ehcache的线程机制设计采用了Doug Lea的想法来获得较高的性能。
单台虚拟机上支持多缓存管理器。
通过Terracotta服务器矩阵,可以伸缩到数百个节点。

3、灵活性


Ehcache 1.2具备对象API接口和可序列化API接口。
不能序列化的对象可以使用除磁盘存储外Ehcache的所有功能。
除了元素的返回方法以外,API都是统一的。只有这两个方法不一致:getObjectValue和getKeyValue。这就使得缓存对象、序列化对象来获取新的特性这个过程很简单。
支持基于Cache和基于Element的过期策略,每个Cache的存活时间都是可以设置和控制的。
提供了LRU、LFU和FIFO缓存淘汰算法,Ehcache 1.2引入了最少使用和先进先出缓存淘汰算法,构成了完整的缓存淘汰算法。
提供内存和磁盘存储,Ehcache和大多数缓存解决方案一样,提供高性能的内存和磁盘存储。
动态、运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目都是可以在运行时修改的。

4、标准支持


Ehcache提供了对JSR107 JCACHE API最完整的实现。因为JCACHE在发布以前,Ehcache的实现(如net.sf.jsr107cache)已经发布了。
实现JCACHE API有利于到未来其他缓存解决方案的可移植性。
Ehcache的维护者Greg Luck,正是JSR107的专家委员会委员。

5、可扩展性


监听器可以插件化。Ehcache 1.2提供了CacheManagerEventListener和CacheEventListener接口,实现可以插件化,并且可以在ehcache.xml里配置。
节点发现,冗余器和监听器都可以插件化。
分布式缓存,从Ehcache 1.2开始引入,包含了一些权衡的选项。Ehcache的团队相信没有什么是万能的配置。
实现者可以使用内建的机制或者完全自己实现,因为有完整的插件开发指南。
缓存的可扩展性可以插件化。创建你自己的缓存扩展,它可以持有一个缓存的引用,并且绑定在缓存的生命周期内。
缓存加载器可以插件化。创建你自己的缓存加载器,可以使用一些异步方法来加载数据到缓存里面。
缓存异常处理器可以插件化。创建一个异常处理器,在异常发生的时候,可以执行某些特定操作。

6、应用持久化


在VM重启后,持久化到磁盘的存储可以复原数据。
Ehcache是第一个引入缓存数据持久化存储的开源Java缓存框架。缓存的数据可以在机器重启后从磁盘上重新获得。
根据需要将缓存刷到磁盘。将缓存条目刷到磁盘的操作可以通过cache.flush()方法来执行,这大大方便了Ehcache的使用。

7、监听器


缓存管理器监听器。允许注册实现了CacheManagerEventListener接口的监听器:

  1 notifyCacheAdded()
  2 notifyCacheRemoved()
View Code

缓存事件监听器。允许注册实现了CacheEventListener接口的监听器,它提供了许多对缓存事件发生后的处理机制:
  1 notifyElementRemoved/Put/Updated/Expired
View Code

8、开启JMX


Ehcache的JMX功能是默认开启的,你可以监控和管理如下的MBean:
CacheManager、Cache、CacheConfiguration、CacheStatistics

9、分布式缓存


从Ehcache 1.2开始,支持高性能的分布式缓存,兼具灵活性和扩展性。
分布式缓存的选项包括:
通过Terracotta的缓存集群:设定和使用Terracotta模式的Ehcache缓存。缓存发现是自动完成的,并且有很多选项可以用来调试缓存行为和性能。
使用RMI、JGroups或者JMS来冗余缓存数据:节点可以通过多播或发现者手动配置。状态更新可以通过RMI连接来异步或者同步完成。
Custom:一个综合的插件机制,支持发现和复制的能力。
可用的缓存复制选项。支持的通过RMI、JGroups或JMS进行的异步或同步的缓存复制。
可靠的分发:使用TCP的内建分发机制。
节点发现:节点可以手动配置或者使用多播自动发现,并且可以自动添加和移除节点。对于多播阻塞的情况下,手动配置可以很好地控制。
分布式缓存可以任意时间加入或者离开集群。缓存可以配置在初始化的时候执行引导程序员。
BootstrapCacheLoaderFactory抽象工厂,实现了BootstrapCacheLoader接口(RMI实现)。
缓存服务端。Ehcache提供了一个Cache Server,一个war包,为绝大多数web容器或者是独立的服务器提供支持。
缓存服务端有两组API:面向资源的RESTful,还有就是SOAP。客户端没有实现语言的限制。
RESTful缓存服务器:Ehcached的实现严格遵循RESTful面向资源的架构风格。
SOAP缓存服务端:Ehcache RESTFul Web Services API暴露了单例的CacheManager,他能在ehcache.xml或者IoC容器里面配置。
标准服务端包含了内嵌的Glassfish web容器。它被打成了war包,可以任意部署到支持Servlet 2.5的web容器内。Glassfish V2/3、Tomcat 6和Jetty 6都已经经过了测试。

10、搜索


标准分布式搜索使用了流式查询接口的方式,请参阅文档。

11、Java EE和应用缓存


为普通缓存场景和模式提供高质量的实现。
阻塞缓存:它的机制避免了复制进程并发操作的问题。
SelfPopulatingCache在缓存一些开销昂贵操作时显得特别有用,它是一种针对读优化的缓存。它不需要调用者知道缓存元素怎样被返回,也支持在不阻塞读的情况下刷新缓存条目。
CachingFilter:一个抽象、可扩展的cache filter。
SimplePageCachingFilter:用于缓存基于request URI和Query String的页面。它可以根据HTTP request header的值来选择采用或者不采用gzip压缩方式将页面发到浏览器端。你可以用它来缓存整个Servlet页面,无论你采用的是JSP、velocity,或者其他的页面渲染技术。
SimplePageFragmentCachingFilter:缓存页面片段,基于request URI和Query String。在JSP中使用jsp:include标签包含。
已经使用Orion和Tomcat测试过,兼容Servlet 2.3、Servlet 2.4规范。
Cacheable命令:这是一种老的命令行模式,支持异步行为、容错。
兼容Hibernate,兼容Google App Engine。
基于JTA的事务支持,支持事务资源管理,二阶段提交和回滚,以及本地事务。

12、开源协议


Apache 2.0 license

二、Ehcache的加载模块列表

他们都是独立的库,每个都为Ehcache添加新的功能,可以在此下载

  • ehcache-core:API,标准缓存引擎,RMI复制和Hibernate支持
  • ehcache:分布式Ehcache,包括Ehcache的核心和Terracotta的库
  • ehcache-monitor:企业级监控和管理
  • ehcache-web:为Java Servlet Container提供缓存、gzip压缩支持的filters
  • ehcache-jcache:JSR107 JCACHE的实现
  • ehcache-jgroupsreplication:使用JGroup的复制
  • ehcache-jmsreplication:使用JMS的复制
  • ehcache-openjpa:OpenJPA插件
  • ehcache-server:war内部署或者单独部署的RESTful cache server
  • ehcache-unlockedreadsview:允许Terracotta cache的无锁读
  • ehcache-debugger:记录RMI分布式调用事件
  • Ehcache for Ruby:Jruby and Rails支持

Ehcache的结构设计概览:

三、核心定义

cache manager:缓存管理器,以前是只允许单例的,不过现在也可以多实例了

cache:缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口

element:单条缓存数据的组成单位

system of record(SOR):可以取到真实数据的组件,可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等等,缓存就是从SOR中读取或者写入到SOR中去的。

代码示例:

Java代码 收藏代码

  1 CacheManager manager = CacheManager.newInstance("src/config/ehcache.xml");
  2 manager.addCache("testCache");
  3 Cache test = singletonManager.getCache("testCache");
  4 test.put(new Element("key1", "value1"));
  5 manager.shutdown();
  6 
View Code

当然,也支持这种类似DSL的配置方式,配置都是可以在运行时动态修改的:

Java代码 收藏代码

  1  Cache testCache = new Cache(
  2   new CacheConfiguration("testCache", maxElements)
  3      .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
  4      .overflowToDisk(true)
  5      .eternal(false)
  6      .timeToLiveSeconds(60)
  7      .timeToIdleSeconds(30)
  8      .diskPersistent(false)
  9      .diskExpiryThreadIntervalSeconds(0));
 10 
View Code

事务的例子:

Java代码 收藏代码

  1  Ehcache cache = cacheManager.getEhcache("xaCache");
  2  transactionManager.begin();
  3  try {
  4      Element e = cache.get(key);
  5      Object result = complexService.doStuff(element.getValue());
  6      cache.put(new Element(key, result));
  7      complexService.doMoreStuff(result);
  8      transactionManager.commit();
  9  } catch (Exception e) {
 10      transactionManager.rollback();
 11 }
 12 
View Code

四、一致性模型

说到一致性,数据库的一致性是怎样的?不妨先来回顾一下数据库的几个隔离级别:

未提交读(Read Uncommitted):在读数据时不会检查或使用任何锁。因此,在这种隔离级别中可能读取到没有提交的数据。会出现脏读、不可重复读、幻象读。
已提交读(Read Committed):只读取提交的数据并等待其他事务释放排他锁。读数据的共享锁在读操作完成后立即释放。已提交读是数据库的默认隔离级别。会出现不可重复读、幻象读。
可重复读(Repeatable Read):像已提交读级别那样读数据,但会保持共享锁直到事务结束。会出现幻象读。
可序列化(Serializable):工作方式类似于可重复读。但它不仅会锁定受影响的数据,还会锁定这个范围,这就阻止了新数据插入查询所涉及的范围。

基于以上,再来对比思考下面的一致性模型:

1、强一致性模型:系统中的某个数据被成功更新(事务成功返回)后,后续任何对该数据的读取操作都得到更新后的值。这是传统关系数据库提供的一致性模型,也是关系数据库深受人们喜爱的原因之一。强一致性模型下的性能消耗通常是最大的。

2、弱一致性模型:系统中的某个数据被更新后,后续对该数据的读取操作得到的不一定是更新后的值,这种情况下通常有个“不一致性时间窗口”存在:即数据更新完成后在经过这个时间窗口,后续读取操作就能够得到更新后的值。

3、最终一致性模型:属于弱一致性的一种,即某个数据被更新后,如果该数据后续没有被再次更新,那么最终所有的读取操作都会返回更新后的值。

最终一致性模型包含如下几个必要属性,都比较好理解:

  • 读写一致:某线程A,更新某条数据以后,后续的访问全部都能取得更新后的数据。
  • 会话内一致:它本质上和上面那一条是一致的,某用户更改了数据,只要会话还存在,后续他取得的所有数据都必须是更改后的数据。
  • 单调读一致:如果一个进程可以看到当前的值,那么后续的访问不能返回之前的值。
  • 单调写一致:对同一进程内的写行为必须是保序的,否则,写完毕的结果就是不可预期的了。

4、Bulk Load:这种模型是基于批量加载数据到缓存里面的场景而优化的,没有引入锁和常规的淘汰算法这些降低性能的东西,它和最终一致性模型很像,但是有批量、高速写和弱一致性保证的机制。

这样几个API也会影响到一致性的结果:

1、显式锁(Explicit Locking ):如果我们本身就配置为强一致性,那么自然所有的缓存操作都具备事务性质。而如果我们配置成最终一致性时,再在外部使用显式锁API,也可以达到事务的效果。当然这样的锁可以控制得更细粒度,但是依然可能存在竞争和线程阻塞。

2、无锁可读取视图(UnlockedReadsView):一个允许脏读的decorator,它只能用在强一致性的配置下,它通过申请一个特殊的写锁来比完全的强一致性配置提升性能。

举例如下,xml配置为强一致性模型:

Xml代码 收藏代码

  1  <cache name="myCache"
  2   maxElementsInMemory="500"
  3   eternal="false"
  4   overflowToDisk="false"
  5   <terracotta clustered="true" consistency="strong" />
  6  </cache>
  7 
View Code

但是使用UnlockedReadsView:

Java代码 收藏代码

  1  Cache cache = cacheManager.getEhcache("myCache");
  2  UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache");
  3 
View Code

3、原子方法(Atomic methods):方法执行是原子化的,即CAS操作(Compare and Swap)。CAS最终也实现了强一致性的效果,但不同的是,它是采用乐观锁而不是悲观锁来实现的。在乐观锁机制下,更新的操作可能不成功,因为在这过程中可能会有其他线程对同一条数据进行变更,那么在失败后需要重新执行更新操作。现代的CPU都支持CAS原语了。

Java代码 收藏代码

  1  cache.putIfAbsent(Element element);
  2  cache.replace(Element oldOne, Element newOne);
  3  cache.remove(Element);
  4 
View Code

五、缓存拓扑类型

1、独立缓存(Standalone Ehcache):这样的缓存应用节点都是独立的,互相不通信。

2、分布式缓存(Distributed Ehcache):数据存储在Terracotta的服务器阵列(Terracotta Server Array,TSA)中,但是最近使用的数据,可以存储在各个应用节点中。

逻辑视角:


L1缓存就在各个应用节点上,而L2缓存则放在Cache Server阵列中。

组网视角:

模型存储视角:


L1级缓存是没有持久化存储的。另外,从缓存数据量上看,server端远大于应用节点。

3、复制式缓存(Replicated Ehcache):缓存数据时同时存放在多个应用节点的,数据复制和失效的事件以同步或者异步的形式在各个集群节点间传播。上述事件到来时,会阻塞写线程的操作。在这种模式下,只有弱一致性模型。

它有如下几种事件传播机制:RMI、JGroups、JMS和Cache Server。

RMI模式下,所有节点全部对等:

JGroup模式:可以配置单播或者多播,协议栈和配置都非常灵活。

Xml代码 收藏代码

  1  <cacheManagerPeerProviderFactory
  2  class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"
  3  properties="connect=UDP(mcast_addr=231.12.21.132;mcast_port=45566;):PING:  
  4  MERGE2:FD_SOCK:VERIFY_SUSPECT:pbcast.NAKACK:UNICAST:pbcast.STABLE:FRAG:pbcast.GMS"  
  5  propertySeparator="::"
  6  />
  7 
View Code

JMS模式:这种模式的核心就是一个消息队列,每个应用节点都订阅预先定义好的主题,同时,节点有元素更新时,也会发布更新元素到主题中去。JMS规范实现者上,Open MQ和Active MQ这两个,Ehcache的兼容性都已经测试过。

Cache Server模式:这种模式下存在主从节点,通信可以通过RESTful的API或者SOAP。

无论上面哪个模式,更新事件又可以分为updateViaCopy或updateViaInvalidate,后者只是发送一个过期消息,效率要高得多。

复制式缓存容易出现数据不一致的问题,如果这成为一个问题,可以考虑使用数据同步分发的机制。

即便不采用分布式缓存和复制式缓存,依然会出现一些不好的行为,比如:

缓存漂移(Cache Drift):每个应用节点只管理自己的缓存,在更新某个节点的时候,不会影响到其他的节点,这样数据之间可能就不同步了。这在web会话数据缓存中情况尤甚。

数据库瓶颈(Database Bottlenecks ):对于单实例的应用来说,缓存可以保护数据库的读风暴;但是,在集群的环境下,每一个应用节点都要定期保持数据最新,节点越多,要维持这样的情况对数据库的开销也越大。

六、存储方式

1、堆内存储:速度快,但是容量有限。

2、堆外(OffHeapStore)存储

:被称为BigMemory,只在企业版本的Ehcache中提供,原理是利用nio的DirectByteBuffers实现,比存储到磁盘上快,而且完全不受GC的影响,可以保证响应时间的稳定性;但是direct buffer的在分配上的开销要比heap buffer大,而且要求必须以字节数组方式存储,因此对象必须在存储过程中进行序列化,读取则进行反序列化操作,它的速度大约比堆内存储慢一个数量级。

(注:direct buffer不受GC影响,但是direct buffer归属的的JAVA对象是在堆上且能够被GC回收的,一旦它被回收,JVM将释放direct buffer的堆外空间。)

3、磁盘存储。

七、缓存使用模式

cache-aside:直接操作。先询问cache某条缓存数据是否存在,存在的话直接从cache中返回数据,绕过SOR;如果不存在,从SOR中取得数据,然后再放入cache中。

Java代码 收藏代码

  1  public V readSomeData(K key)
  2  {
  3     Element element;
  4   if ((element = cache.get(key)) != null) {
  5   return element.getValue();
  6     }
  7   if (value = readDataFromDataStore(key)) != null) {
  8         cache.put(new Element(key, value));
  9     }
 10   return value;
 11  }
 12 
View Code

cache-as-sor:结合了read-through、write-through或write-behind操作,通过给SOR增加了一层代理,对外部应用访问来说,它不用区别数据是从缓存中还是从SOR中取得的。

read-through。

write-through。

write-behind(write-back):既将写的过程变为异步的,又进一步延迟写入数据的过程。

Copy Cache的两个模式:CopyOnRead和CopyOnWrite。

CopyOnRead指的是在读缓存数据的请求到达时,如果发现数据已经过期,需要重新从源处获取,发起的copy element的操作(pull);

CopyOnWrite则是发生在真实数据写入缓存时,发起的更新其他节点的copy element的操作(push)。

前者适合在不允许多个线程访问同一个element的时候使用,后者则允许你自由控制缓存更新通知的时机。

更多push和pull的变化和不同,也可参见这里

八、多种配置方式

包括配置文件、声明式配置、编程式配置,甚至通过指定构造器的参数来完成配置,配置设计的原则包括:

所有配置要放到一起

缓存的配置可以很容易在开发阶段、运行时修改

错误的配置能够在程序启动时发现,在运行时修改出错则需要抛出运行时异常

提供默认配置,几乎所有的配置都是可选的,都有默认值

九、自动资源控制(Automatic Resource Control,ARC):

它是提供了一种智能途径来控制缓存,调优性能。特性包括:

内存内缓存对象大小的控制,避免OOM出现

池化(cache manager级别)的缓存大小获取,避免单独计算缓存大小的消耗

灵活的独立基于层的大小计算能力,下图中可以看到,不同层的大小都是可以单独控制的

可以统计字节大小、缓存条目数和百分比

优化高命中数据的获取,以提升性能,参见下面对缓存数据在不同层之间的流转的介绍

缓存数据的流转包括了这样几种行为:

Flush:缓存条目向低层次移动。

Fault:从低层拷贝一个对象到高层。在获取缓存的过程中,某一层发现自己的该缓存条目已经失效,就触发了Fault行为。

Eviction:把缓存条目除去。

Expiration:失效状态。

Pinning:强制缓存条目保持在某一层。

下面的图反映了数据在各个层之间的流转,也反映了数据的生命周期:

十、监控功能

监控的拓扑:


每个应用节点部署一个监控探针,通过TCP协议与监控服务器联系,最终将数据提供给富文本客户端或者监控操作服务器。

十一、广域网复制

缓存数据复制方面,Ehcache允许两个地理位置各异的节点在广域网下维持数据一致性,同时它提供了这样几种方案(注:下面的示例都只绘制了两个节点的情形,实际可以推广到N个节点):

第一种方案:Terracotta Active/Mirror Replication。


这种方案下,服务端包含一个活跃节点,一个备份节点;各个应用节点全部靠该活跃节点提供读写服务。这种方式最简单,管理容易;但是,需要寄希望于理想的网络状况,服务器之间和客户端到服务器之间都存在走WAN的情况,这样的方案其实最不稳定。

第二种方案:Transactional Cache Manager Replication。


这种方案下,数据读取不需要经过WAN,写入数据时写入两份,分别由两个cache manager处理,一份在本地Server,一份到其他Server去。这种方案下读的吞吐量较高而且延迟较低;但是需要引入一个XA事务管理器,两个cache manager写两份数据导致写开销较大,而且过WAN的写延迟依然可能导致系统响应的瓶颈。

第三种方案:Messaging based (AMQ) replication。


这种方案下,引入了批量处理和队列,用以减缓WAN的瓶颈出现,同时,把处理读请求和复制逻辑从Server Array物理上就剥离开,避免了WAN情况恶化对节点读取业务的影响。这种方案要较高的吞吐量和较低的延迟,读/复制的分离保证了可以提供完备的消息分发保证、冲突处理等特性;但是它较为复杂,而且还需要一个消息总线。

有一些Ehcache特性应用较少或者比较边缘化,没有提到,例如对于JMX的支持;还有一些则是有类似的特性和介绍了,例如对于WEB的支持,请参见我这篇关于OSCache的解读,其中的“web支持”一节有详细的原理分析。

最后,关于Ehcache的性能比对,下面这张图来自Ehcache的创始人Greg Luck的blog

put/get上Ehcache要500-1000倍快过Memcached。原因何在?他自己分析道:“In-process caching and asynchronous replication are a clear performance winner”。







二:ehcache 缓存使用


本文来源:lishuangzhe7047 的《ehcache 缓存使用


一:快速上手

1、  项目类库中添加ehcache.jar;

2、  在类路径下编写ehcache.xml配置文件。


二:详细配置步骤

     1,添加ehcache.xml文件

      将ehcache.xml文件添加到src路径下面。ehcache.xml文件内容如下

  1 <ehcache>
  2     <diskStore path="java.io.tempdir" />
  3     <defaultCache maxElementsInMemory="1000" eternal="false"
  4         timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" />
  5     <cache name="ehcacheName" maxElementsInMemory="10000"
  6         eternal="false" timeToIdleSeconds="300000" timeToLiveSeconds="600000"
  7         overflowToDisk="true" />
  8 </ehcache>

2,添加spring配置文件

     在applicContext.xml文件中添加

  1  <bean id="cacheManagerFactory"
  2     class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
  3     p:configLocation="classpath:ehcache.xml"></bean>
  4 
  5 <!-- 声明cacheManager -->
  6 <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
  7     p:cacheManager-ref="cacheManagerFactory" ></bean>
View Code


三:使用

     1,定义EHCache工具方法

  1 public class EHCache {
  2     private static final CacheManager cacheManager = new CacheManager();
  3     private Cache cache;
  4     public EHCacheService(){
  5         this.cache=cacheManager.getCache("ehcacheName")
  6     }
  7 
  8     public Cache getCache() {
  9         return cache;
 10     }
 11 
 12     public void setCache(Cache cache) {
 13         this.cache = cache;
 14     }
 15 
 16 
 17 
 18         /*
 19      * 通过名称从缓存中获取数据
 20      */
 21     public Object getCacheElement(String cacheKey) throws Exception {
 22             net.sf.ehcache.Element e = cache.get(cacheKey);
 23         if (e == null) {
 24             return null;
 25         }
 26         return e.getValue();
 27     }
 28     /*
 29      * 将对象添加到缓存中
 30      */
 31     public void addToCache(String cacheKey, Object result) throws Exception {
 32         Element element = new Element(cacheKey, result);
 33         cache.put(element);
 34     }
 35 
 36 
 37 }
View Code


2,测试


  1 public class Test{
  2     EHCache ehCache = new EHCache();
  3     public void Test(){
  4         //测试将json对象存入缓存中  
  5         JSONObject obj = new JSONObject();
  6         obj.put("name","lsz");
  7         ehCache.addToCache("cache_json",obj);
  8 
  9         //从缓存中获取  
 10         JSONObject getobj = (JSONObject)ehCache.getCacheElement("cache_json");
 11         System.out.println(getobj.toString());
 12     }
 13 }
View Code

四:问题解决

1,框架环境是自己搭建的,添加ehcache后运行出错:

org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/cache]
Offending resource: class path resource [applicationContext.xml]

出现这种问题,原因是因为在applicationContext.xml文件中 多加了

    <cache:annotation-driven cache-manager="cacheManager" /> 将其去掉即可

     2,框架需要添加jar包

     spring-context-support-3.2.0.RELEASE.jar

     spring-context-3.2.0.RELEASE.jar





三、配置文件参数详解

maven配置信息:

网最新的3.2 maven不能加载进来,而低于3.0的版本是运行不起来这段示例的。所以我把maven引用也贴出来

  1 <dependency>
  2         <groupId>javax.cache</groupId>
  3         <artifactId>cache-api</artifactId>
  4         <version>1.0.0</version>
  5     </dependency>
  6 
  7     <dependency>
  8  <groupId>org.ehcache</groupId>
  9  <artifactId>ehcache</artifactId>
 10  <version>3.0.0</version>
 11 </dependency>
View Code



本文来源:letmedown的《缓存之EHCache(一)

ehcache.xml详解

  1 ehcache.xml是ehcache的配置文件,并且存放在应用的classpath中。下面是对该XML文件中的一些元素及其属性的相关说明:
  2 
  3 <diskStore>元素:指定一个文件目录,当EHCache把数据写到硬盘上时,将把数据写到这个文件目录下。 下面的参数这样解释:
  4 
  5          user.home – 用户主目录
  6 
  7          user.dir      – 用户当前工作目录
  8 
  9          java.io.tmpdir – 默认临时文件路径
 10 
 11 <defaultCache>元素:设定缓存的默认数据过期策略。
 12 
 13 <cache>元素:设定具体的命名缓存的数据过期策略。
 14 
 15 <cache>元素的属性
 16 
 17         name:缓存名称。通常为缓存对象的类名(非严格标准)。
 18 
 19         maxElementsInMemory:设置基于内存的缓存可存放对象的最大数目。
 20 
 21         maxElementsOnDisk:设置基于硬盘的缓存可存放对象的最大数目。
 22 
 23         eternal:如果为true,表示对象永远不会过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false;
 24 
 25         timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期。当对象过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态。
 26 
 27         timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期。当对象过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义。
 28 
 29         overflowToDisk:如果为true,表示当基于内存的缓存中的对象数目达到了maxElementsInMemory界限后,会把益出的对象写到基于硬盘的缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。
 30 
 31 memoryStoreEvictionPolicy:缓存对象清除策略。有三种:
 32 
 33         1 FIFO ,first in first out ,这个是大家最熟的,先进先出,不多讲了
 34 
 35         2 LFU , Less Frequently Used ,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。
 36 
 37         2 LRU ,Least Recently Used ,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
 38 
 39 


四、单独使用EHCache 详解

本文来源:letmedown的《缓存之EHCache(一)

1.创建CacheManager (net.sf.ehcache.CacheManager)

(1)使用默认配置文件创建
CacheManager manager = CacheManager.create();

(2)使用指定配置文件创建


CacheManager manager = CacheManager.create("src/config/ehcache.xml");

(3)从classpath找寻配置文件并创建

  1 URL url = getClass().getResource("/anothername.xml");
  2 CacheManager manager = CacheManager.create(url);

(4)通过输入流创建

  1 
  2 InputStream fis = new FileInputStream(new File("src/config/ehcache.xml").getAbsolutePath());
  3 try {
  4  manager = CacheManager.create(fis);
  5  } finally {
  6  fis.close();
  7 }
  8 

2.创建Caches (net.sf.ehcache.Cache)

(1)取得配置文件中预先 定义的sampleCache1设置,生成一个Cache


Cache cache = manager.getCache("sampleCache1");

(2)设置一个名为test 的新cache,test属性为默认


CacheManager manager = CacheManager.create();
manager.addCache("test");

(3)设置一个名为test 的新cache,并定义其属性


CacheManager manager = CacheManager.create();
Cache cache = new Cache("test", 1, true, false, 5, 2);
manager.addCache(cache);

(4)删除cache


CacheManager singletonManager = CacheManager.create();
singletonManager.removeCache("sampleCache1");

3.使用Caches

(1)往cache中加入元素


Element element = new Element("key1", "value1");
cache.put(new Element(element);

(2)从cache中取得元素


Element element = cache.get("key1");

(3)从cache中删除元素


Cache cache = manager.getCache("sampleCache1");
Element element = new Element("key1", "value1");
cache.remove("key1");

4.卸载CacheManager ,关闭Cache

1
manager.shutdown();

缓存的创建,采用自动的方式
CacheManager singletonManager = CacheManager.create();
singletonManager.addCache("testCache");
Cache test = singletonManager.getCache("testCache");   

或者直接创建Cache

CacheManager singletonManager = CacheManager.create();
Cache memoryOnlyCache = new Cache("testCache", 5000, false, false, 5, 2);
manager.addCache(memoryOnlyCache);
Cache test = singletonManager.getCache("testCache");   
删除cache
CacheManager singletonManager = CacheManager.create();
singletonManager.removeCache("sampleCache1");   
在使用ehcache后,需要关闭
CacheManager.getInstance().shutdown()   

caches 的使用

Cache cache = manager.getCache("sampleCache1");   

执行crud操作

Cache cache = manager.getCache("sampleCache1");
Element element = new Element("key1", "value1");
cache.put(element);   
update
Cache cache = manager.getCache("sampleCache1");
cache.put(new Element("key1", "value1");
//This updates the entry for "key1"
cache.put(new Element("key1", "value2");   

get Serializable

Cache cache = manager.getCache("sampleCache1");
Element element = cache.get("key1");
Serializable value = element.getValue();   
get non serializable
Cache cache = manager.getCache("sampleCache1");
Element element = cache.get("key1");
Object value = element.getObjectValue();   
remove
Cache cache = manager.getCache("sampleCache1");
Element element = new Element("key1", "value1"
cache.remove("key1");   

五、在 Hibernate 中运用EHCache

1、hibernate.cfg.xml中需设置如下:

3系列版本加入

  1 <property name= hibernate.cache.provider_class>
  2  org.hibernate.cache.EhCacheProvider
  3 </property>
  4 
View Code

EhCacheProvider类位于hibernate3.jar

2.1版本加入

net.sf.ehcache.hibernate.Provider

2.1以下版本加入

net.sf.hibernate.cache.EhCache

2、在Hibernate3.x中的etc目录下有ehcache.xml的示范文件,将其复制应用程序的src目录下(编译时会把ehcache.xml复制到WEB-INF/classess目录下),对其中的相关值进行更改以和自己的程序相适合。

3、持久化类的映射文件进行配置


<cache usage="read-write"/>

在<set>标记中设置了<cache usage="read-write"/>,但Hibernate仅把和Group相关的Student的主键id加入到缓存中,如果希望把整个Student的散装属性都加入到二级缓存中,还需要在Student.hbm.xml文件的<class>标记中加入<cache>子标记,如下所示:

 
<cache usage="read-write" /> <!--cache标记需跟在class标记后-->

注:SSH中hibernate配置的cache信息

 
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>

配置实例:

ehcache.xml

  1 
  2 
  3 <?xml version="1.0" encoding="UTF-8"?>
  4 <ehcache>
  5     <diskStore path="java.io.tmpdir"/>--------------这一行很重要不然会报错
  6      <!--
  7     <cacheManagerEventListenerFactory class="" properties=""/>
  8     <cacheManagerPeerProviderFactory
  9             class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
 10             properties="peerDiscovery=automatic,
 11                         multicastGroupAddress=230.0.0.1,
 12                         multicastGroupPort=4446, timeToLive=1"
 13             propertySeparator=","
 14             />
 15 
 16     <cacheManagerPeerListenerFactory
 17             class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"/>
 18     -->
 19     <defaultCache
 20             maxElementsInMemory="10000"
 21             eternal="false"
 22             timeToIdleSeconds="36000"
 23             timeToLiveSeconds="86400"
 24             overflowToDisk="false"
 25             />
 26             <!--
 27             diskSpoolBufferSizeMB="30"
 28             maxElementsOnDisk="100000"
 29             diskPersistent="false"
 30             diskExpiryThreadIntervalSeconds="120"
 31             memoryStoreEvictionPolicy="LRU"
 32             -->
 33     <cache name="reCache"
 34             maxElementsInMemory="10000"
 35             eternal="false"
 36             timeToIdleSeconds="36000"
 37             timeToLiveSeconds="86400"
 38             overflowToDisk="false"
 39             />
 40 
 41     <!--<cache name="sampleDistributedCache1"
 42            maxElementsInMemory="10"
 43            eternal="false"
 44            timeToIdleSeconds="100"
 45            timeToLiveSeconds="100"
 46            overflowToDisk="false">
 47         <cacheEventListenerFactory
 48                 class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>
 49         <bootstrapCacheLoaderFactory
 50                 class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
 51     </cache>-->
 52 
 53 </ehcache>
 54 
View Code





六:ehcache简单实例

本文来源:kanguhong 的《java缓存框架---ehcache简单实例

java缓存有很多,ehcache是比较流行的java缓存框架,它以简单,快速等特点受到广大开发人员的喜爱,下面是我参考了一些资料后整理的关于ehcache的简单实例,包括通过配置文件和java动态添加缓存,以下是详细代码:

一、通过使用API来动态的添加缓存(将缓存的配置信息通过java代码来实现而非写在配置文件)

  1 package ehcache;
  2 
  3 import net.sf.ehcache.Cache;
  4 import net.sf.ehcache.CacheManager;
  5 import net.sf.ehcache.Element;
  6 /**
  7  * 使用API来动态的添加缓存(将缓存的配置信息通过java代码来实现而非写在配置文件)
  8  * @author Administrator
  9  *
 10  */
 11 public class EhCache1 {
 12 
 13 	public static void main(String[] args) {
 14         //创建一个缓存管理器
 15         CacheManager singletonManager = CacheManager.create();
 16         //建立一个缓存实例
 17         Cache memoryOnlyCache = new Cache("testCache", 5000, false, false, 5, 2);
 18         //在内存管理器中添加缓存实例
 19         singletonManager.addCache(memoryOnlyCache);
 20         //在缓存管理器中获取一个缓存实例
 21         Cache cache = singletonManager.getCache("testCache");
 22         //使用获取到的缓存实例
 23         Element element = new Element("key1", "value1");
 24         cache.put(element);//添加缓存值
 25         cache.put(new Element("key2", "value2"));//添加缓存值
 26 
 27         int elementsInMemory = cache.getSize();//获取缓存个数
 28         System.out.println("缓存个数======="+elementsInMemory);
 29 
 30 //        Object obj = element.getObjectValue();//获取对象值
 31 //        cache.remove("key1");//删除缓存
 32 
 33         Cache cache2 = singletonManager.getCache("testCache");//获取缓存实例
 34         Element element2 = cache2.get("key1");
 35         System.out.println("value====="+element2.getValue());//获取缓存值
 36 //        singletonManager.shutdown();
 37 	}
 38 
 39 }
 40 
View Code


二、通过配置文件ehcache.xml创建缓存实例

1.ehcache.xml

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3          xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  4 
  5   <!-- 磁盘缓存位置 -->
  6   <diskStore path="java.io.tmpdir/ehcache"/>
  7 
  8   <!-- 默认缓存 -->
  9   <defaultCache
 10           maxEntriesLocalHeap="10000"
 11           eternal="false"
 12           timeToIdleSeconds="120"
 13           timeToLiveSeconds="120"
 14           maxEntriesLocalDisk="10000000"
 15           diskExpiryThreadIntervalSeconds="120"
 16           memoryStoreEvictionPolicy="LRU"/>
 17 
 18   <!-- helloworld1缓存 -->
 19   <cache name="helloworld1"
 20          maxElementsInMemory="1000"
 21          eternal="false"
 22          timeToIdleSeconds="5"
 23          timeToLiveSeconds="5"
 24          overflowToDisk="false"
 25          memoryStoreEvictionPolicy="LRU"/>
 26 
 27          <!-- helloworld2缓存 -->
 28   <cache name="helloworld2"
 29          maxElementsInMemory="1000"
 30          eternal="false"
 31          timeToIdleSeconds="5"
 32          timeToLiveSeconds="5"
 33          overflowToDisk="false"
 34          memoryStoreEvictionPolicy="LRU"/>
 35 
 36 </ehcache>
View Code


2.java代码

  1 package ehcache;
  2 
  3 import net.sf.ehcache.Cache;
  4 import net.sf.ehcache.CacheManager;
  5 import net.sf.ehcache.Element;
  6 /**
  7  * 通过配置文件(ehcache.xml)来使用缓存
  8  * @author Administrator
  9  */
 10 public class EhCache2 {
 11 
 12     public static void main(String[] args) {
 13          //创建缓存管理器  
 14         final CacheManager cacheManager = new CacheManager();
 15 
 16         // 创建一个缓存实例(在配置文件中获取一个缓存实例)  
 17         final Cache cache = cacheManager.getCache("helloworld1");
 18 
 19         final String key = "greeting";
 20 
 21         //他建一个数据容器  
 22         final Element putGreeting = new Element(key, "Hello, World!");
 23 
 24         //将数据放入到缓存实例中  
 25         cache.put(putGreeting);
 26 
 27         //取值  
 28         final Cache cache2 = cacheManager.getCache("helloworld1");
 29         final Element getGreeting = cache2.get(key);
 30 
 31         // Print the value  
 32         System.out.println("value======//========"+getGreeting.getObjectValue());
 33     }
 34 
 35 }
View Code


spring+ehcache整合

本文来源:kanguhong 的《java缓存框架---spring+ehcache整合》 具体请参考:《Spring 整合 Ehcache 管理缓存详解

工程结构图:

1.ehcache.xml配置文件

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3          xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  4 
  5   <!-- 磁盘缓存位置 -->
  6   <diskStore path="java.io.tmpdir/ehcache"/>
  7 
  8   <!-- 默认缓存 -->
  9   <defaultCache
 10           maxEntriesLocalHeap="10000"
 11           eternal="false"
 12           timeToIdleSeconds="120"
 13           timeToLiveSeconds="120"
 14           maxEntriesLocalDisk="10000000"
 15           diskExpiryThreadIntervalSeconds="120"
 16           memoryStoreEvictionPolicy="LRU"/>
 17 
 18   <!-- helloworld1缓存 -->
 19   <cache name="helloworld1"
 20          maxElementsInMemory="1000"
 21          eternal="false"
 22          timeToIdleSeconds="5"
 23          timeToLiveSeconds="5"
 24          overflowToDisk="false"
 25          memoryStoreEvictionPolicy="LRU"/>
 26 
 27    <!-- helloworld2缓存 -->
 28   <cache name="helloworld2"
 29          maxElementsInMemory="1000"
 30          eternal="false"
 31          timeToIdleSeconds="5"
 32          timeToLiveSeconds="5"
 33          overflowToDisk="false"
 34          memoryStoreEvictionPolicy="LRU"/>
 35 
 36     <!-- users缓存 -->
 37   <cache name="users"
 38          maxElementsInMemory="1000"
 39          eternal="false"
 40          timeToIdleSeconds="5"
 41          timeToLiveSeconds="5"
 42          overflowToDisk="false"
 43          memoryStoreEvictionPolicy="LRU"/>
 44 
 45 </ehcache>
View Code


2.spring-ehcache.xml配置文件

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <beans xmlns="http://www.springframework.org/schema/beans"
  3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4        xmlns:cache="http://www.springframework.org/schema/cache"
  5        xsi:schemaLocation="http://www.springframework.org/schema/beans
  6 
  7 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  8 
  9 
 10 http://www.springframework.org/schema/cache
 11 
 12         http://www.springframework.org/schema/cache/spring-cache-3.2.xsd">
 13 
 14   <description>ehcache缓存配置管理文件</description>
 15 
 16   <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
 17     <property name="configLocation" value="classpath:spring/ehcache.xml"/>
 18   </bean>
 19 
 20   <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
 21     <property name="cacheManager" ref="ehcache"/>
 22   </bean>
 23 
 24     <!-- 启用缓存注解开关 -->
 25     <cache:annotation-driven cache-manager="cacheManager"/>
 26 </beans>
View Code


3.applicationContext.xml配置文件

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <beans xmlns="http://www.springframework.org/schema/beans"
  3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4         xmlns:context="http://www.springframework.org/schema/context"
  5         xsi:schemaLocation="http://www.springframework.org/schema/beans
  6         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7         http://www.springframework.org/schema/context
  8         http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  9 
 10    <!-- 启动注解服务 -->
 11   <context:component-scan base-package="ehcache.com"/>
 12    <!-- 加载资源文件 -->
 13   <import resource="classpath:spring/spring-ehcache.xml"/>
 14 </beans>
View Code


4.实体类User.java

  1 package ehcache.com.model;
  2 
  3 import java.io.Serializable;
  4 
  5 public class User implements Serializable{
  6     private static final long serialVersionUID = 1L;
  7     private int id;
  8     private String name;
  9 
 10     public User() {}
 11 
 12 
 13     public User(int id, String name) {
 14         this.id = id;
 15         this.name = name;
 16     }
 17 
 18 
 19     public int getId() {
 20         return id;
 21     }
 22     public void setId(int id) {
 23         this.id = id;
 24     }
 25     public String getName() {
 26         return name;
 27     }
 28     public void setName(String name) {
 29         this.name = name;
 30     }
 31 
 32 
 33 
 34 }
View Code


5.业务类UserService.java

  1 package ehcache.com.service;
  2 
  3 import java.util.HashSet;
  4 import java.util.Set;
  5 
  6 import org.springframework.stereotype.Service;
  7 import org.springframework.cache.annotation.CacheEvict;
  8 import org.springframework.cache.annotation.Cacheable;
  9 
 10 import ehcache.com.model.User;
 11 
 12 @Service
 13 public class UserService {
 14     private Set<User> users;
 15 
 16     public UserService() {
 17            users = new HashSet<User>();
 18            User user1 = new User(1, "张三");
 19            User user2 = new User(2, "赵四");
 20            User user3 = new User(3, "王五");
 21            users.add(user1);
 22            users.add(user2);
 23            users.add(user3);
 24        }
 25 
 26 
 27         @Cacheable({"users"})
 28         public User findUser(User user) {
 29             return findUserInDB(user.getId());
 30         }
 31 
 32         @Cacheable(value = "users", condition = "#user.getId() <= 2")
 33         public User findUserInLimit(User user) {
 34             return findUserInDB(user.getId());
 35         }
 36 
 37         @CacheEvict(value="users")
 38         public void removeUser(User user) {
 39             removeUserInDB(user.getId());
 40         }
 41 
 42         @CacheEvict(value = "users", allEntries = true)
 43         public void clear() {
 44             removeAllInDB();
 45         }
 46 
 47         /**
 48          * 模拟查找数据库
 49          */
 50         public User findUserInDB(int id) {
 51             for (User u : users) {
 52                 if (id == u.getId()) {
 53                     System.out.println("查找数据库 id = " + id + " 成功");
 54                     return u;
 55                 }
 56             }
 57             return null;
 58         }
 59 
 60 
 61         /**
 62          * 模拟更新数据库
 63          */
 64         public void updateUserInDB(User user) {
 65             for (User u : users) {
 66                 if (user.getId() == u.getId()) {
 67                     System.out.println("更新数据库" + u + " -> " + user);
 68                     u.setName(user.getName());
 69                 }
 70             }
 71         }
 72 
 73 
 74 
 75         private void removeUserInDB(int id) {
 76             for (User u : users) {
 77                 if (id == u.getId()) {
 78                     System.out.println("从数据库移除 id = " + id + " 的数据");
 79                     users.remove(u);
 80                     break;
 81                 }
 82             }
 83         }
 84 
 85 
 86         private void removeAllInDB() {
 87             users.clear();
 88         }
 89 
 90 }
View Code


6.测试类EhcacheWithSpringTest.java

  1 package ehcache.com;
  2 
  3 import org.junit.Test;
  4 import org.junit.runner.RunWith;
  5 import org.springframework.test.context.ContextConfiguration;
  6 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  7 import org.springframework.beans.factory.annotation.Autowired;
  8 
  9 import ehcache.com.model.User;
 10 import ehcache.com.service.UserService;
 11 /**
 12  * 本类展示并测试 Spring + Ehcache 的缓存解决方案
 13  */
 14 @RunWith(SpringJUnit4ClassRunner.class)
 15 @ContextConfiguration(locations = {"classpath:spring/applicationContext.xml"})
 16 public class EhcacheWithSpringTest {
 17     @Autowired
 18     UserService userService;
 19 
 20     /**
 21      * 测试@Cacheable
 22      */
 23     @Test
 24     public void testFindUser() throws InterruptedException {
 25 
 26         // 设置查询条件  
 27         User user1 = new User(1, null);
 28         User user2 = new User(2, null);
 29         User user3 = new User(3, null);
 30 
 31         System.out.println("第一次查询");
 32         System.out.println(userService.findUser(user1));
 33         System.out.println(userService.findUser(user2));
 34         System.out.println(userService.findUser(user3));
 35 
 36         System.out.println("
第二次查询");
 37         System.out.println(userService.findUser(user1));
 38         System.out.println(userService.findUser(user2));
 39         System.out.println(userService.findUser(user3));
 40 
 41         // 在classpath:ehcache/ehcache.xml中,设置了userCache的缓存时间为3000 ms, 这里设置等待  
 42         Thread.sleep(3000);
 43 
 44         System.out.println("
缓存过期,再次查询");
 45         System.out.println(userService.findUser(user1));
 46         System.out.println(userService.findUser(user2));
 47         System.out.println(userService.findUser(user3));
 48    }
 49 
 50 
 51     /**
 52      * 测试@Cacheable设置Spring SpEL条件限制
 53      */
 54     @Test
 55     public void testFindUserInLimit() throws InterruptedException {
 56         // 设置查询条件  
 57         User user1 = new User(1, null);
 58         User user2 = new User(2, null);
 59         User user3 = new User(3, null);
 60 
 61         System.out.println("第一次查询user info");
 62         System.out.println(userService.findUserInLimit(user1));
 63         System.out.println(userService.findUserInLimit(user2));
 64         System.out.println(userService.findUserInLimit(user3));
 65 
 66         System.out.println("
第二次查询user info");
 67         System.out.println(userService.findUserInLimit(user1));
 68         System.out.println(userService.findUserInLimit(user2));
 69         System.out.println(userService.findUserInLimit(user3)); // 超过限制条件,不会从缓存中读数据  
 70 
 71         // 在classpath:ehcache/ehcache.xml中,设置了userCache的缓存时间为3000 ms, 这里设置等待  
 72         Thread.sleep(3000);
 73 
 74         System.out.println("
缓存过期,再次查询");
 75         System.out.println(userService.findUserInLimit(user1));
 76         System.out.println(userService.findUserInLimit(user2));
 77         System.out.println(userService.findUserInLimit(user3));
 78     }
 79 
 80 
 81     /**
 82      * 测试@CachePut
 83      */
 84     @Test
 85     public void testUpdateUser() {
 86         // 设置查询条件  
 87         User user2 = new User(2, null);
 88 
 89         System.out.println(userService.findUser(user2));
 90         userService.updateUserInDB(new User(2, "尼古拉斯.赵四"));
 91         System.out.println(userService.findUser(user2));
 92    }
 93 
 94 
 95     /**
 96      * 测试@CacheEvict删除指定缓存
 97      */
 98     @Test
 99     public void testRemoveUser() {
100         // 设置查询条件  
101         User user1 = new User(1, null);
102 
103         System.out.println("数据删除前:");
104         System.out.println(userService.findUser(user1));
105 
106         userService.removeUser(user1);
107         System.out.println("数据删除后:");
108         System.out.println(userService.findUser(user1));
109     }
110 
111 
112     /**
113      * 测试@CacheEvict删除所有缓存
114      */
115     @Test
116     public void testClear() {
117         System.out.println("数据清空前:");
118         System.out.println(userService.findUser(new User(1, null)));
119         System.out.println(userService.findUser(new User(2, null)));
120         System.out.println(userService.findUser(new User(3, null)));
121 
122         userService.clear();
123         System.out.println("
数据清空后:");
124         System.out.println(userService.findUser(new User(1, null)));
125         System.out.println(userService.findUser(new User(2, null)));
126         System.out.println(userService.findUser(new User(3, null)));
127      }
128 
129 
130 
131 }
132 
View Code


八:EhCache在S2SH的用法

1,下载EhCache web版  http://ehcache.org/downloads/catalog  注意下载web版。可见附件

2,解压 将ehcache-web-2.0.3.jar 、ehcache-core-2.3.0.jar  拷入lib中。

3,在src下建立ehcache.xml

4,开始配置 web.xml ,注意此filter配置应该放在Struts2核心filter的前面。

  1 <!-- 页面缓存配置 ehcache -->
  2     <filter>
  3         <filter-name>SimplePageFragmentCachingFilter</filter-name>
  4         <filter-class>net.sf.ehcache.constructs.web.filter.SimplePageFragmentCachingFilter</filter-class>
  5         <init-param>
  6             <param-name>suppressStackTrace</param-name>
  7             <param-value>false</param-value>
  8         </init-param>
  9         <init-param>
 10             <param-name>cacheName</param-name>
 11             <param-value>SimplePageFragmentCachingFilter</param-value>
 12         </init-param>
 13     </filter>
 14 
 15     <filter-mapping>
 16         <filter-name>SimplePageFragmentCachingFilter</filter-name>
 17         <url-pattern>/index.action</url-pattern>
 18     </filter-mapping>
View Code

5,配置ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>

  1 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2          xsi:noNamespaceSchemaLocation="ehcache.xsd"
  3          updateCheck="true" monitoring="autodetect"
  4          dynamicConfig="true">
  5          <diskStore path="java.io.tmpdir"/>
  6          <cache name="SimplePageFragmentCachingFilter"
  7         maxElementsInMemory="10"
  8         eternal="false"
  9         timeToIdleSeconds="10000"
 10         timeToLiveSeconds="10000"
 11         overflowToDisk="true">
 12     </cache>
 13 </ehcache>
View Code


重启服务器,运行页面,第一次访问查询了数据库,此时EhCache将页面的内容缓存,下一次访问的时候,则直接使用缓存中的数据,而不必去访问数据库。一直到缓存过期,才会重新查询数据库。

ehcache是一个非常轻量级的缓存实现,而且从1.2之后就支持了集群,目前的最新版本是1.3,而且是hibernate默认的缓存provider。虽然本文是介绍的是ehcache对页面缓存的支持,但是ehcache的功能远不止如此,当然要使用好缓存,对JEE中缓存的原理,使用范围,适用场景等等都需要有比较深刻的理解,这样才能用好缓存,用对缓存



九:加入ehcache后,系统出现内存泄漏,解决办法

本文来源:a9529lty 的《加入ehcache后,系统出现内存泄漏,解决办法

最近在系统中,加入缓存ehcache,但发现,每隔一天,服务器就会报出内存溢出。
问题严重,后来在网上查资料发现,一篇解释的网文:

  1 spring中的提供了一个名为org.springframework.web.util.IntrospectorCleanupListener的监听器。它主要负责
  2 处理由 JavaBeans  Introspector的使用而引起的缓冲泄露。spring中对它的描述如下:
  3 
  4 它是一个在web应用关闭的时候,清除JavaBeans Introspector的监听器.在web.xml中注册这个listener.
  5 可以保证在web 应用关闭的时候释放与掉这个web 应用相关的class loader 和由它管理的类
  6 
  7 如果你使用了JavaBeans Introspector来分析应用中的类,Introspector 缓冲中会保留这些类的引用.
  8 结果在你的应用关闭的时候,这些类以及web 应用相关的class loader没有被垃圾回收.
View Code


解决办法,就是在web.xml中加入:
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
问题解决,再也没有报内存泄漏了。


Introspector 缓存清除监听器

Spring 还提供了一个名为 org.springframework.web.util.IntrospectorCleanupListener 的监听器。它主要负责处理由 JavaBean Introspector 功能而引起的缓存泄露。IntrospectorCleanupListener 监听器在 Web 应用关闭的时会负责清除 JavaBean Introspector 的缓存,在 web.xml 中注册这个监听器可以保证在 Web 应用关闭的时候释放与其相关的 ClassLoader 的缓存和类引用。如果您使用了 JavaBean Introspector 分析应用中的类,Introspector 缓存会保留这些类的引用,结果在应用关闭的时候,这些类以及Web 应用相关的 ClassLoader 不能被垃圾回收。不幸的是,清除 Introspector 的唯一方式是刷新整个缓存,这是因为没法准确判断哪些是属于本 Web 应用的引用对象,哪些是属于其它 Web 应用的引用对象。所以删除被缓存的 Introspection 会导致将整个 JVM 所有应用的 Introspection 都删掉。需要注意的是,Spring 托管的 Bean 不需要使用这个监听器,因为 Spring 的 Introspection 所使用的缓存在分析完一个类之后会马上从 javaBean Introspector 缓存中清除掉,并将缓存保存在应用程序特定的 ClassLoader 中,所以它们一般不会导致内存资源泄露。但是一些类库和框架往往会产生这个问题。例如 Struts 和 Quartz 的 Introspector 的内存泄漏会导致整个的 Web 应用的 ClassLoader 不能进行垃圾回收。在 Web 应用关闭之后,您还会看到此应用的所有静态类引用,这个错误当然不是由这个类自身引起的。解决这个问题的方法很简单,您仅需在 web.xml 中配置 IntrospectorCleanupListener 监听器就可以了:

<listener>
<listener-class>
org.springframework.web.util.IntrospectorCleanupListener
</listener-class>
</listener>



十:Ehcache的一个完整例子

本文来源:ouyida3 的 《Ehcache的一个完整例子

测试类

测试驱动开发,先写测试类

  1 package com.company.mapp.cache;
  2 
  3 import org.junit.Test;
  4 
  5 import com.company.component.PageData;
  6 
  7 public class CacheTest {
  8 
  9     @Test
 10     public final void testGetValue() throws Exception {
 11         Cache cache = CacheFactory.getCache(Constants.MAPP_STATIC_PARAM_CFG);
 12         cache.setPageData(new PageData());
 13         cache.getValue("MAPP_AMAP_PARAM");
 14     }
 15 
 16 }
View Code


接口

面向接口开发,先有一个接口

  1 package com.company.mapp.cache;
  2 
  3 import java.util.Map;
  4 
  5 import com.company.component.PageData;
  6 
  7 /**
  8  * 缓存接口
  9  *
 10  * @see CacheTest
 11  * @author ouyida3
 12  * @since 2015.4.3
 13  */
 14 public interface Cache {
 15 
 16     public String getValue(String key) throws Exception;
 17 
 18     public Map<String, String> getMap(String key) throws Exception;
 19 
 20     public Cache setPageData(PageData pd);
 21 
 22 }
View Code


工厂类

  1 package com.company.mapp.cache;
  2 
  3 /**
  4  * 缓存工厂
  5  * 生产各张数据表的缓存
  6  *
  7  * @author ouyida3
  8  * @since 2015.4.4
  9  */
 10 public class CacheFactory {
 11 
 12     public static Cache getCache(String name){
 13         if (Constants.MAPP_STATIC_PARAM_CFG.equals(name))
 14             return new StaticParamCfgCache(name);
 15         else if (Constants.MAPP_S_STATIC.equals(name))
 16             return new SStaticCache(name);
 17         return null;
 18     }
 19 
 20 }
View Code

常量

  1 package com.company.mapp.cache;
  2 
  3 /**
  4  * 缓存常量类
  5  *
  6  * @author ouyida3
  7  * @since 2015.4.4
  8  */
  9 public class Constants {
 10     public static final String MAPP_STATIC_PARAM_CFG = "MAPP_STATIC_PARAM_CFG";
 11     public static final String MAPP_S_STATIC = "MAPP_S_STATIC";
 12 }
View Code

抽象类

每个缓存都有共同的方法,所以必须得有抽象类放共同方法

  1 package com.company.mapp.cache;
  2 
  3 import java.util.Map;
  4 
  5 import com.company.component.PageData;
  6 import com.company.mapp.bean.FSBean;
  7 
  8 /**
  9  * 缓存抽象类
 10  * 每个数据表的缓存类的重复的方法在这里实现
 11  *
 12  * @author ouyida3
 13  * @since 2015.4.4
 14  */
 15 public abstract class AbstractCache implements Cache {
 16     protected String cacheName;
 17     protected String eparchyCode;
 18     protected PageData pd;
 19 
 20     protected Cache setEparchyCode(String eparchyCode) {
 21         this.eparchyCode = eparchyCode;
 22         return this;
 23     }
 24 
 25     protected AbstractCache(String name) {
 26         this.cacheName = name;
 27     }
 28 
 29     @Override
 30     public Cache setPageData(PageData pd) {
 31         this.pd = pd;
 32         try {
 33             setEparchyCode(FSBean.getEparchyCode(pd));
 34         } catch (Exception e) {
 35             e.printStackTrace();
 36         }
 37         return this;
 38     }
 39 
 40     @Override
 41     public String getValue(String key) throws Exception {
 42         return null;
 43     }
 44 
 45     @Override
 46     public Map<String, String> getMap(String key) throws Exception {
 47         return null;
 48     }
 49 }
View Code


其中一张数据表

表里就两个字段最重要,很简单,就是根据key取value

  1 package com.company.mapp.cache;
  2 
  3 import net.sf.ehcache.CacheManager;
  4 import net.sf.ehcache.Element;
  5 
  6 /**
  7  * 数据表MAPP_STATIC_PARAM_CFG缓存
  8  *
  9  * @author ouyida3
 10  * @since 2015.4.3
 11  */
 12 public class StaticParamCfgCache extends AbstractCache {
 13 
 14     protected StaticParamCfgCache(String name) {
 15         super(name);
 16     }
 17 
 18     public String getValue(String key) throws Exception {
 19         net.sf.ehcache.Cache cache = CacheManager.getInstance().getCache(cacheName);
 20         String cacheKey = key + "_" + eparchyCode;
 21 
 22         if (cache != null) {
 23             Element element = cache.get(cacheKey);
 24             if (element != null)
 25                 return (String) element.getValue();
 26         }
 27 
 28         CacheDao dao = new CacheDao();
 29         String value = dao.queryStaticParamCfg(cacheName, key, pd);
 30 
 31         if (cache != null) {
 32             cache.put(new Element(cacheKey, value));
 33         }
 34 
 35         return value;
 36     }
 37 }
View Code


其中另一张数据表

比刚才复杂一点,有三个重要字段,根据1、2取到3,因此我设计为先根据1取到2和3的map,然后再根据2取3

  1 package com.company.mapp.cache;
  2 
  3 import java.util.HashMap;
  4 import java.util.Map;
  5 
  6 import net.sf.ehcache.CacheManager;
  7 import net.sf.ehcache.Element;
  8 
  9 import com.company.appframework.data.DataMap;
 10 import com.company.appframework.data.IDataset;
 11 
 12 /**
 13  * 数据表MAPP_S_STATIC缓存
 14  *
 15  * @author ouyida3
 16  * @since 2015.4.4
 17  */
 18 public class SStaticCache extends AbstractCache {
 19 
 20     public SStaticCache(String name) {
 21         super(name);
 22     }
 23 
 24     @SuppressWarnings("unchecked")
 25     public Map<String, String> getMap(String key) throws Exception {
 26         net.sf.ehcache.Cache cache = CacheManager.getInstance().getCache(cacheName);
 27         String cacheKey = key + "_" + eparchyCode;
 28 
 29         if (cache != null) {
 30             Element element = cache.get(cacheKey);
 31             if (element != null)
 32                 return (Map<String, String>) element.getValue();
 33         }
 34 
 35         CacheDao dao = new CacheDao();
 36         IDataset dataSet = dao.querySStatic(cacheName, key, pd);
 37         HashMap<String, String> value = new HashMap<String, String>();
 38         for (Object dataMap : dataSet) {
 39             value.put((String)((DataMap)dataMap).get("DATA_ID"), (String)((DataMap)dataMap).get("DATA_NAME"));
 40         }
 41 
 42         if (cache != null) {
 43             cache.put(new Element(cacheKey, value));
 44         }
 45 
 46         return value;
 47     }
 48 
 49 }
View Code

工具类

最后加的,同事认为这样调用更方便,我试了一下,确实是。

  1 package com.company.mapp.cache;
  2 
  3 import java.util.Map;
  4 
  5 import com.company.component.PageData;
  6 
  7 /**
  8  * 缓存工具类
  9  * 外部直接调用该类的方法,隐藏整个Cache框架的实现与调用细节
 10  *
 11  * @author ouyida3
 12  * @since 2015.4.4
 13  */
 14 public class CacheUtils {
 15 
 16     public static String getStaticValue(String dataId, String typeId, PageData pd) throws Exception {
 17         Cache cache = CacheFactory.getCache(Constants.MAPP_S_STATIC);
 18         cache.setPageData(pd);
 19         Map<String, String> map = cache.getMap(typeId);
 20         String dataName = (String)map.get(dataId);
 21         return dataName;
 22     }
 23 
 24 }
View Code





---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

原文地址:https://www.cnblogs.com/ios9/p/7725234.html