ehcache详细解读

原文:
https://blog.csdn.net/u014209975/article/details/53320395

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

1.快速轻量

过去几年,众多的测试表明ehcache是最快的java缓存之一

ehcache的线程机制是为大型高并发系统设计的

大量性能测试用例保证ehcache在不同版本间性能表现的一致性

很多用户都不知道他们正在用ehcache,因此不需要什么特别的配置

api易于使用,这就很容易部署上线和运行

2.伸缩性

缓存在内存和硬盘存储可以伸缩到数G,ehcache为大数据存储做过优化

大内存的情况下,所有进程可以支持数百G的吞吐

为高并发和大型多CPU服务器做优化

线程安全和性能总是一些矛盾,ehcache的线程机制设计采用了Doug lea的想法来获得较高的性能

单台虚拟机上支持多缓存管理器

通过Terracotta服务器矩阵,可以伸缩到数百个节点

3.灵活性

ehcache具备对象api接口和可序列化api接口

不能序列化的对象可以使用出磁盘存储外ehcache的所有功能

除了元素的返回方法以外,api都是统一的,只有这2个方法不一致:getObjectValue和getKeyValue。这就使得缓存对象,序列化对象来获取新的特性这个过程简单

支持基于Cache和基于Element的过期策略,每个Cache的存活时间都是可以设置和控制的。
提供了LRU、LFU和FIFO缓存淘汰算法,Ehcache 1.2引入了最少使用和先进先出缓存淘汰算法,构成了完整的缓存淘汰算法。
提供内存和磁盘存储,Ehcache和大多数缓存解决方案一样,提供高性能的内存和磁盘存储。
动态、运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目都是可以在运行时修改的。

4.标准支持

5.可扩展性

监听器可以插件化.ehcache1.2提供了cacheManageEventListener和cacheEventListener接口,实现了可插件化。并且可以在ehcache。xml配置

节点发现,冗余器和监听器都可以插件化

6.应用持久化

在vm重启后,持久化到磁盘的存储可以复原数据

Ehcache是第一个引入缓存数据持久化存储的开源java缓存框架,缓存的数据可以在机器重启后从磁盘上重新获得

根据需要将缓存刷到磁盘。将缓存条目刷到磁盘的操作可以通过cache.fiush方法执行,这大大方便了ehcache的使用

7、监听器
缓存管理器监听器。允许注册实现了CacheManagerEventListener接口的监听器:
notifyCacheAdded()
notifyCacheRemoved()
缓存事件监听器。允许注册实现了CacheEventListener接口的监听器,它提供了许多对缓存事件发生后的处理机制:
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的结构设计概览:


image.png

三、核心定义:

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

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

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

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

代码示例:

CacheManager manager = CacheManager.newInstance("src/config/ehcache.xml");  
manager.addCache("testCache");  
Cache test = singletonManager.getCache("testCache");  
test.put(new Element("key1", "value1"));  
manager.shutdown(); 

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

Cache testCache = new Cache(  
  new CacheConfiguration("testCache", maxElements)  
    .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)  
    .overflowToDisk(true)  
    .eternal(false)  
    .timeToLiveSeconds(60)  
    .timeToIdleSeconds(30)  
    .diskPersistent(false)  
    .diskExpiryThreadIntervalSeconds(0));

事务的例子:

Ehcache cache = cacheManager.getEhcache("xaCache");  
transactionManager.begin();  
try {  
    Element e = cache.get(key);  
    Object result = complexService.doStuff(element.getValue());  
    cache.put(new Element(key, result));  
    complexService.doMoreStuff(result);  
    transactionManager.commit();  
} catch (Exception e) {  
    transactionManager.rollback();  
}  

四、一致性模型

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

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

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

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

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

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

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

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

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

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

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

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

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

<cache name="myCache"  
     maxElementsInMemory="500"  
     eternal="false"  
     overflowToDisk="false"  
   <terracotta clustered="true" consistency="strong" />  
</cache> 

但是使用UnlockedReadsView:

Cache cache = cacheManager.getEhcache("myCache");  
UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache");

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

cache.putIfAbsent(Element element);  
cache.replace(Element oldOne, Element newOne);  
cache.remove(Element);  

五、缓存拓扑类型:

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

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

逻辑视角:


image.png

组网视角:


image.png

模型存储视角:
image.png

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

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

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

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

image.png

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

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


image.png

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


image.png

无论上面哪个模式,更新事件又可以分为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中。

public V readSomeData(K key)   
{  
   Element element;  
   if ((element = cache.get(key)) != null) {  
       return element.getValue();  
   }  
   if (value = readDataFromDataStore(key)) != null) {  
       cache.put(new Element(key, value));  
   }   
   return value;  
}  

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级别)的缓存大小获取,避免单独计算缓存大小的消耗
灵活的独立基于层的大小计算能力,下图中可以看到,不同层的大小都是可以单独控制的
可以统计字节大小、缓存条目数和百分比
优化高命中数据的获取,以提升性能,参见下面对缓存数据在不同层之间的流转的介绍

image.png

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 原文连接:https://my.oschina.net/coolfire368/blog/123377 ehcac...
    晴天哥_王志阅读 1,343评论 0 1
  • Ehcache是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,最初知道它,是从Hibernat...
    安易学车阅读 2,028评论 0 11
  • 理论总结 它要解决什么样的问题? 数据的访问、存取、计算太慢、太不稳定、太消耗资源,同时,这样的操作存在重复性。因...
    jiangmo阅读 2,842评论 0 11
  • 商城APP开发的购物流程主要步骤: 第一步:商品资源信息展示,包括活动页面商品展示、列表页、搜索框、店铺/商品收藏...
    Crazy2015阅读 1,218评论 0 2
  • 那些主要是敛财为目的,打出夸张旗号,实际内容空洞无物(甚至有害)的网络营销培训班,和新型传销只隔了一个遮羞布。——...
    巴图鲁阅读 536评论 27 18