一、简介

Ehcache是一个用Java实现的应用简略,高速,实现线程平安的缓存治理类库,ehcache提供了用内存,磁盘文件存储,以及分布式存储形式等多种灵便的cache治理计划。同时ehcache作为凋谢源代码我的项目,采纳限度比拟宽松的Apache License V2.0作为受权形式,被宽泛地用于Hibernate, Spring,Cocoon等其余开源零碎。Ehcache 从 Hibernate 倒退而来,逐步涵盖了 Cahce 界的全副性能,是目前发展势头最好的一个我的项目。具备疾速,简略,低消耗,依赖性小,扩展性强,反对对象或序列化缓存,反对缓存或元素的生效,提供 LRU、LFU 和 FIFO 缓存策略,反对内存缓存和磁盘缓存,分布式缓存机制等等特点。

备注:为了不便大家了最新版本的Ehcache,本文中1-6节采纳的最新的Ehcache3.0的个性和应用介绍,从第7节开始采纳的是Ehcache2.10.2版本来与Spring相结合来做案例介绍,包含前面的源码剖析也将采纳这个版本
 

二、次要个性

疾速;
简略;
多种缓存策略;
缓存数据有两级:内存和磁盘,因而无需放心容量问题;
缓存数据会在虚拟机重启的过程中写入磁盘;
能够通过 RMI、可插入 API 等形式进行分布式缓存;
具备缓存和缓存管理器的侦听接口;
反对多缓存管理器实例,以及一个实例的多个缓存区域;
提供 Hibernate 的缓存实现;

三、Ehcache的架构设计图

  
阐明
CacheManager:是缓存管理器,能够通过单例或者多例的形式创立,也是Ehcache的入口类。
Cache:每个CacheManager能够治理多个Cache,每个Cache能够采纳hash的形式治理多个Element。
Element:用于寄存真正缓存内容的。

结构图如下所示:

四、Ehcache的缓存数据淘汰策略

FIFO:先进先出
LFU:起码被应用,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU:最近起码应用,缓存的元素有一个工夫戳,当缓存容量满了,而又须要腾出中央来缓存新的元素的时候,那么现有缓存元素中工夫戳离以后工夫最远的元素将被清出缓存。

五、Ehcache的缓存数据过期策略

Ehcache采纳的是懒淘汰机制,每次往缓存放入数据的时候,都会存一个工夫,在读取的时候要和设置的工夫做TTL比拟来判断是否过期。
 

六、Ehcache缓存的应用介绍

6.1、目前最新的Ehcache是3.0版本,咱们也就应用3.0版本来介绍它的应用介绍,看如下代码:

注:这段代码介绍了Ehcache3.0的缓存应用生命周期的一个过程。
1、静态方法CacheManagerBuilder.newCacheManagerBuilder将返回一个新的org.ehcache.config.builders.CacheManagerBuilder的实例。
2、当咱们要构建一个缓存管理器的时候,应用CacheManagerBuilder来创立一个预配置(pre-configured)缓存。

  • 第一个参数是一个别名,用于Cache和Cachemanager进行配合。
  • 第二个参数是org.ehcache.config.CacheConfiguration次要用来配置Cache。咱们应用org.ehcache.config.builders.CacheConfigurationBuilder的静态方法newCacheConfigurationBuilder来创立一个默认配置实例。

3、最初调用.build办法返回一个残缺的实例,当然咱们也能应用CacheManager来初始化。
4、在你开始应用CacheManager的时候,须要应用init()办法进行初始化。
5、咱们能取回在第二步中设定的pre-configured别名,咱们对于key和要传递的值类型,要求是类型平安的,否则将抛出ClassCastException异样。
6、能够依据需要,通过CacheManager创立出新的Cache。实例化和残缺实例化的Cache将通过CacheManager.getCache API返回。
7、应用put办法存储数据。
8、应用get办法获取数据。
9、咱们能够通过CacheManager.removeCache办法来获取Cache,然而Cache取出来当前CacheManager将会删除本身保留的Cache实例。
10、close办法将开释CacheManager所治理的缓存资源。

6.2、对于磁盘长久化

注:如果您想应用长久化机制,就须要提供一个磁盘存储的地位给CacheManagerBuilder.persistence这个办法,另外在应用的过程中,你还须要定义一个磁盘应用的资源池。

下面的例子其实是调配了非常少的磁盘存储量,不过咱们须要留神的是因为存储在磁盘上咱们须要做序列化和反序列化,以及读和写的操作。它的速度必定比内存要慢的多。

6.3、通过xml配置文件创立CacheManager

注:
1、形容缓存的别名。
2、foo的key的类型指定为String类型,而value并没有指定类型,默认就是Object类型。
3、能够在堆中为foo创立2000个实体。
4、在开始淘汰过期缓存项之前,能够调配多达500M的堆内存。
5、cache-template能够实现一个配置形象,以便在将来能够进行扩大。
6、bar应用了cache-template模板myDefaults,并且笼罩了key-type类型,myDefaults的key-type是Long类型,笼罩后成了Number类型。

应用以下代码创立CacheManager:

七、UserManagerCache介绍

7.1 什么是UserManagerCache,它能做什么?
UserManagerCache这是在Ehcache3.0中引入的新的概念,它将间接创立缓存而不须要应用CacheManager来进行治理。所以这也就是UserManagerCache名称的由来。
因为没有CacheManager的治理,用户就必须要手动配置所须要的服务,当然如果你发现要应用大量的服务,那么CacheManager则是更好的抉择。

7.2 应用示例
1、根本示例

UserManagedCache<Long, String> userManagedCache =    UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)        .build(false); userManagedCache.init(); userManagedCache.put(1L, "da one!"); userManagedCache.close(); 

2、长久化示例

LocalPersistenceService persistenceService = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(new File(getStoragePath(), "myUserData"))); PersistentUserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)    .with(new UserManagedPersistenceContext<Long, String>("cache-name", persistenceService))     .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()        .heap(10L, EntryUnit.ENTRIES)        .disk(10L, MemoryUnit.MB, true))     .build(true);// Work with the cachecache.put(42L, "The Answer!");assertThat(cache.get(42L), is("The Answer!"));cache.close(); cache.destroy(); 

3、读写缓存示例

UserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)    .withLoaderWriter(new SampleLoaderWriter<Long, String>())     .build(true);// Work with the cachecache.put(42L, "The Answer!");assertThat(cache.get(42L), is("The Answer!"));cache.close();

注:
如果你心愿频繁的读和写缓存,则能够应用CacheLoaderWriter。

4、缓存淘汰策略示例

UserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)    .withEvictionAdvisor(new OddKeysEvictionAdvisor<Long, String>())     .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()        .heap(2L, EntryUnit.ENTRIES))     .build(true);// Work with the cachecache.put(42L, "The Answer!");cache.put(41L, "The wrong Answer!");cache.put(39L, "The other wrong Answer!");cache.close(); 

注:
如果你想应用缓存淘汰算法来淘汰数据,则要应用EvictionAdvisor这个类。

5、按字节设定的缓存示例

UserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)    .withSizeOfMaxObjectSize(500, MemoryUnit.B)    .withSizeOfMaxObjectGraph(1000)     .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()        .heap(3, MemoryUnit.MB))     .build(true);cache.put(1L, "Put");cache.put(1L, "Update");assertThat(cache.get(1L), is("Update"));cache.close();

注:
withSizeOfMaxObjectGraph这个次要是调整能够设置多少字节对象。
.heap办法次要是设置每个对象最大能够设置多大。

八、缓存的应用模式

应用缓存时有几种常见的拜访模式:
1、预留缓存(Cache-Aside)
应用程序在拜访数据库之前必须要先拜访缓存,如果缓存中蕴含了该数据则间接返回,不必再通过数据库,否则应用程序必须要从先数据库中取回数据,存储在缓存中并且将数据返回,当有数据要写入的时候,缓存内容必须要和数据库内容保持一致。

示例如下代码别离对应读和写:

v = cache.get(k)if(v == null) {    v = sor.get(k)    cache.put(k, v)}v = newVsor.put(k, v)cache.put(k, v)

这种形式是将数据库与缓存通过客户端应用程序被动治理来进行同步,这不是很好的应用形式。

2、Read-Through模式
相比下面的由客户端应用程序来治理数据库和缓存同步的形式,这种模式缓存会配有一个缓存中间件,该中间件来负责数据库数据和缓存之间的同步问题。当咱们利用要获取缓存数据时,这个缓存中间件要确认缓存中是否有该数据,如果没有,从数据库加载,而后放入缓存,下次当前再拜访就能够间接从缓存中取得。

3、Write-Through模式
这种模式就是缓存可能感知数据的变动。
也就是说在更新数据库的同时也要更新缓存,该模式其实也是弱一致性,当数据库更新数据失败的时候,缓存不能持续更新数据,要放弃数据库和缓存的最终一致性。

4、Write-behind模式
该模式是以缓存为优先,将缓存更新的数据寄存队列中,而后数据库定时批量从队列中取出数据更新数据库。

九、Spring3.2+Ehcache2.10.2的应用

为了使例子更加简略易懂,我没有间接去连贯数据库而模仿了一些操作目标次要是演示Spring联合Ehcache的应用。
JavaBean代码如下:

public class User {      public Integer id;      public String name;      public String password;            // 这个须要,不然在实体绑定的时候出错      public User(){}            public User(Integer id, String name, String password) {          super();          this.id = id;          this.name = name;          this.password = password;      }            public Integer getId() {          return id;      }      public void setId(Integer id) {          this.id = id;      }      public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }      public String getPassword() {          return password;      }      public void setPassword(String password) {          this.password = password;      }        @Override      public String toString() {          return "User [id=" + id + ", name=" + name + ", password=" + password                  + "]";      }  }  

UserDAO代码如下:

@Repository("userDao")  public class UserDao {      List<User> userList = initUsers();            public User findById(Integer id) {         for(User user : userList){              if(user.getId().equals(id)){                   return user;            }          }          return null;      }            public void removeById(Integer id) {         User delUser = null;        for(User user : userList){              if(user.getId().equals(id)){                    delUser = user;            }          }          users.remove(delUser);      }            public void addUser(User user){          users.add(user);      }            public void updateUser(User user){          addUser(user);      }       private List<User> initUsers(){          List<User> users = new ArrayList<User>();          User u1 = new User(1,"张三","123");          User u2 = new User(2,"李四","124");          User u3 = new User(3,"王五","125");          users.add(u1);          users.add(u2);          users.add(u3);          return users;      }  }  

UserService代码如下:

@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配置文件内容如下

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

Spring配置文件内容如下:

    <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">          <property name="configLocation"  value="classpath:com/config/ehcache.xml"/>     </bean>         <!-- 反对缓存注解 -->    <cache:annotation-driven cache-manager="cacheManager" />        <!-- 默认是cacheManager -->    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">          <property name="cacheManager"  ref="cacheManagerFactory"/>      </bean>  
十、Spring3.2+Ehcache2.10.2分布式缓存的应用

10.1 Ehcache集群简介
从Ehcache1.2版本开始,Ehcache就能够应用分布式的缓存了,从 1.7版本开始,开始反对共五种集群计划,别离是:

  • Terracotta
  • RMI
  • JMS
  • JGroups
  • EhCache Server

其中有三种上最为罕用集群形式,别离是 RMI、JGroups 以及 EhCache Server 。
其实咱们在应用Ehcache分布式缓存的过程中,次要是以缓存插件的形式应用,如果咱们想依据本人的须要应用分布式缓存那就须要本人开发来定制化,在前面咱们会发现其实Ehcache提供的分布式缓存并不是十分好用,有不少问题存在,所以对缓存数据一致性比拟高的状况下,应用集中式缓存更适合,比方Redis、Memcached等。

10.2 Ehcache集群的基本概念
1、成员发现(Peer Discovery)
Ehcache集群概念中有一个cache组,每个cache都是另一个cache的peer,并不像Redis或者其余分布式组件一样有一个主的存在,Ehcache并没有主Cache,可是那如何晓得集群中的其余缓存都有谁呢?这个就是成员发现。

Ehcache提供了二种机制来实现成员发现性能,别离是手动发现和主动发现。

  • 手动发现
在Ehcache的配置文件中指定cacheManagerPeerProviderFactory元素的class属性为net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory。这就须要本人去配置IP地址和端口号。
  • 主动发现

主动的发现形式用TCP播送机制来确定和维持一个播送组。它只须要一个简略的配置能够主动的在组中增加和移除成员。在集群中也不须要什么优化服务器的常识,这是默认举荐的。

成员每秒向群组发送一个“心跳”。如果一个成员 5秒种都没有发出信号它将被群组移除。如果一个新的成员发送了一个“心跳”它将被增加进群组。

任何一个用这个配置装置了复制性能的cache都将被其余的成员发现并标识为可用状态。

要设置主动的成员发现,须要指定ehcache配置文件中

cacheManagerPeerProviderFactory元素的properties属性,就像上面这样:peerDiscovery=automaticmulticastGroupAddress=multicast address | multicast host namemulticastGroupPort=porttimeToLive=0-255 (timeToLive属性详见常见问题局部的形容)

10.3 联合Spring看示例
先看Spring配置文件:

<!-- spring cache 配置 -->  <!-- 启用缓存注解性能 -->  <cache:annotation-driven cache-manager="cacheManager"/>    <!-- cacheManager工厂类,指定ehcache.xml的地位 -->  <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"        p:configLocation="classpath:ehcache/ehcache.xml"/>    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"        p:cacheManager-ref="ehcache"/>    <cache:annotation-driven />  

Ehcache配置文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>  <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">          <!--EHCache分布式缓存集群环境配置-->      <!--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"/>      <defaultCache              maxElementsInMemory="10000"              eternal="false"              timeToIdleSeconds="120"              timeToLiveSeconds="120"              overflowToDisk="true"              diskSpoolBufferSizeMB="30"              maxElementsOnDisk="10000000"              diskPersistent="false"              diskExpiryThreadIntervalSeconds="120"              memoryStoreEvictionPolicy="LRU">          <cacheEventListenerFactory                  class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>      </defaultCache>      <cache name="user"             maxElementsInMemory="1000"             eternal="false"             timeToIdleSeconds="100000"             timeToLiveSeconds="100000"             overflowToDisk="false">          <cacheEventListenerFactory                  class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>      </cache>  </ehcache>  

以上配置其实就是应用RMI形式在集群的环境进行缓存数据的复制。

十一、Ehcache的应用场景

11.1、Ehcache应用的留神点

1、比拟少的更新数据表的状况
2、对并发要求不是很严格的状况

    多台应用服务器中的缓存是不能进行实时同步的。

3、对一致性要求不高的状况下

    因为Ehcache本地缓存的个性,目前无奈很好的解决不同服务器间缓存同步的问题,所以咱们在一致性要求十分高的场合下,尽量应用Redis、Memcached等集中式缓存。

11.2、Ehcache在集群、分布式的状况下体现如何

在分布式状况下有二种同步形式:
1、RMI组播形式


示例:

<cacheManagerPeerProviderFactory        class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"        properties="peerDiscovery=automatic, multicastGroupAddress=localhost,        multicastGroupPort=4446,timeToLive=255"/>

原理:当缓存扭转时,ehcache会向组播IP地址和端口号发送RMI UDP组播包。
缺点:Ehcache的组播做得比拟高级,性能只是根本实现(比方简略的一个HUB,接两台单网卡的服务器,相互之间组播同步就没问题),对一些简单的环境(比方多台服务器,每台服务器上多地址,尤其是集群,存在一个集群地址带多个物理机,每台物理机又带多个虚构站的子地址),就容易呈现问题。

2、P2P形式
原理:P2P要求每个节点的Ehcache都要指向其余的N-1个节点。

3、JMS音讯模式


原理:这种模式的外围就是一个音讯队列,每个利用节点都订阅事后定义好的主题,同时,节点有元素更新时,也会公布更新元素到主题中去。各个应用服务器节点通过侦听MQ获取到最新的数据,而后别离更新本人的Ehcache缓存,Ehcache默认反对ActiveMQ,咱们也能够通过自定义组件的形式实现相似Kafka,RabbitMQ。

4、Cache Server模式
原理:这种模式会存在主从节点。

缺点:缓存容易呈现数据不统一的问题,

11.3、应用Ehcache的瓶颈是什么

1、缓存漂移(Cache Drift):每个利用节点只治理本人的缓存,在更新某个节点的时候,不会影响到其余的节点,这样数据之间可能就不同步了。

2、数据库瓶颈(Database Bottlenecks ):对于单实例的利用来说,缓存能够爱护数据库的读风暴;然而,在集群的环境下,每一个利用节点都要定期保持数据最新,节点越多,要维持这样的状况对数据库的开销也越大。

11.4、理论工作中如何应用Ehcache

在理论工作中,我更多是将Ehcache作为与Redis配合的二级缓存。
第一种形式:


注:
这种形式通过应用服务器的Ehcache定时轮询Redis缓存服务器更同步更新本地缓存,毛病是因为每台服务器定时Ehcache的工夫不一样,那么不同服务器刷新最新缓存的工夫也不一样,会产生数据不统一问题,对一致性要求不高能够应用。

第二种形式:

注:
通过引入了MQ队列,使每台应用服务器的Ehcache同步侦听MQ音讯,这样在肯定水平上能够达到准同步更新数据,通过MQ推送或者拉取的形式,然而因为不同服务器之间的网络速度的起因,所以也不能齐全达到强一致性。基于此原理应用Zookeeper等分布式协调告诉组件也是如此。

总结:
1、应用二级缓存的益处是缩小缓存数据的网络传输开销,当集中式缓存呈现故障的时候,Ehcache等本地缓存仍然可能撑持应用程序失常应用,减少了程序的健壮性。另外应用二级缓存策略能够在肯定水平上阻止缓存穿透问题。

2、依据CAP原理咱们能够晓得,如果要应用强一致性缓存(依据本身业务决定),集中式缓存是最佳抉择,如(Redis,Memcached等)。

十二、Ehcache2.10.2源码剖析

12.1 源码淘汰策略解析
首先看一下类结构图:

从类结构图上看一共有三种缓存淘汰策略别离是:LFU,LRU,FIFO。对于这三个概念在后面都曾经有过解释,咱们间接这三个的源码:
1、LRUPolicy代码如下:

public class LruPolicy extends AbstractPolicy {    /**     * The name of this policy as a string literal     */     public static final String NAME = "LRU";    /**     * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO.     */    public String getName() {        return NAME;    }    /**     * Compares the desirableness for eviction of two elements     *     * Compares hit counts. If both zero,     *     * @param element1 the element to compare against     * @param element2 the element to compare     * @return true if the second element is preferable to the first element for ths policy     */    public boolean compare(Element element1, Element element2) {        return element2.getLastAccessTime() < element1.getLastAccessTime();    }

注:
accessTime小的缓存淘汰。

2、LFUPolicy代码如下:

public class LfuPolicy extends AbstractPolicy {    /**     * The name of this policy as a string literal     */    public static final String NAME = "LFU";    /**     * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO.     */    public String getName() {        return NAME;    }    /**     * Compares the desirableness for eviction of two elements     *     * Compares hit counts. If both zero,      *     * @param element1 the element to compare against     * @param element2 the element to compare     * @return true if the second element is preferable to the first element for ths policy     */    public boolean compare(Element element1, Element element2) {        return element2.getHitCount() < element1.getHitCount();            }}

注:
hit值小的缓存淘汰。

3、FIFOPolicy代码如下:

public class FifoPolicy extends AbstractPolicy {    /**     * The name of this policy as a string literal     */     public static final String NAME = "FIFO";    /**     * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO.     */    public String getName() {        return NAME;    }    /**     * Compares the desirableness for eviction of two elements     *     * Compares hit counts. If both zero,     *     * @param element1 the element to compare against     * @param element2 the element to compare     * @return true if the second element is preferable to the first element for ths policy     */    public boolean compare(Element element1, Element element2) {        return element2.getLatestOfCreationAndUpdateTime() < element1.getLatestOfCreationAndUpdateTime();    }}

注:
以creationAndUpdateTime最新或者最近的缓存淘汰。

4、这三个策略类对立继承AbstractPolicy抽类
最要害的就是上面这个办法:

public Element selectedBasedOnPolicy(Element[] sampledElements, Element justAdded) {        //edge condition when Memory Store configured to size 0        if (sampledElements.length == 1) {            return sampledElements[0];        }        Element lowestElement = null;        for (Element element : sampledElements) {            if (element == null) {                continue;            }            if (lowestElement == null) {                if (!element.equals(justAdded)) {                    lowestElement = element;                }            } else if (compare(lowestElement, element) && !element.equals(justAdded)) {                lowestElement = element;            }        }        return lowestElement;    }

注:
1、这个办法次要是从取样节点中查找须要淘汰的缓存。
2、最要害的就是调用compare这个办法其实就是调用的下面那三个策略实现的办法来找个能够淘汰的缓存节点。

那么接下来咱们看一下淘汰缓存的生命周期流程是怎么样的。

12.2 EhcacheManager类解析
这个类是2.10.2版本的最外围类,初始化、创立缓存、获取缓存都会用到这个类,这个类外面有几十个办法十分多,咱们会依照类别别离进行介绍,先看其构造方法吧。

 先看办法CacheManager()默认的状况代码如下:

public CacheManager() throws CacheException {        // default config will be done        status = Status.STATUS_UNINITIALISED;        init(null, null, null, null);}

Status.STATUS_UNINITIALISED这句的意思是缓存未被初始化,在构造方法外面要设定一个初始状态。

咱们接着看init办法,这个办法是有别于其余构造方法的,因为默认的状况下这个办法的参数全传null值,这就意味着应用ehcache本人默认的配置了。
代码如下:

protected synchronized void init(Configuration initialConfiguration, String configurationFileName, URL configurationURL,            InputStream configurationInputStream) {        Configuration configuration;        if (initialConfiguration == null) {            configuration = parseConfiguration(configurationFileName, configurationURL, configurationInputStream);        } else {            configuration = initialConfiguration;        }        assertManagementRESTServiceConfigurationIsCorrect(configuration);        assertNoCacheManagerExistsWithSameName(configuration);        try {            doInit(configuration);        } catch (Throwable t) {            if (terracottaClient != null) {                terracottaClient.shutdown();            }            if (statisticsExecutor != null) {                statisticsExecutor.shutdown();            }            if (featuresManager != null) {                featuresManager.dispose();            }            if (diskStorePathManager != null) {                diskStorePathManager.releaseLock();            }            if (cacheManagerTimer != null) {                cacheManagerTimer.cancel();                cacheManagerTimer.purge();            }            synchronized (CacheManager.class) {                final String name = CACHE_MANAGERS_REVERSE_MAP.remove(this);                CACHE_MANAGERS_MAP.remove(name);            }            ALL_CACHE_MANAGERS.remove(this);            if (t instanceof CacheException) {                throw (CacheException) t;            } else {                throw new CacheException(t);            }        }    }

阐明
1、首先要判断initialConfiguration这个参数是不是为空,判断的状况下必定是为就间接调用了parseConfiguration这个办法,这个办法调用classpath默认门路来查找配置文件内容,初始化完configuration当前调用doInit办法。
2、doInit办法次要用来初始化最大的本地堆大小,初始化最大的本地长久化磁盘设置大小,集群模式,事务设置等等。

12.3 Cache类解析

cache的类继承构造如下所示:

阐明:
Ehcache接口是整个缓存的外围接口,该接口提供的办法能够间接操作缓存,比方put,get等办法。因为办法太多咱们只单拿进去put和get办法做一个深入分析。

先看put办法源码:

 private void putInternal(Element element, boolean doNotNotifyCacheReplicators, boolean useCacheWriter) {        putObserver.begin();        if (useCacheWriter) {            initialiseCacheWriterManager(true);        }        checkStatus();        if (disabled) {            putObserver.end(PutOutcome.IGNORED);            return;        }        if (element == null) {            if (doNotNotifyCacheReplicators) {                LOG.debug("Element from replicated put is null. This happens because the element is a SoftReference" +                        " and it has been collected. Increase heap memory on the JVM or set -Xms to be the same as " +                        "-Xmx to avoid this problem.");            }            putObserver.end(PutOutcome.IGNORED);            return;        }        if (element.getObjectKey() == null) {            putObserver.end(PutOutcome.IGNORED);            return;        }        element.resetAccessStatistics();        applyDefaultsToElementWithoutLifespanSet(element);        backOffIfDiskSpoolFull();        element.updateUpdateStatistics();        boolean elementExists = false;        if (useCacheWriter) {            boolean notifyListeners = true;            try {                elementExists = !compoundStore.putWithWriter(element, cacheWriterManager);            } catch (StoreUpdateException e) {                elementExists = e.isUpdate();                notifyListeners = configuration.getCacheWriterConfiguration().getNotifyListenersOnException();                RuntimeException cause = e.getCause();                if (cause instanceof CacheWriterManagerException) {                    throw ((CacheWriterManagerException)cause).getCause();                }                throw cause;            } finally {                if (notifyListeners) {                    notifyPutInternalListeners(element, doNotNotifyCacheReplicators, elementExists);                }            }        } else {            elementExists = !compoundStore.put(element);            notifyPutInternalListeners(element, doNotNotifyCacheReplicators, elementExists);        }        putObserver.end(elementExists ? PutOutcome.UPDATED : PutOutcome.ADDED);    }

阐明:
1、代码的逻辑其实很简略,咱们看一下compoundStore这个类,实际上咱们缓存的数据最终都是要到这个类外面进行存储的。
2、代码外面应用了putObserver观察者对象次要是用来做计数统计工作用的。

看一下compoundStore类的继承结构图如下:


通过图中能够看到所有的存储类都实现Store接口类,大略有以下几种存储形式:
1、集群形式:ClusteredStore
2、缓存形式:CacheStore
3、内存形式:MemoryStore
4、磁盘形式:DiskStore

咱们以DiskStore为例深刻解说磁盘的局部源码剖析。

writeLock().lock();        try {            // ensure capacity            if (count + 1 > threshold) {                rehash();            }            HashEntry[] tab = table;            int index = hash & (tab.length - 1);            HashEntry first = tab[index];            HashEntry e = first;            while (e != null && (e.hash != hash || !key.equals(e.key))) {                e = e.next;            }            Element oldElement;            if (e != null) {                DiskSubstitute onDiskSubstitute = e.element;                if (!onlyIfAbsent) {                    e.element = encoded;                    installed = true;                    oldElement = decode(onDiskSubstitute);                    free(onDiskSubstitute);                    final long existingHeapSize = onHeapPoolAccessor.delete(onDiskSubstitute.onHeapSize);                    LOG.debug("put updated, deleted {} on heap", existingHeapSize);                    if (onDiskSubstitute instanceof DiskStorageFactory.DiskMarker) {                        final long existingDiskSize = onDiskPoolAccessor.delete(((DiskStorageFactory.DiskMarker) onDiskSubstitute).getSize());                        LOG.debug("put updated, deleted {} on disk", existingDiskSize);                    }                    e.faulted.set(faulted);                    cacheEventNotificationService.notifyElementUpdatedOrdered(oldElement, element);                } else {                    oldElement = decode(onDiskSubstitute);                    free(encoded);                    final long outgoingHeapSize = onHeapPoolAccessor.delete(encoded.onHeapSize);                    LOG.debug("put if absent failed, deleted {} on heap", outgoingHeapSize);                }            } else {                oldElement = null;                ++modCount;                tab[index] = new HashEntry(key, hash, first, encoded, new AtomicBoolean(faulted));                installed = true;                // write-volatile                count = count + 1;                cacheEventNotificationService.notifyElementPutOrdered(element);            }            return oldElement;        } finally {            writeLock().unlock();            if (installed) {                encoded.installed();            }        }

阐明:
1、流程采纳写锁,先将这段代码锁定。
2、程序中有HashEntry[] tab这样一个桶,每个桶中存储一个链表,首先通过hash & (tab -1) 也就是key的hash值与桶的长度减1取余得出一个桶的index。而后取出链表实体,失去以后链表实体的下一个元素,如果元素为null则间接将元素赋值,否则取出旧的元素用新元素替换,开释旧元素空间,返回旧元素。

十三、Guava Cache的应用与实现

Guava Cache与ConcurrentMap很类似,但也不齐全一样。最根本的区别是ConcurrentMap会始终保留所有增加的元素,直到显式地移除。绝对地,Guava Cache为了限度内存占用,通常都设定为主动回收元素。在某些场景下,只管LoadingCache 不回收元素,它也是很有用的,因为它会主动加载缓存。

通常来说,Guava Cache
实用于:
你违心耗费一些内存空间来晋升速度。
你预料到某些键会被查问一次以上。
缓存中寄存的数据总量不会超出内存容量。(Guava Cache是单个利用运行时的本地缓存。它不把数据寄存到文件或内部服务器。如果这不合乎你的需要,请尝试Memcached或者Redis等集中式缓存。

Guava Cache是一个全内存的本地缓存实现,它提供了线程平安的实现机制。

Guava Cache有两种创立形式:

  1. CacheLoader  
  2. Callable callback

13.1 CacheLoader形式
先看一段示例代码如下:

 public static void main(String[] args) throws ExecutionException, InterruptedException {        //缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时能够主动加载缓存        LoadingCache<Integer, String> strCache                //CacheBuilder的构造函数是公有的,只能通过其静态方法newBuilder()来取得CacheBuilder的实例                = CacheBuilder.newBuilder()                //设置并发级别为8,并发级别是指能够同时写缓存的线程数                .concurrencyLevel(8)                //设置写缓存后8秒钟过期                .expireAfterWrite(8, TimeUnit.SECONDS)                //设置缓存容器的初始容量为10                .initialCapacity(10)                //设置缓存最大容量为100,超过100之后就会依照LRU最近虽少应用算法来移除缓存项                .maximumSize(100)                //设置要统计缓存的命中率                .recordStats()                //设置缓存的移除告诉                .removalListener(new RemovalListener<Object, Object>() {                    public void onRemoval(RemovalNotification<Object, Object> notification) {                        System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());                    }                })                //build办法中能够指定CacheLoader,在缓存不存在时通过CacheLoader的实现主动加载缓存                .build(                        new CacheLoader<Integer, String>() {                            @Override                            public String load(Integer key) throws Exception {                                System.out.println("load data: " + key);                                String str = key + ":cache-value";                                return str;                            }                        }                );        for (int i = 0; i < 20; i++) {            //从缓存中失去数据,因为咱们没有设置过缓存,所以须要通过CacheLoader加载缓存数据            String str = strCache.get(1);            System.out.println(str);            //休眠1秒            TimeUnit.SECONDS.sleep(1);        }        System.out.println("cache stats:");        //最初打印缓存的命中率等 状况        System.out.println(strCache.stats().toString());    }

运行后果如下:


阐明:
guava中应用缓存须要先申明一个CacheBuilder对象,并设置缓存的相干参数,而后调用其build办法取得一个Cache接口的实例。

13.2 Callable形式
办法原型如下:get(K, Callable<V>)
这个办法返回缓存中相应的值,如果未获取到缓存值则调用Callable办法。这个办法简便地实现了模式"如果有缓存则返回;否则运算、缓存、而后返回"。
看示例代码如下:

 Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();          String resultVal = cache.get("test", new Callable<String>() {              public String call() {                  //未依据key查到对应缓存,设置缓存                String strProValue="test-value"                             return strProValue;            }          });              System.out.println("return value : " + resultVal);      }

13.3 缓存过期删除
guava的cache数据过期删除的形式有二种,别离是被动删除和被动删除二种。

被动删除三种形式
  • 基于条数限度的删除

应用CacheBuilder.maximumSize(long)办法进行设置。
留神点:
1、这个size不是容量大小,而是记录条数。
2、应用CacheLoader形式加载缓存的时候,在并发状况下如果一个key过期删除,正好同时有一个申请获取缓存,有可能会报错。

  • 基于过期工夫删除

在Guava Cache中提供了二个办法能够基于过期工夫删除
1、expireAfterAccess(long, TimeUnit):某个key最初一次拜访后,再隔多长时间后删除。
2、expireAfterWrite(long, TimeUnit):某个key被创立后,再隔多长时间后删除。

  • 基于援用的删除

通过应用弱援用的键、或弱援用的值、或软援用的值,Guava Cache能够把缓存设置为容许垃圾回收。

被动删除三种形式
  • 个别革除:Cache.invalidate(key)
  • 批量革除:Cache.invalidateAll(keys)
  • 革除所有缓存项:Cache.invalidateAll()
本文由博客群发一文多发等经营工具平台 OpenWrite 公布