博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
缓存插件 EHCache
阅读量:4306 次
发布时间:2019-06-06

本文共 12868 字,大约阅读时间需要 42 分钟。

 

EHCache是来自sourceforge()的开源项目,也是纯Java实现的简单、快速的Cache组件。

下载jar包

 

Ehcache 对象、数据缓存:http://ehcache.org/downloads/destination?name=ehcache-core-2.5.2-distribution.tar.gz&bucket=tcdistributions&file=ehcache-core-2.5.2-distribution.tar.gzWeb页面缓存:http://ehcache.org/downloads/destination?name=ehcache-web-2.0.4-distribution.tar.gz&bucket=tcdistributions&file=ehcache-web-2.0.4-distribution.tar.gz

 

需要添加如下jar包到lib目录下

ehcache-core-2.5.2.jarehcache-web-2.0.4.jar 主要针对页面缓存

 当前工程的src目录中加入配置文件

ehcache.xmlehcache.xsd这些配置文件在ehcache-core这个jar包中可以找到

 

EHCache支持内存和磁盘的缓存,支持LRU、LFU和FIFO多种淘汰算法,支持分布式的Cache,可以作为Hibernate/Spring等技术的缓存插件。同时它也能提供基于Filter的Cache,该Filter可以缓存响应的内容并采用Gzip压缩提高响应速度。

 

 

EHCache API的基本用法

  首先介绍CacheManager类。它主要负责读取配置文件,默认读取CLASSPATH下的ehcache.xml,根据配置文件创建并管理Cache对象。

// 使用默认配置文件创建CacheManagerCacheManager manager = CacheManager.create();// 通过manager可以生成指定名称的Cache对象Cache cache = cache = manager.getCache("demoCache");// 使用manager移除指定名称的Cache对象manager.removeCache("demoCache");//可以通过调用manager.removalAll()来移除所有的Cache。//通过调用manager的shutdown()方法可以关闭CacheManager。

 

 

有了Cache对象之后就可以进行一些基本的Cache操作,例如:

//往cache中添加元素Element element = new Element("key", "value");cache.put(element);//从cache中取回元素Element element = cache.get("key");element.getValue();//从Cache中移除一个元素cache.remove("key");

 

可以直接使用上面的API进行数据对象的缓存,这里需要注意的是对于缓存的对象都是必须可序列化的。

在下面的篇幅中还会介绍EHCache和Spring、Hibernate的整合使用。

 

 

配置文件介绍

配置文件ehcache.xml中命名为demoCache的缓存配置:

 

各配置参数的含义:

maxElementsInMemory:缓存中允许创建的最大对象数eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是 0 就意味着元素可以停顿无穷长的时间。timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。overflowToDisk:内存不足时,是否启用磁盘缓存。memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。LRU和FIFO算法这里就不做介绍。LFU算法直接淘汰使用比较少的对象,在内存保留的都是一些经常访问的对象。对于大部分网站项目,该算法比较适用。

 

 

如果应用需要配置多个不同命名并采用不同参数的Cache,可以相应修改配置文件,增加需要的Cache配置即可。

 

 

利用Spring APO整合EHCache

首先,在CLASSPATH下面放置ehcache.xml配置文件。在Spring的配置文件中先添加如下cacheManager配置:

配置demoCache:

demoCache

 

接下来,写一个实现org.aopalliance.intercept.MethodInterceptor接口的拦截器类。有了拦截器就可以有选择性的配置想要缓存的 bean 方法。如果被调用的方法配置为可缓存,拦截器将为该方法生成 cache key 并检查该方法返回的结果是否已缓存。如果已缓存,就返回缓存的结果,否则再次执行被拦截的方法,并缓存结果供下次调用。具体代码如下:

public class MethodCacheInterceptor implements MethodInterceptor,InitializingBean {  private Cache cache;  public void setCache(Cache cache) {    this.cache = cache;  }  public void afterPropertiesSet() throws Exception {    Assert.notNull(cache,"A cache is required. Use setCache(Cache) to provide one.");  }  public Object invoke(MethodInvocation invocation) throws Throwable {    String targetName = invocation.getThis().getClass().getName();    String methodName = invocation.getMethod().getName();    Object[] arguments = invocation.getArguments();    Object result;    String cacheKey = getCacheKey(targetName, methodName, arguments);    Element element = null;    synchronized (this){      element = cache.get(cacheKey);      if (element == null) {        //调用实际的方法        result = invocation.proceed();        element = new Element(cacheKey, (Serializable) result);        cache.put(element);      }    }    return element.getValue();  }  private String getCacheKey(String targetName, String methodName,Object[] arguments) {    StringBuffer sb = new StringBuffer();    sb.append(targetName).append(".").append(methodName);    if ((arguments != null) && (arguments.length != 0)) {      for (int i = 0; i < arguments.length; i++) {        sb.append(".").append(arguments[i]);      }    }    return sb.toString();  }}

 

synchronized (this)这段代码实现了同步功能。为什么一定要同步?Cache对象本身的get和put操作是同步的

如果我们缓存的数据来自数据库查询,在没有这段同步代码时,当key不存在或者key对应的对象已经过期时,在多线程并发访问的情况下,许多线程都会重新执行该方法,由于对数据库进行重新查询代价是比较昂贵的,而在瞬间大量的并发查询,会对数据库服务器造成非常大的压力。所以这里的同步代码是很重要的。

 

 

接下来,继续完成拦截器和Bean的配置:

  
    
  
  
    
  
  
    
      
.*myMethod
    
  
  
    
  
  
    
      
methodCachePointCut
    
  

 

 

其中myServiceBean是实现了业务逻辑的Bean,里面的方法myMethod()的返回结果需要被缓存。这样每次对myServiceBean的myMethod()方法进行调用,都会首先从缓存中查找,其次才会查询数据库。使用AOP的方式极大地提高了系统的灵活性,通过修改配置文件就可以实现对方法结果的缓存,所有的对Cache的操作都封装在了拦截器的实现中。

 

 

CachingFilter功能

  使用Spring的AOP进行整合,可以灵活的对方法的的返回结果对象进行缓存。

  CachingFilter功能可以对HTTP响应的内容进行缓存

  这种方式缓存数据的粒度比较粗,例如缓存整张页面。它的优点是使用简单、效率高,缺点是不够灵活,可重用程度不高。

  EHCache使用SimplePageCachingFilter类实现Filter缓存。该类继承自CachingFilter,有默认产生cache key的calculateKey()方法,该方法使用HTTP请求的URI和查询条件来组成key。也可以自己实现一个Filter,同样继承CachingFilter类,然后覆写calculateKey()方法,生成自定义的key。
  在笔者参与的项目中很多页面都使用AJAX,为保证JS请求的数据不被浏览器缓存,每次请求都会带有一个随机数参数i。如果使用SimplePageCachingFilter,那么每次生成的key都不一样,缓存就没有意义了。这种情况下,我们就会覆写calculateKey()方法。

 

要使用SimplePageCachingFilter,首先在配置文件ehcache.xml中,增加下面的配置:

 

其中name属性必须为SimplePageCachingFilter,修改web.xml文件,增加一个Filter的配置:

  
SimplePageCachingFilter
  
net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter
  
SimplePageCachingFilter
  
/test.jsp

 

 

下面我们写一个简单的test.jsp文件进行测试,缓存后的页面每次刷新,在600秒内显示的时间都不会发生变化的。代码如下:

<%out.println(new Date());%>

 

 

   CachingFilter输出的数据会根据浏览器发送的Accept-Encoding头信息进行Gzip压缩。经过笔者测试,Gzip压缩后的数据量是原来的1/4,速度是原来的4-5倍,所以缓存加上压缩,效果非常明显。

 

在使用Gzip压缩时,需注意两个问题:

1. Filter在进行Gzip压缩时,采用系统默认编码,对于使用GBK编码的中文网页来说,需要将操作系统的语言设置为:zh_CN.GBK,否则会出现乱码的问题。2. 默认情况下CachingFilter会根据浏览器发送的请求头部所包含的Accept-Encoding参数值来判断是否进行Gzip压缩。虽然IE6/7浏览器是支持Gzip压缩的,但是在发送请求的时候却不带该参数。为了对IE6/7也能进行Gzip压缩,可以通过继承CachingFilter,实现自己的Filter,然后在具体的实现中覆写方法acceptsGzipEncoding。

 

 

 

 

具体实现参考:

protected boolean acceptsGzipEncoding(HttpServletRequest request) {final boolean ie6 = headerContains(request, "User-Agent", "MSIE 6.0");final boolean ie7 = headerContains(request, "User-Agent", "MSIE 7.0");return acceptsEncoding(request, "gzip") || ie6 || ie7;}

 

 

 

 

EHCache在Hibernate中的使用

EHCache可以作为Hibernate的二级缓存使用。在hibernate.cfg.xml中需增加如下设置:

  org.hibernate.cache.EhCacheProvider

 

然后在Hibernate映射文件的每个需要Cache的Domain中,加入类似如下格式信息:

 

比如:

 

最后在配置文件ehcache.xml中增加一段cache的配置,其中name为该domain的类名。

 

 

 

 EHCache的监控

  对于Cache的使用,除了功能,在实际的系统运营过程中,我们会比较关注每个Cache对象占用的内存大小和Cache的命中率。有了这些数据,我们就可以对Cache的配置参数和系统的配置参数进行优化,使系统的性能达到最优。EHCache提供了方便的API供我们调用以获取监控数据,其中主要的方法有:

 

//得到缓存中的对象数cache.getSize();//得到缓存对象占用内存的大小cache.getMemoryStoreSize();//得到缓存读取的命中次数cache.getStatistics().getCacheHits()//得到缓存读取的错失次数cache.getStatistics().getCacheMisses()

 

 

 

分布式缓存

EHCache从1.2版本开始支持分布式缓存。分布式缓存主要解决集群环境中不同的服务器间的数据的同步问题。具体的配置如下:

 

在配置文件ehcache.xml中加入

 

另外,需要在每个cache属性中加入

 

例如:

 

简单完整例子: 

测试环境: 

MySQL 5.0.22, jdk1.6.0_07, ehcache-1.6.0-beta2, mysql-connector-java-3.1.14

测试表:

CREATE TABLE TEST(TEST_ID BIGINT,TEST_NAME VARCHAR(50),TEST_TIME TIMESTAMP,TEST_VALUE DECIMAL(10, 3));

支持类:

public class Util {    public static Random rand = new Random();    public static String atoz = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";    public static String genString(int length) {        StringBuilder re = new StringBuilder(length);        re.append(atoz.charAt(rand.nextInt(52)));        for (int i = 0; i < length; i++) {            re.append(atoz.charAt(rand.nextInt(62)));        }        return re.toString();    }    public static double genDouble() {        double d1 = 5120 * rand.nextDouble();        double d2 = 1024000 * rand.nextDouble();        return d1 + d2;    }}

 

 插入测试数据:

public static void traditionalInsert(int total) throws Exception {        Thread.sleep(3000);        Timestamp current = new Timestamp(System.currentTimeMillis());        String currentStr = dateFormat.format(current);        System.out.println(currentStr);        Connection conn = DriverManager.getConnection(dbURL, user, pass);        try {            long begin = System.currentTimeMillis();            conn.setAutoCommit(false);            String sql = "INSERT INTO TEST (TEST_ID,TEST_NAME,TEST_TIME,TEST_VALUE) VALUES (?, ?, ?, ?)";            PreparedStatement ps = conn.prepareStatement(sql);            for (int i = 1; i <= total; i++) {                ps.setLong(1, i);                ps.setString(2, Util.genString(33));                ps.setTimestamp(3, current);                ps.setBigDecimal(4, new BigDecimal(Util.genDouble()));                ps.addBatch();                if ((i % 500) == 0) {                    ps.executeBatch();                }            }            ps.executeBatch();            conn.commit();            long end = System.currentTimeMillis();            System.out.printf("Count:%d Time:%d\n", total, (end - begin));        } catch (Exception ex) {            ex.printStackTrace();            conn.rollback();        } finally {            conn.close();        }    }

 

使用的javaBean:

public class TEST implements Serializable {    private static final long serialVersionUID = 1L;    public Long TEST_ID;    public String TEST_NAME;    public Timestamp TEST_TIME;    public BigDecimal TEST_VALUE;    @Override    public String toString() {        return String.format("ID:%s,,,NAME:%s", TEST_ID, TEST_NAME);    }}

 

先试一下缓存到字典中:

public static HashMap
simpleCache() throws Exception { HashMap
cacheid = new HashMap
(); Class.forName(dbDriver); Connection conn = DriverManager.getConnection(dbURL, user, pass); try { long begin = System.currentTimeMillis(); Statement s = conn.createStatement(); String sql = "SELECT TEST_ID,TEST_NAME,TEST_TIME,TEST_VALUE FROM TEST"; ResultSet querySet = s.executeQuery(sql); for (int i = 1; querySet.next(); i++) { TEST curr = new TEST(); curr.TEST_ID = querySet.getLong(1); curr.TEST_NAME = querySet.getString(2); curr.TEST_TIME = querySet.getTimestamp(3); curr.TEST_VALUE = querySet.getBigDecimal(4); cacheid.put(curr.TEST_ID, curr); } long end = System.currentTimeMillis(); System.out.printf("Time:%d\n", (end - begin)); } catch (Exception ex) { ex.printStackTrace(); } finally { conn.close(); } return cacheid; }

 

缓存到字典中,写法比较简单,使用方便,缺点就是缓存数据量比较少,一般缓存10W就有可能把jvm的缓存给占完了。用ehcache就可以解决缓存数据太少的问题。

一个简单的配置:  

 Cache配置中的几个属性: 

name:Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)。 maxElementsInMemory:内存中保持的对象数量。 maxElementsOnDisk:DiskStore中保持的对象数量,默认值为0,表示不限制。 eternal:是否是永恒数据,如果是,则它的超时设置会被忽略。 overflowToDisk:如果内存中数据数量超过maxElementsInMemory限制,是否要缓存到磁盘上。 timeToIdleSeconds:对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。 timeToLiveSeconds:对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。 diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。 diskExpiryThreadIntervalSeconds:对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。 diskSpoolBufferSizeMB:DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。 memoryStoreEvictionPolicy:如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。

 

 

获取配置中的demoCache:

CacheManager manager = CacheManager.create("ehcache.xml");Cache demo = manager.getCache("demoCache");

 

往cache中加入数据:

public static void ehcache() throws Exception {        CacheManager manager = CacheManager.create("ehcache.xml");        manager.addCache("TEST_ID.TEST");        Cache cid = manager.getCache("TEST_ID.TEST");        Class.forName(dbDriver);        Connection conn = DriverManager.getConnection(dbURL, user, pass);        try {            long begin = System.currentTimeMillis();            Statement s = conn.createStatement();            String sql = "SELECT TEST_ID,TEST_NAME,TEST_TIME,TEST_VALUE FROM TEST";            ResultSet querySet = s.executeQuery(sql);            for (int i = 1; querySet.next(); i++) {                TEST curr = new TEST();                curr.TEST_ID = querySet.getLong(1);                curr.TEST_NAME = querySet.getString(2);                curr.TEST_TIME = querySet.getTimestamp(3);                curr.TEST_VALUE = querySet.getBigDecimal(4);                cid.put(new Element(curr.TEST_ID, curr));            }            long end = System.currentTimeMillis();            System.out.printf("Time:%d\n", (end - begin));        } catch (Exception ex) {            ex.printStackTrace();        } finally {            conn.close();        }    }

 

这里在CacheManager中直接加入了一个叫TEST_ID.TEST的cache。因为只给了一个名字,所以系统会把defaultCache的设置给它clone一份。 

使用方法,像字典一样使用就行:

Cache cid = manager.getCache("TEST_ID.TEST");Element e5120 = cid.get(new Long(5120));System.out.println(e5120.getValue());

ehcache中数据是以java对象的形式存在的,使用了java的序列化保存到磁盘,所以保存的对象要实现Serializable接口。ehcache还可以支持分布式缓存。

 

 

 

总结

EHCache是一个非常优秀的基于Java的Cache实现。它简单、易用,而且功能齐全,并且非常容易与Spring、Hibernate等流行的开源框架进行整合。通过使用EHCache可以减少网站项目中数据库服务器的访问压力,提高网站的访问速度,改善用户的体验。

 

转载于:https://www.cnblogs.com/hwaggLee/p/4443127.html

你可能感兴趣的文章
必学高级SQL语句
查看>>
经典SQL语句大全
查看>>
log日志记录是什么
查看>>
<rich:modelPanel>标签的使用
查看>>
<h:commandLink>和<h:inputLink>的区别
查看>>
<a4j:keeyAlive>的英文介绍
查看>>
关于list对象的转化问题
查看>>
VOPO对象介绍
查看>>
suse创建的虚拟机,修改ip地址
查看>>
linux的挂载的问题,重启后就挂载就没有了
查看>>
docker原始镜像启动容器并创建Apache服务器实现反向代理
查看>>
docker容器秒死的解决办法
查看>>
管理网&业务网的一些笔记
查看>>
openstack报错解决一
查看>>
openstack报错解决二
查看>>
linux source命令
查看>>
openstack报错解决三
查看>>
乙未年年终总结
查看>>
子网掩码
查看>>
第一天上班没精神
查看>>