前言:设计一套缓存框架需要关注的要素
本文来源:RayChase 的《设计一套缓存框架需要关注的要素》
最近关注了一些缓存框架的特性和实现,包括OSCache、JCS、Ehcache、Memcached等等,公司的两个缓存框架,以及一个标准JSR 107(JCache),发现一些诸多类同的方面。如果你不够熟悉以上,不妨先看看这两篇文章:《OSCache框架源码解析》和《Ehcache详细解读》,再看下面的内容也许会有更多想法。之后再思考,如果要自己去实现一套缓存框架,需要考虑哪些东西?
1、为哪些数据做缓存?
- 模型对象,这在业务逻辑层面最常见。
- 数据库查询结果集。
- 页面缓存、页面片段缓存。
- 运算结果集,尤其对于幂等性服务。
- 外部接口查询结果。
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()
缓存事件监听器。允许注册实现了CacheEventListener接口的监听器,它提供了许多对缓存事件发生后的处理机制:
1 notifyElementRemoved/Put/Updated/Expired
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
当然,也支持这种类似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
事务的例子:
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
四、一致性模型:
说到一致性,数据库的一致性是怎样的?不妨先来回顾一下数据库的几个隔离级别:
未提交读(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
但是使用UnlockedReadsView:
Java代码
1 Cache cache = cacheManager.getEhcache("myCache");
2 UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache");
3
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
五、缓存拓扑类型:
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
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
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>
三:使用
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 }
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 }
四:问题解决
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>
本文来源: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
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
六: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
二、通过配置文件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>
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 }
七: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>
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>
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>
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 }
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 }
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("\n第二次查询");
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("\n缓存过期,再次查询");
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("\n第二次查询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("\n缓存过期,再次查询");
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("\n数据清空后:");
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
八: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>
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>
重启服务器,运行页面,第一次访问查询了数据库,此时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没有被垃圾回收.
解决办法,就是在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 }
接口
面向接口开发,先有一个接口
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 }
工厂类
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 }
常量
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 }
抽象类
每个缓存都有共同的方法,所以必须得有抽象类放共同方法
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 }
其中一张数据表
表里就两个字段最重要,很简单,就是根据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 }
其中另一张数据表
比刚才复杂一点,有三个重要字段,根据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 }
工具类
最后加的,同事认为这样调用更方便,我试了一下,确实是。
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 }
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Ehcache的更多相关文章
-
[原创]mybatis中整合ehcache缓存框架的使用
mybatis整合ehcache缓存框架的使用 mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓 ...
-
springmvc 多数据源 SSM java redis shiro ehcache 头像裁剪
获取下载地址 QQ 313596790 A 调用摄像头拍照,自定义裁剪编辑头像 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,开发利器)+快速构建表单; 技术:31359679 ...
-
网站缓存技术总结( ehcache、memcache、redis对比)
网站技术高速发展的今天,缓存技术已经成为大型网站的一个关键技术,缓存设计好坏直接关系的一个网站访问的速度,以及购置服务器的数量,甚至影响到用户的体验. 网站缓存按照存放的地点不同,可以分为客户端缓存. ...
-
【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(前言)
一直希望能够搭建一个完整的,基础Web框架,方便日后接一些外快的时候,能够省时省力,终于花了一周的时间,把这个东西搞定了.特此写下此博客,一来是纪念,二来是希望能够为别人提供方便.顺带说一下,恩,组合 ...
-
转载:Spring+EhCache缓存实例
转载来自:http://www.cnblogs.com/mxmbk/articles/5162813.html 一.ehcahe的介绍 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干 ...
-
Hibernate+EhCache配置二级缓存
步骤: 第一步:加入ehcache.jar 第二步: 在src目录下新建一个文件,名为:ehcache.xml 第三步:在hibernate配置文件的<session-factory>下配 ...
-
【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(五)
SpringSecurity(2) 好久没有写了,之前只写了一半,我是一边开发一边写Blog一边上班,所以真心没有那么多时间来维护Blog,项目已经开发到编写逻辑及页面部分了,框架基本上已经搭建好不会 ...
-
(转)springMVC+mybatis+ehcache详细配置
一. Mybatis+Ehcache配置 为了提高MyBatis的性能,有时候我们需要加入缓存支持,目前用的比较多的缓存莫过于ehcache缓存了,ehcache性能强大,而且位各种应用都提供了解决方 ...
-
ehcache注解全面解析---打酱油的日子
通过ehcache以编程方式使用缓存: 跟上面的方式相同,但是缓存通过ehcache去管理,当然比使用map有N多种好处,比如缓存太大了快达到上限之后,将哪一部分缓存清除出去.这种方式完全是通过代码的 ...
-
spring mvc + ehcache 利用注解实现缓存功能
我的spring是3.1的,因为项目需求,需要在查询时候加上缓存,小白一个,完全没有用过缓存(ehcache),摸索了一天终于会了一点通过注解来使用ehcache进行缓存,立刻给记录下来. 首先 我的 ...
随机推荐
-
javascrip中cookie的使用详细分析
JavaScript中的另一个机制:cookie,则可以达到真正全局变量的要求. cookie是浏览器 提供的一种机制,它将document 对象的cookie属性提供给JavaScript.可以由J ...
-
JavaScript之父谈JavaScript
本文翻译自popularity,为了更好的阅读我把部分内容进行了增删改,如果你英语比较好,建议直接阅读原文,因为这篇文章是我通过google翻译再进行修改的. 貌似(根据一位精神导师的说法)JavaS ...
-
Scala编程入门---Map与Tuple
创建Map //创建一个不可变的Map val ages = Map("Leo" -> 30,"Jen" ->25,"Jack" ...
-
FtpHelper实现ftp服务器文件读写操作(C#)
最近做了一个项目,需要读取ftp服务器上的文件,于是参考了网上提供的一些帮组方法,使用过程中,出现一些小细节问题,于是本人做了一些修改,拿来分享一下 using System; using Syste ...
-
java.lang.NumberFormatException: Infinite or NaN
1.异常提示: java.lang.NumberFormatException: Infinite or NaN 2.原因:无法格式化的数字,此数字要么不是个数字,要么是无穷大的数字,从而导致 B ...
-
location位置操作
使用location对象可以通过很多方式来改变浏览器的位置. location.assign('http://www.klkx.com') 传入一个URL地址 这样可以立即打开一个新的URL并在浏览器 ...
-
P3195 [HNOI2008]玩具装箱TOY
列出DP方程式:设f[i]表示分组完前i件物品的最小花费,为方便计算,设sum[i]表示是前i件物品的长度和. f[i]=min(f[j]+(sum[i]-sum[j]+i-j-L-1)^2) [0& ...
-
2015 ICL, Finals, Div. 2【ABFGJK】
[题外话:我......不补了......] 2015 ICL, Finals, Div. 2:http://codeforces.com/gym/100637 G. #TheDress[水] (st ...
-
构建高可靠hadoop集群之3- Quorum Journal Manager
在正式环境中,搭建高可靠(ha)的系统是必须的. 例如oralce的rac,apache集群,windows服务器集群 本文不再赘言ha的重要性. 本文主要是对 http://hadoop.apach ...
-
【Java】Java_08 字符型与布尔值
1.字符型(2个字节) 单引号用来表示字符常量.例如‘A’是一个字符,它与“A”是不同的,“A”表示含有一个字符的字符串 char 类型用来表示在Unicode编码表中的字符 Unicode编码被设计 ...