【JAVA】缓存解读之EHcache

目录

绪论

众所周知,在吞吐量需求越来越大的web运用中,缓存无非是最大的一块需要攻略的地方之一,目前流行的cache中有Ehcache,OScache,JBoss Cache以及Memcached.其中OScache功能强大,但是在并发量较高时,OsCache会出现线程阻塞和数据错误,通过分析源代码发现是其内部实现的缺陷;JBossCache最大的优点是支持基于对象属性的集群同步,不过JBossCache的配置使用都较复杂,在并发量较高的情况下,对象属性数据在集群中同步也会加大系统的开销;Memcached做统一缓存,用起来很爽,但是数据是保存在内存当中的,一旦服务进程重启,数据会全部丢失; Ehcache是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,所以,在此我就只拿Ehcache进行解析。

接触

最早接触Ehcache是在hibernate中,只是为了使用二级缓存的时候用了下,然而却没有深究下去。最近在看葛一鸣老师的《Java程序性能优化——让你的java程序更快更稳定》中提到了这么一节,于是借此深入下去看了下。

Ehcache的特性

EhCache非常简单,而且易用,是一个非常轻量级的缓存实现,而且从1.2 之后就支持了集群。Ehcache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。Ehcache可以直接使用。也可以和Hibernate ORM框架结合使用。还可以做Servlet缓存。Cache 存储方式 :内存或磁盘。它具有以下几个特性:

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

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

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

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

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

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

  • 监听器
    缓存管理器监听器。允许注册实现了CacheManagerEventListener接口的监听器:notifyCacheAdded()、notifyCacheRemoved()
    缓存事件监听器。允许注册实现了CacheEventListener接口的监听器,它提供了许多对缓存事件发生后的处理机制:notifyElementRemoved/Put/Updated/Expired

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

  • 分布式缓存
    从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都已经经过了测试。

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

  • 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的事务支持,支持事务资源管理,二阶段提交和回滚,以及本地事务。

  • 开源协议
    Apache 2.0 license

Ehcache配置

在classpath路径下新建ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
   <!--<diskStore path="java.io.tmpdir"/> -->
   <diskStore path="data/ehcache" /> <!-- 默认cache模板-->
   <defaultCache 
      <!-- 该缓存允许存放的最大条目数量-->
      maxElementsInMemory="10000"  
      <!--缓存内容是否永久缓存-->
       eternal="false"
      <!--如果不是永久缓存,则超过该时间未被访问,则删除该缓存条目-->
       timeToIdleSeconds="10" 
      <!--如果不是永久缓存,一个缓存条目存在的最长时间-->
       timeToLiveSeconds="120"
    <!-- 如果内存中的数据超过maxElementsInMemory,是否使用磁盘-->
       overflowToDisk="true" 
      <!-- 磁盘存储的条目是否永久保存-->
       diskPersistent="false"  
      <!-- 磁盘清理线程的运行时间间隔-->
       diskExpiryThreadIntervalSeconds="120" />

   <cache name="cache1" 
      maxElementsInMemory="100" 
      eternal="false"
      timeToIdleSeconds="10" 
      timeToLiveSeconds="60" 
      overflowToDisk="true"
      diskPersistent="false" />

   <cache name="cache2" 
       maxElementsInMemory="100000" 
       eternal="false"
       timeToIdleSeconds="300" 
       timeToLiveSeconds="600" 
       overflowToDisk="false"
       diskPersistent="false" />
 </ehcache>

Ehcache代码实现

新建EHCacheUtil.java工具类

public class EHCacheUtil 
{
    static CacheManager manager =null;
    static String configurationFile = "./ehcache.xml";
    static{
    //读取配置文件,构造CahceMananger
     manager = CacheManager.create(EHCacheUtil.class.getClassLoader().getResourceAsStream(configurationFile));
    }
    //写入缓存
    public static void put(String cachename,Serializable key,Serializable value){
        manager.getCache(cachename).put(new Element(key,value));
    }
    //读取缓存
    public static Serializable get(String cachename,Serializable key){
        Element e = manager.getCache(cachename).get(key);
        if(e==null){
            return null;
        }else{
            return e.getValue();
        }
    }
    //清除cachename缓存区所有缓存
    public static void clearcache(String cachename){
        manager.getCache(cachename).removeAll();
    }
    //清除cachename中key条目的缓存
    public static void remove(String cachename,String key){
        manager.getCache(cachename).remove(key);
    }
    //关闭缓存。清除所以缓存
    public static void shutdown(){
        manager.shutdown();
    }
}

测试代码

  • 当timeToIdleSeconds和timeToLiveSeconds足够大时,程序读取缓存,都可以找到
public class AppTest extends TestCase
{
    public AppTest( String testName )
    {
        super( testName );
    }
    public static Test suite()
    {
        return new TestSuite( AppTest.class );
    }
    public void testApp()
    {
        assertTrue( true );
    }
    public void test1() throws InterruptedException{
        EHCacheUtil.put("cache1", "key1", "value1");
        System.out.println("开始:"+EHCacheUtil.get("cache1", "key1"));
        for(int i=0;i<6;i++){
          System.out.println("i="+i+" "+EHCacheUtil.get("cache1", "key1"));
          Thread.sleep(1000);
        }
        System.out.println("结束:"+EHCacheUtil.get("cache1", "key1"));
    }
}

运行结果

开始:value1
i=0 value1
i=1 value1
i=2 value1
i=3 value1
i=4 value1
i=5 value1
结束:value1
  • 当timeToIdleSeconds失效,timeToLiveSeconds足够大时,程序读取缓存只有在访问时间间隔内可以读取到值
    将ehcache.xml中,name=”cache1”的配置timeToIdleSeconds=”3”,timeToLiveSeconds=”60”。
    将测试代码修改为:
 public void test1() throws InterruptedException{
        EHCacheUtil.put("cache1", "key1", "value1");
        System.out.println("开始:"+EHCacheUtil.get("cache1", "key1"));
        for(int i=0;i<6;i++){
          if(i<3){
               System.out.println("i="+i+" "+EHCacheUtil.get("cache1", "key1"));
          }
          Thread.sleep(1000);
        }
        System.out.println("结束:"+EHCacheUtil.get("cache1", "key1"));
    }

运行结果
开始:value1
i=0 value1
i=1 value1
i=2 value1
结束:null
  • 当timeToIdleSeconds足够大,timeToLiveSeconds失效时,程序读取缓存只有在生存时间内可以读取到值
    将ehcache.xml中,name=”cache1”的配置timeToIdleSeconds=”60”,timeToLiveSeconds=”3”。
    将测试代码修改为:
 public void test1() throws InterruptedException{
        EHCacheUtil.put("cache1", "key1", "value1");
        System.out.println("开始:"+EHCacheUtil.get("cache1", "key1"));
        for(int i=0;i<6;i++){
         System.out.println("i="+i+" "+EHCacheUtil.get("cache1", "key1"));
          Thread.sleep(1000);
        }
        System.out.println("结束:"+EHCacheUtil.get("cache1", "key1"));
    }

运行结果
开始:value1
i=0 value1
i=1 value1
i=2 value1
i=3 null
i=4 null
i=5 null
结束:null
  • 当eternal=”true” timeToIdleSeconds,timeToLiveSeconds不管是否有效,程序读取缓存都可以读取到值
    将ehcache.xml中,name=”cache1”的配置eternal=”true” ,timeToIdleSeconds=”3”,timeToLiveSeconds=”3”。测试代码如上不变。
运行结果
开始:value1
i=0 value1
i=1 value1
i=2 value1
i=3 value1
i=4 value1
i=5 value1
结束:value1

结论

当配置了eternal=”false”,timeToIdleSeconds,timeToLiveSeconds取最小值内有效
当配置了eternal=”true”为永久有效,和timeToIdleSeconds,timeToLiveSeconds无关

在 Hibernate 中运用EHCache

  • hibernate.cfg.xml中需设置如下:
<property name=" hibernate.cache.provider_class">
    org.hibernate.cache.EhCacheProvider
 </property>

EhCacheProvider类位于hibernate3.jar
2.1版本加入
net.sf.ehcache.hibernate.Provider
2.1以下版本加入
net.sf.hibernate.cache.EhCache
在Hibernate3.x中的etc目录下有ehcache.xml的示范文件,将其复制应用程序的src目录下(编译时会把ehcache.xml复制到WEB-INF/classess目录下),对其中的相关值进行更改以和自己的程序相适合**。

  • 持久化类的映射文件进行配置
 <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的结构设计概览

cache manager:缓存管理器,以前是只允许单例的,不过现在也可以多实例了
cache:缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口
element:单条缓存数据的组成单位
system of record(SOR):可以取到真实数据的组件,可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等等,缓存就是从SOR中读取或者写入到SOR中去的。


参考资料

[1]: ehCache的使用 http://blog.sina.com.cn/s/blog_647a492a0101cziu.html
[2]: Ehcache详细解读 http://www.blogjava.net/libin2722/articles/406569.html
[3]:缓存之EHCache(一) http://blog.csdn.net/l271640625/article/details/20528573

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值