说明:所有的基础配置和Ehcache的简单学习2【https://my.oschina.net/hanchao/blog/1596654】是一样的,只是多了一些具体代码,下面贴出来。
一、实际使用
1.简单回顾了解
Ehcache是一个用Java实现的使用简单,高速,实现线程安全的缓存管理类库,ehcache提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案。同时ehcache作为开放源代码项目,采用限制比较宽松的Apache License V2.0作为授权方式,被广泛地用于Hibernate, Spring,Cocoon等其他开源系统。Ehcache 从 Hibernate 发展而来,逐渐涵盖了 Cahce 界的全部功能,是目前发展势头最好的一个项目。具有快速,简单,低消耗,依赖性小,扩展性强,支持对象或序列化缓存,支持缓存或元素的失效,提供 LRU、LFU 和 FIFO 缓存策略,支持内存缓存和磁盘缓存,分布式缓存机制等等特点。
特性:
快速;
简单;
多种缓存策略;
缓存数据有两级:内存和磁盘,因此无需担心容量问题;
缓存数据会在虚拟机重启的过程中写入磁盘;
可以通过 RMI、可插入 API 等方式进行分布式缓存;
具有缓存和缓存管理器的侦听接口;
支持多缓存管理器实例,以及一个实例的多个缓存区域;
提供 Hibernate 的缓存实现;
2.实际工作使用
在实际工作中,我更多是将Ehcache作为与Redis配合的二级缓存。
1.第一种方式
注意:这种方式通过应用服务器的Ehcache定时轮询Redis缓存服务器更同步更新本地缓存,缺点是因为每台服务器定时Ehcache的时间不一样,那么不同服务器刷新最新缓存的时间也不一样,会产生数据不一致问题,对一致性要求不高可以使用。【我们现在的例子就是这种方式】
2.第二种方式
注意:通过引入了MQ队列,使每台应用服务器的Ehcache同步侦听MQ消息,这样在一定程度上可以达到准同步更新数据,通过MQ推送或者拉取的方式,但是因为不同服务器之间的网络速度的原因,所以也不能完全达到强一致性。基于此原理使用Zookeeper等分布式协调通知组件也是如此
3.总结
《1.》使用二级缓存的好处是减少缓存数据的网络传输开销,当集中式缓存出现故障的时候,Ehcache等本地缓存依然能够支撑应用程序正常使用,增加了程序的健壮性。另外使用二级缓存策略可以在一定程度上阻止缓存穿透问题。
《2.》根据CAP原理我们可以知道,如果要使用强一致性缓存(根据自身业务决定),集中式缓存是最佳选择,如(Redis,Memcached等)。
二、spring管理bean
三、本地缓存代码
package com.book.web.controller;
import net.sf.ehcache.Cache;
/**
* 本地缓存
* @author liweihan
*
*/
public interface LocalEhCache {
/**
* 获取Cache对象
* @return
*/
public Cache getEhCache();
/**
* 根据key获取缓存对象
* @param key 缓存的key
* @param tClass 类似的class
* @return
*/
public <T> T get(Object key, Class<T> tClass);
/**
* 缓存对象
* @param key
* @param value
*/
public void put(Object key, Object value);
/**
* 可以控制时间的缓存对象
* @param key
* @param obj
* @param idleTime 无访问缓存时间(单位:秒)
* @param liveTime 生存时间(单位:秒)
*
*/
public void put(Object key, Object obj, Integer idleTime, Integer liveTime);
/**
* 根据key清除缓存对象
* @param key
*/
public boolean remove(Object key);
/**
* 判断缓存中是否包括key的缓存
* @param key
* @return
*/
public boolean containsKey(Object key);
/**
* 缓存的数量
* @return
*/
public int size();
/**
* 清除过期的缓存
* 注意的是触发ehcache去检查这个元素是否过期expiry,
* 是由用户访问了元素,即调用cache.get(key)按需触发,
* 这时ehcache才会去检查这个元素是否过期,
* 如果过期就把该元素清除,并返回null。
*
* 所以如果存在这样的场景:
* 有些元素我们一直都不去访问,
* 且内存中的元素数量又没超出maxElementsInMemory的值,
* 那么这些过期元素将一直驻留在内存中。
*
* 为了解决这个问题,我们应该创建一个后台线程,
* 这个线程可以过一段时间去触发一下cache.evictExpiredElements(),
* 这样即可把内存中驻留的过期元素清除。
*/
public void evictExpiredElements();
/**
* 清除所有ehcache缓存
*/
public void removeAll();
}
package com.book.web.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
public class LocalEhCacheImpl implements LocalEhCache{
private Cache cache;
private static Logger logger = LoggerFactory.getLogger(LocalEhCacheImpl.class);
public LocalEhCacheImpl(EhCacheCacheManager ehCacheCacheManager,String name) {
logger.info(" ====== init ehcache!!");
cache = ehCacheCacheManager.getCacheManager().getCache(name);
}
@Override
public Cache getEhCache() {
return this.cache;
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Object key, Class<T> tClass) {
try {
if (this.containsKey(key)) {
return (T)cache.get(key).getObjectValue();
}
} catch (IllegalStateException e) {
logger.error(" ====== Get from Ehcache Error ,key:{}",key,e);
}
return null;
}
@Override
public void put(Object key, Object value) {
try {
Element element = new Element(key, value);
cache.put(element);
} catch (Exception e) {
logger.error(" ====== put into Ehcache Error ,key:{},value:{}",key,value,e);
}
}
@Override
public void put(Object key, Object value, Integer idleTime, Integer liveTime) {
try {
Element element = new Element(key, value, false, idleTime, liveTime);
cache.put(element);
} catch (Exception e) {
logger.error(" ====== put into Ehcache Error ,key:{},value:{},idle:{},liveTime:{}",
key,value,idleTime,liveTime,e);
}
}
@Override
public boolean remove(Object key) {
return cache.remove(key);
}
@Override
public boolean containsKey(Object key) {
boolean f = false;
try {
Element element = cache.getQuiet(key);//静态的获取Element,不会产生update
if (cache.isKeyInCache(key) && element != null && !cache.isExpired(element)) {
f = true;
} else {
f = false;
}
} catch (Exception e) {
logger.error(" ====== ehcache check Key ERROR ! key:{}",key,e);
f = false;
}
return f;
}
@Override
public int size() {
return cache.getSize();
}
@Override
public void evictExpiredElements() {
cache.evictExpiredElements();
}
@Override
public void removeAll() {
cache.removeAll();
}
}
四、web调用时
package com.book.web.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
import com.book.core.model.User;
import com.book.core.serializable.SerializationUtil;
/**
* Ehcache实际使用
* @author liweihan
*
*/
@Controller
@RequestMapping("/c")
public class TestEhcache2Controller {
private static Logger logger = LoggerFactory.getLogger(TestEhcache2Controller.class);
@Autowired
private LocalEhCache localEhcache;
private static final String TEST_SUFF = "T_%s";
@ResponseBody
@RequestMapping("/get/{id}")
public String get1(@PathVariable(value = "id") int id) {
logger.info(" ----- 用户Id:{},{}",
id,System.getProperty("java.io.tmpdir"));
//字符串
String key = String.format(TEST_SUFF,1);
localEhcache.put(key, "Test");
logger.info(" ----- 获取缓存数据:key:{},value:{}",
key,localEhcache.get(key, String.class));
//整型
String key2 = String.format(TEST_SUFF, 2);
localEhcache.put(key2, 2);
logger.info(" ----- 获取缓存数据:key:{},value:{}",
key2,localEhcache.get(key2, Integer.class));
//对象
User user = new User();
user.setId(1);
user.setName("韩超");
user.setEmail("hanchaohan@126.com");
String key3 = String.format(TEST_SUFF, 3);
long begin3 = System.currentTimeMillis();
localEhcache.put(key3, user);
logger.info(" ----- 获取缓存数据:key:{},value:{},time:{}",
key3,localEhcache.get(key3, User.class).getName()
,(System.currentTimeMillis()-begin3));
//JSON对象
JSONObject jsonObject = new JSONObject();
jsonObject.put("user", "hanchao");
jsonObject.put("blog", "https://my.oschina.net/hanchao/blog");
String key4 = String.format(TEST_SUFF, 4);
localEhcache.put(key4,jsonObject);
logger.info(" ----- 获取缓存数据:key:{},value:{}",
key4,localEhcache.get(key4,JSONObject.class).get("blog"));
//序列化对象-注意:不是所有的数据都需要序列化的!比如JSON对象,字符串,等没有必要序列化
//关于序列化参考一下:http://blog.51cto.com/hanchaohan/1962798
//因为Ehcache一般作为第一层的本地化缓存,究竟要不要序列化,具体项目具体考虑效率,内存等!
String key5 = String.format(TEST_SUFF, 5);
try {
long begin5 = System.currentTimeMillis();
localEhcache.put(key5, SerializationUtil.object2Bytes(user));
byte[] obj = localEhcache.get(key5, byte[].class);
User result = (User)SerializationUtil.bytes2Object(obj);
logger.info(" ----- 获取缓存数据:key:{},value:{},time:{}",
key5,result.getName(),(System.currentTimeMillis() - begin5));
} catch (Exception e) {
e.printStackTrace();
}
return "OK";
}
/**
* 根据获取
* @param key
* @return
*/
@ResponseBody
@RequestMapping("/get2/{key}")
public String get2(@PathVariable(value = "key") String key) {
String key1 = String.format(TEST_SUFF,1);
logger.info(" ----- 获取缓存数据:key:{},value:{}",
key,localEhcache.get(key1, String.class));
String key4 = String.format(TEST_SUFF, 4);
logger.info(" ----- 获取缓存数据:key:{},value:{}",
key4,localEhcache.get(key4,JSONObject.class));
return "OK";
}
/**
* 删除某个元素
* @param key
* @return
*/
@ResponseBody
@RequestMapping("/r/{key}")
public String remove(@PathVariable(value = "key") String key) {
boolean result = localEhcache.remove(key);
localEhcache.evictExpiredElements();
logger.info(" ====== result : {} ,key :{}",result,key);
return "OK";
}
@ResponseBody
@RequestMapping("/i")
public String info() {
logger.info(" ====== size:{}",localEhcache.size());
return "OK";
}
/**
* 删除所有元素
* @param key
* @return
*/
@ResponseBody
@RequestMapping("/removeall")
public String removeAll() {
localEhcache.removeAll();
return "OK";
}
}
五、Ehcache与Guava的比较
《0.》两者都是很成熟的JVM级别缓存,所以在绝大多数情况都是可以满足要求的。
《1.》Ehcache支持持久化到本地磁盘,Guava不可以;
《2.》Ehcache有现成的集群解决方案,Guava没有。不过个人感觉比较鸡肋,对JVM级别的缓存来讲太重了。
《3.》Ehcache jar包庞大,Guava Cache只是Guava jar包中的工具之一,而且后者远远小于Ehcache;
《4.》两种缓存当缓存过期或者没有命中的时候都可以通过load接口重载数据,调用方式略有不同。两者的主要区别是Ehcache的缓存load的时候,允许用户返回null,而Guava Cache则不允许返回为null,因为Guava Cache是根据value的值是否为null来判断是否需要load,所以不允许返回为null,但是使用的时候可以使用空对象替换。不允许返回null是一个很好的考虑;
《5.》Ehcache有内存占用大小统计,Guava Cache没有,需要自己开发。(但是,打开Ehcache的统计也是需要耗费性能的!)
《6.》
适用Ehcache的情况:
《1.》需要持久化持久化。使用持久化功能需要,缓存稳定,以免持久化的数据不准确影响结果。
《2.》有集群解决方案。
适用Guava cache的情况:
Guava cache说简单点就是一个支持LRU的ConCurrentHashMap,它没有Ehcache那么多的各种特性,只是提供了增、删、改、查、刷新规则和时效规则设定等最基本的元素。做一个jar包中的一个功能之一,Guava cache极度简洁并能满足觉大部分人的要求。
总结:
Ehcache有着全面的缓存特性,但是略重。Guava cache有最基本的缓存特性,很轻。大家根据具体情况选择使用。
六、Ehcache和Redis、Memcache的比较
Ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。
redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。
如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。
另:ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。
参考文档:
http://blog.youkuaiyun.com/jationxiaozi/article/details/8509732
七、Ehcache对并发的支持
在高并发的情况下,使用Ehcache缓存时,由于并发的读与写,我们读的数据有可能是错误的,我们写的数据也有可能意外的被覆盖。所幸的是Ehcache为我们提供了针对于缓存元素Key的Read(读)、Write(写)锁。
当一个线程获取了某一Key的Read锁之后,其它线程获取针对于同一个Key的Read锁不会受到限制,但其它线程(包括获取了该Key的Read锁的线程)如果想获取针对同一个Key的Write锁就不行,它需要等到针对于该Key的Read锁释放后才能获取其Write锁;
当一个线程获取了某一Key的Write锁之后,其它线程获取同一个Key的Read锁或者Write锁的请求将等待针对于该Key的Write锁释放后才能继续进行,但是同一个线程获取该Key对应的Read锁或者Write锁将不需要等待。获取了对应的锁之后,记得在不再需要该锁后释放该锁。并且需要注意不要引起死锁。
Ehcache对并发的支持,可以参考这篇文章,实际中,我使用也许是读居多!
http://blog.youkuaiyun.com/elim168/article/details/71723348
JSR107简单了解:http://tangmingjie2009.iteye.com/blog/2042485