缓存组件-Ehcache

Ehcache概述

Ehcache是一个用Java实现的使用简单、高速、线程安全的缓存管理类库, 其提供了用内存、磁盘文件寸纯、以及分布式存储等多种灵活的管理方案. Ehcache从Hibernate发展而来, 快速、简单、低消耗、依赖性小、扩展性强.

Ehcache特点

  1. 快速、简单;

  2. 多种缓存策略: LRU、LFU和FIFO;

  3. 缓存数据有两级:内存和磁盘,因此无需担心容量问题;

  4. 缓存数据会在虚拟机重启的过程中写入磁盘;

  5. 可以通过 RMI、可插入 API 等方式进行分布式缓存;

  6. 具有缓存和缓存管理器的侦听接口: 缓存管理器监听器、缓存事件监听器;

  7. 支持多缓存管理器实例,以及一个实例的多个缓存区域;

  8. 提供 Hibernate 的缓存实现: Hibernate默认二级缓存是不启动的, 启动二级缓存需要采用 Ehcache来实现;

Ehcache缓存数据过期策略

Ehcache提供三种缓存过期策略.

  • FIFO: (First In First Out)先进先出, 驱除原则是优先驱除最先储存的元素.

  • LRU: (Least Recently Used) 最近最少使用, 驱除原则是优先驱除最后使用时间最早的元素. 缓存的元素有一个时间戳, 距离当前时间最远的元素优先被清出缓存.

  • LFU: (Least Frequently Used)最不常使用, 每次我们从Cache中获取一个元素时都会更新该元素的hitCount属性值加1。当采用最不常使用原则进行驱除时hitCount属性值最小的元素将优先驱除.

对比LRU、LFU、FIFO

  • LRU: 比较最后的访问时间

  • LFU: 比较get次数

  • FIFO: 根据创建或修改时间

Ehcache基本使用

Ehcache配置

CacheManager是Ehcache的核心,它的主要职责是对Cache的创建、移除和访问。只有CacheManager里面的Cache才能实现缓存数据的功能。一切使用Ehcache的应用都是从构建CacheManager开始的, 一个应用可以有多个CacheManager,而一个CacheManager下又可以有多个Cache。Cache内部保存的是一个个的Element,而一个Element中保存的是一个key和value的配对,相当于Map里面的一个Entry.

ehcache.xml文件是用来定义Ehcache的配置信息的,更准确的来说它是定义CacheManager的配置信息的, 配置示例如下.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<ehcache
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">

<!-- 硬盘缓存的临时路径 -->
<diskStore path="java.io.tmpdir"/>

<!-- 默认的缓存区域的缓存策略

-->
<defaultCache
maxElementsInMemory="20000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>

<!-- 自定义缓存区域 -->
<cache name="eternalCache"
maxElementsInMemory="20000"
eternal="true"
overflowToDisk="true"
diskPersistent="false"
timeToLiveSeconds="0"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>

参数说明

  • name: cache的唯一标识

  • maxElementsInMemory:内存中最大容纳的缓存对象数量

  • maxElementsOnDisk:硬盘上能存放的最大缓存对象数量, 0标识无穷大.

  • eternal: 对象是否永生,默认是false, 一丹设置了, timeout将不起作用

  • overflowToDisk: 配置此属性,当内存中缓存对象数量达到maxElementsInMemory, Ehcahce会将Element写到磁盘中.

  • timeToIdleSeconds: 设置缓存对象在失效前的允许闲置时间. 仅当缓存对象不是永久有效时使用, 默认值是0, 也就是闲置时间无穷大.

  • timeToLiveSeconds: 设置缓存对象在失效前允许存活时间. 最大时间介于创建时间和失效时间之间; 仅当缓存对象不是永久有效时使用, 默认值是0, 也就是存活时间无穷大.

  • diskPersistent: 是否将缓存数据持久化在磁盘上, 默认为false, true表示JVM重启 原来的缓存数据仍然存在.

  • diskExpiryThreadIntervalSeconds: 磁盘失效线程运行时间间隔, 默认是120秒.

  • memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时, Ehcache将会根据指定的策略进行清理内存, 支持: LRU(默认侧),LFU,FIFO.

更多参数: http://elim.iteye.com/blog/2113728

新增元素

1
2
3
4
5
6
7
@Test  
public void create() {
Cache cache = cacheManager.getCache("cache");
Element ele = new Element("key", "value");
//把ele放入缓存cache中
cache.put(ele);
}

获取元素

1
2
3
4
5
6
7
8
9
public void read() {  
Cache cache = cacheManager.getCache("cache");
//通过key来获取缓存中对应的元素
Element ele = cache.get("key");
System.out.println(ele);
if (ele != null) {//当缓存的元素存在时获取缓存的值
System.out.println(ele.getObjectValue());
}
}

更新元素

1
2
3
4
5
6
7
8
public void update() {  
Cache cache = cacheManager.getCache("cache");
cache.put(new Element("key", "value1"));
System.out.println(cache.get("key"));
//当添加元素的时候,如果缓存中已经存在相同key的元素则会将后者覆盖前者
cache.put(new Element("key", "value2"));
System.out.println(cache.get("key"));
}

删除元素

1
2
3
4
5
6
public void delete() {  
Cache cache = cacheManager.getCache("cache");
//根据key来移除一个元素
cache.remove("key");
System.out.println(cache.get("key"));
}

Ehcache集群方式

Ehcache从1.7版本开始, 支持5种集群方案: Terracotta、RMI、JMS、JGroup、Ehcache Server; 其中RMI、JMS和Ehcache Server是最经常使用的.

RMI组播

RMI 是一种点对点的基于Java 对象的通讯方式, Ehcache从1.2版本开始就支持RMI方式的缓存机器. 在集群环境中Ehcache所有缓存对象的Key和value都必须是可序列化的, 也就是必须实现java.io.serializable. 这点在其它集群方式下也是需要遵守的.

JMS消息方式

JSM 是两个应用程序之间进行异步通信的API, 它为标准消息协议和消息服务提供了一组通用接口, 包括创建、发送、读取消息等, 用于支持Java应用程序开发, JMS也支持基于事件的通信机制, 通过发布事件机制向所有服务器保持连接的客户端发送消息, 在发送消息时, 接受者不需要在线, 等到客户端上线的时候, 能保证接收到服务器发送的消息.

JMS 的核心就是一个消息队列, 每个应用节点都订阅预先定义好的主题, 同时,节点有元素更新时, 也会发布更新元素到主题去, 各个应用服务器节点通过侦听MQ获取到最新的数据, 然后分别更新自己的Ehcache缓存.

Ehcache默认支持ActiveMQ, 也可以通过自定义组件的方式实现类似Kafka和RabbitMQ等.

Cache Server 模式

缓存服务器集群模式, 缓存数据集中放在Ehcache Server中, Ehcache Server之间做数据复制.

Cache Server一般以War包方式独立部署, Cache Server有两种类型的API: 面向资源的Restful和Soap. 这两种API都能够跨语言支持.

Spring中使用cache

和Spring对事务管理的支持一样,Spring对Cache的支持也有基于注解和基于XML配置两种方式。下面我们先来看看基于注解的方式

Spring提供了4中方法级的缓存注解:

@Cacheable

@Cacheable 可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的.

该注解支持用condition属性来设置条件, 如果不满足条件, 就不适用缓存能力, 直接执行方法.

使用示例

示例表示只有当user的id为偶数时才会进行缓存.

1
2
3
4
5
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}

@CachePut

此注解的支持的属性和方法与@Cacheable一致, 对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

使用示例

1
2
3
4
@CachePut("users")
public User find(Integer id) {
return getFromDB(id);
}

对比@CachePut@Cacheable

  • @CachePut: 这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中。

  • @Cacheable: 当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了

@CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的.

应用示例

1
2
3
4
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}

@CacheConfig

这是一个类级别的注解, 主要是共享缓存的名称, 比如@Cacheable里面有一个value=”xxx”的属性, 如果需要配置的方法很多, 以后维护起来非常不方便, 所以@CacheConfig就是用来统一声明.

Ehcache架构图

Ehcache应用场景

  • 比较少的更新数据表的场景

  • 对并发要求不是很严格的场景

  • 对一致性要求不高的场景

Ehcache瓶颈点

缓存漂移

每个应用节点只管理自己的缓存, 在更新某个节点的时候, 不会影响到其它的节点, 这样数据之间可能就不同步了.

数据库瓶颈

对于单实例应用来说, 缓存可以保护数据库的读风暴, 但是在集群环境下, 每个应用节点都要定期保证数据最新, 节点越多, 节点越大, 维护这样的情况对数据库开销也越大.

参考文档