ehcache

一、简介

ehcache是一个比较成熟的java缓存框架,它提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案。

ehcache最早从hibernate发展而来。由于3.x的版本和2.x的版本API差异比较大。这里直接学习最新版本的了,但是最后整合spring的时候还是有2.x。

二、特性

1、快速轻量:不需要特别的配置,API易于使用,很容易部署上线和运行。很小的jar包,Ehcache 2.2.3才668kb。唯一的依赖就是SLF4J了。

2、伸缩性:缓存在内存和磁盘存储可以伸缩到数G,Ehcache为大数据存储做过优化。大内存的情况下,所有进程可以支持数百G的吞吐。为高并发和大型多CPU服务器做优化。

线程安全和性能总是一对矛盾,Ehcache的线程机制采用了Doug Lea的想法来获得较高的性能。单台虚拟机上支持多缓存管理器。通过Terracotta服务器矩阵,可以伸缩到数百个节点。

3、灵活性:提供了LRU、LFU和FIFO缓存淘汰算法,提供内存和磁盘存储;动态、运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目都是可以在运行时修改。

4、可扩展性:监听器可以插件化。

5、应用持久化:在JVM重启后,持久化到磁盘的存储可以复原数据(缓存的数据可以在机器重启后从磁盘上重新获得)。

6、分布式缓存:通过Terracotta的缓存集群,设定和使用Terracotta模式的Ehcache缓存。使用RMI、JGroups或者JMS来冗余缓存数据。

三、如何使用(Spring与Ehcache整合)

1、ehcache.xml配置

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

<diskStore path="java.io.tmpdir" /><!-- 指定一个文件目录,当EhCache把数据写到硬盘上时,将把数据写到这个文件目录下 -->

<!-- 设定缓存的默认数据过期策略 -->
<defaultCache maxElementsInMemory="10000" eternal="false"
overflowToDisk="true" timeToIdleSeconds="10" timeToLiveSeconds="20"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />
<!-- name:缓存名称 -->

<!-- maxElementsInMemory:内存中最大缓存对象数 -->

<!-- maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大 -->

<!-- eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false -->

<!-- overflowToDisk:true表示当内存缓存的对象数目达到了 maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。-->

<!-- diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。-->

<!-- diskPersistent:是否缓存虚拟机重启期数据,是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名 为cache名称,后缀名为index的文件,这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存,要想把 cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法。-->

<!-- diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒 -->

<!-- timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性 值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限 期地处于空闲状态 -->

<!-- timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有 效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有 意义 -->

<!-- memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。-->

<cache name="serviceCache"
eternal="false"
maxElementsInMemory="100"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU" />

</ehcache>

2、applicationContext.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-3.1.xsd">

<!-- 自动扫描注解的bean -->
<context:component-scan base-package="com.wd.service" />

<!-- 启动缓存注解扫描 -->
<cache:annotation-driven cache-manager="ehCacheCacheManager" />

<!-- 实例CacheManager -->
<bean id="ehCacheCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="ehcache"></property>
</bean>

<!-- 加载ehcache配置文件 -->
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>

</beans>

3、创建一个接口并实现缓存

@Service
public class UserServiceImpl implements UserService {

/**
* @Cacheable
* 表明所修饰的方法是可以缓存的:当第一次调用这个方法时,
* 它的结果会被缓存下来,在缓存的有效时间内。如果每次缓存返回的结果都是一样的,则不会执行方法体的代码
* 以后访问这个方法都直接返回缓存结果,不再执行方法中的代码段。
* @CachePut:功能比@Cacheable更强,不仅缓存方法结果,还缓存代码段,每次都会执行方法体的内容
* @CacheEvict:功能和@Cacheable功能相反,@CacheEvict表明所修饰的方法是用来删除失效或无用的缓存数据。
* @CacheConfig:与前面的缓存注解不同,这是一个类级别的注解。如果类的所有操作都是缓存操作,你可以使用@CacheConfig来指定类,省去一些配置。
* condition:缓存的条件,可以为null,使用spel编写,只有返回true的情况下才进行缓存
* 这个方法会以key中的值作为缓存中的key,返回结果作为缓存中的值进行缓存
*/

@Service("userService")
public class UserService {
  @Autowired
  private UserDao userDao;
  // 查询所有数据,不需要key
  @Cacheable(value = "serviceCache")
  public List<User> getAll(){
    printInfo("getAll");
    return userDao.users;
  }
  //根据ID来获取数据,ID可能是主键也可能是唯一键
  @Cacheable(value = "serviceCache", key="#id")
  public User findById(Integer id){
    printInfo(id);
    return userDao.findById(id);
  }
  //通过ID进行删除
  @CacheEvict(value = "serviceCache", key="#id")
  public void removeById(Integer id){
    userDao.removeById(id);
  }
  //添加数据
  public void addUser(User user){
    if(user != null && user.getId() != null){
      userDao.addUser(user);
    }
  }
  // key 支持条件,包括 属性condition ,可以 id < 20 等等
  @CacheEvict(value="serviceCache", key="#u.id")
  public void updateUser(User u){
    removeById(u.getId());
    userDao.updateUser(u);
  }

  // allEntries 表示调用之后,清空缓存,默认false,
  // 还有个beforeInvocation 属性,表示先清空缓存,再进行查询
  @CacheEvict(value="serviceCache",allEntries=true)
  public void removeAll(){
    System.out.println("清除所有缓存");
  }

  private void printInfo(Object str){
    System.out.println("非缓存查询----------findById"+str);
  }
 }

}

四、Ehcache分布式缓存的使用

从Ehcache1.2版本开始,Ehcache就可以使用分布式的缓存了,从 1.7版本开始,开始支持共五种集群方案,分别是:Terracotta、RMI、JMS、JGroups、EhCache Server。

其中有三种上最为常用集群方式,分别是 RMI、JGroups 以及 EhCache Server 。

其实我们在使用Ehcache分布式缓存的过程中,主要是以缓存插件的方式使用,如果我们想根据自己的需要使用分布式缓存那就需要自己开发来定制化。

在后面我们会发现其实Ehcache提供的分布式缓存并不是非常好用,有不少问题存在,所以对缓存数据一致性比较高的情况下,使用集中式缓存更合适,比如Redis、Memcached等。

ehcache.xml文件的配置增加了集群的内容:

<!--EhCache分布式缓存集群环境配置(使用RMI方式在集群的环境进行缓存数据的复制)-->

<!--rmi手动配置-->

<cacheManagerPeerProviderFactory class= "net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
  properties="peerDiscovery=manual,rmiUrls=//localhost:40000/user"/>
<cacheManagerPeerListenerFactory
  class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
  properties="hostName=localhost,port=40001, socketTimeoutMillis=120000"/>

<cache name="serviceCache"
  maxElementsInMemory="1000"
  eternal="false"
  timeToIdleSeconds="100000"
  timeToLiveSeconds="100000"
  overflowToDisk="false">
<cacheEventListenerFactory
  class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>
</cache>

<!-- RMI方式: 当缓存改变时,ehcache会向组播IP地址和端口号发送RMI UDP组播包。-->

五、Ehcache的使用场景

比较少的更新数据表的情况;

对并发要求不是很严格的情况,多台应用服务器中的缓存是不能进行实时同步的;

对一致性要求不高的情况下,因为Ehcache本地缓存的特性,目前无法很好的解决不同服务器间缓存同步的问题。

所以我们在一致性要求非常高的场合下,尽量使用Redis、Memcached等集中式缓存。

Ehcache在集群、分布式环境下如何同步缓存的(以常用的两种举例):

 

六、Redis和Ehcache联合使用(将Ehcache作为与Redis配合的二级缓存)

1、方式一

通过应用服务器的Ehcache定时轮询Redis缓存服务器更同步更新本地缓存,缺点是因为每台服务器定时Ehcache的时间不一样,那么不同服务器刷新最新缓存的时间也不一样。

会产生数据不一致问题,对一致性要求不高可以使用。

2、方式二

通过引入了MQ队列,使每台应用服务器的Ehcache同步侦听MQ消息,这样在一定程度上可以达到准同步更新数据,通过MQ推送或者拉取的方式。

但是因为不同服务器之间的网络速度的原因,所以也不能完全达到强一致性。基于此原理使用Zookeeper等分布式协调通知组件也是如此。

3、总结

使用二级缓存的好处是减少缓存数据的网络传输开销,当集中式缓存出现故障的时候,Ehcache等本地缓存依然能够支撑应用程序正常使用,增加了程序的健壮性。

另外使用二级缓存策略可以在一定程度上阻止缓存穿透问题。

根据CAP原理我们可以知道,如果要使用强一致性缓存(根据自身业务决定),集中式缓存是最佳选择,如(Redis,Memcached等)。

 七、ehcache和redis比较

redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。

ehcache也有缓存共享方案,是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。

如果是单个应用或者对缓存访问要求很高的应用,用ehcache。

如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

posted @ 2019-07-30 17:23  tt&yy  阅读(281)  评论(0编辑  收藏  举报