Cache高速缓存
Google Guava官方教程(中文版) http://ifeve.com/google-guava/
高速缓存(英语:Cache),其原始意义是指访问速度比一般随机存取存储器(RAM)快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。
原理:
Cache一词来源于1967年的一篇电子工程期刊论文。其作者将法语词“cache”赋予“safekeeping storage”的涵义,用于计算机工程领域。
当CPU处理数据时,它会先到Cache中去寻找,如果数据因之前的操作已经读取而被暂存其中,就不需要再从随机存取存储器(Main memory)中读取数据——由于CPU的运行速度一般比主内存的读取速度快,主存储器周期(访问主存储器所需要的时间)为数个时钟周期。因此若要访问主内存的话,就必须等待数个CPU周期从而造成浪费。
提供“高速缓存”的目的是为了让数据访问的速度适应CPU的处理速度,其基于的原理是内存中“程序执行与数据访问的局域性行为”,即一定程序执行时间和空间内,被访问的代码集中于一部分。为了充分发挥高速缓存的作用,不仅依靠“暂存刚刚访问过的数据”,还要使用硬件实现的指令预测与数据预取技术——尽可能把将要使用的数据预先从内存中取到高速缓存里。
CPU的高速缓存曾经是用在超级计算机上的一种高级技术,不过现今计算机上使用的的AMD或Intel微处理器都在芯片内部集成了大小不等的数据高速缓存和指令高速缓存,通称为L1高速缓存(L1Cache即Level 1 On-dieCache,第一级片上高速缓冲存储器);而比L1更大容量的L2高速缓存曾经被放在CPU外部(主板或者CPU接口卡上),但是现在已经成为CPU内部的标准组件;更昂贵的CPU会配备比L2高速缓存还要大的L3高速缓存(level 3 On-die Cache第三级高速缓冲存储器)。
概念的扩充
如今高速缓存的概念已被扩充,不仅在CPU和主内存之间有Cache,而且在内存和硬盘之间也有Cache(磁盘缓存),乃至在硬盘与网络之间也有某种意义上的Cache——称为Internet临时文件夹或网络内容缓存等。凡是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为Cache。
地址镜像与变换
由于主存容量远大于CPU高速缓存的容量,因此两者之间就必须按一定的规则对应起来。地址镜像就是指按某种规则把主存块装入高速缓存中。地址变换是指当按某种镜像方式把主存块装入高速缓存后,每次访问CPU高速缓存时,如何把主存的物理地址(Physical address)或虚拟地址(Virtual address)变换成CPU高速缓存的地址,从而访问其中的数据。
缓存置换策略
主存容量远大于CPU高速缓存,磁盘容量远大于主存,因此无论是哪一层次的缓存都面临一个同样的问题:当容量有限的缓存的空闲空间全部用完后,又有新的内容需要添加进缓存时,如何挑选并舍弃原有的部分内容,从而腾出空间放入这些新的内容。解决这个问题的算法有几种,如最久未使用算法(LRU)、先进先出算法(FIFO)、最近最少使用算法(LFU)、非最近使用算法(NMRU)等,这些算法在不同层次的缓存上执行时拥有不同的效率和代价,需根据具体场合选择最合适的一种。
Guava学习笔记:Guava cache
缓存,在我们日常开发中是必不可少的一种解决性能问题的方法。简单的说,cache 就是为了提升系统性能而开辟的一块内存空间。
缓存的主要作用是暂时在内存中保存业务系统的数据处理结果,并且等待下次访问使用。在日常开发的很多场合,由于受限于硬盘IO的性能或者我们自身业务系统的数据处理和获取可能非常费时,当我们发现我们的系统这个数据请求量很大的时候,频繁的IO和频繁的逻辑处理会导致硬盘和CPU资源的瓶颈出现。缓存的作用就是将这些来自不易的数据保存在内存中,当有其他线程或者客户端需要查询相同的数据资源时,直接从缓存的内存块中返回数据,这样不但可以提高系统的响应时间,同时也可以节省对这些数据的处理流程的资源消耗,整体上来说,系统性能会有大大的提升。
缓存在很多系统和架构中都用广泛的应用,例如:
1.CPU缓存 2.操作系统缓存 3.本地缓存 4.分布式缓存 5.HTTP缓存 6.数据库缓存 等等,可以说在计算机和网络领域,缓存无处不在。可以这么说,只要有硬件性能不对等,涉及到网络传输的地方都会有缓存的身影。
Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。整体上来说Guava cache 是本地缓存的不二之选,简单易用,性能好。
Guava Cache有两种创建方式:
1. cacheLoader 2. callable callback
通过这两种方法创建的cache,和通常用map来缓存的做法比,不同在于,这两种方法都实现了一种逻辑——从缓存中取key X的值,如果该值已经缓存过了,则返回缓存中的值,如果没有缓存过,可以通过某个方法来获取这个值。但不同的在于cacheloader的定义比较宽泛,是针对整个cache定义的,可以认为是统一的根据key值load value的方法。而callable的方式较为灵活,允许你在get的时候指定。
cacheLoader方式实现实例:
@Test
public void TestLoadingCache() throws Exception{
LoadingCache<String,String> cahceBuilder=CacheBuilder
.newBuilder()
.build(new CacheLoader<String, String>(){
@Override
public String load(String key) throws Exception {
String strProValue="hello "+key+"!";
return strProValue;
}
});
System.out.println("jerry value:"+cahceBuilder.apply("jerry"));
System.out.println("jerry value:"+cahceBuilder.get("jerry"));
System.out.println("peida value:"+cahceBuilder.get("peida"));
System.out.println("peida value:"+cahceBuilder.apply("peida"));
System.out.println("lisa value:"+cahceBuilder.apply("lisa"));
cahceBuilder.put("harry", "ssdded");
System.out.println("harry value:"+cahceBuilder.get("harry"));
}
输出:
jerry value:hello jerry!
jerry value:hello jerry!
peida value:hello peida!
peida value:hello peida!
lisa value:hello lisa!
harry value:ssdded
callable callback的实现:
@Test
public void testcallableCache()throws Exception{
Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();
String resultVal = cache.get("jerry", new Callable<String>() {
public String call() {
String strProValue="hello "+"jerry"+"!";
return strProValue;
}
});
System.out.println("jerry value : " + resultVal);
resultVal = cache.get("peida", new Callable<String>() {
public String call() {
String strProValue="hello "+"peida"+"!";
return strProValue;
}
});
System.out.println("peida value : " + resultVal);
}
输出:
jerry value : hello jerry!
peida value : hello peida!
cache的参数说明:
回收的参数: 1. 大小的设置:CacheBuilder.maximumSize(long) CacheBuilder.weigher(Weigher) CacheBuilder.maxumumWeigher(long) 2. 时间:expireAfterAccess(long, TimeUnit) expireAfterWrite(long, TimeUnit) 3. 引用:CacheBuilder.weakKeys() CacheBuilder.weakValues() CacheBuilder.softValues() 4. 明确的删除:invalidate(key) invalidateAll(keys) invalidateAll() 5. 删除监听器:CacheBuilder.removalListener(RemovalListener)
refresh机制: 1. LoadingCache.refresh(K) 在生成新的value的时候,旧的value依然会被使用。 2. CacheLoader.reload(K, V) 生成新的value过程中允许使用旧的value 3. CacheBuilder.refreshAfterWrite(long, TimeUnit) 自动刷新cache
基于泛型的实现:
/**
* 不需要延迟处理(泛型的方式封装)
* @return
*/
public <K , V> LoadingCache<K , V> cached(CacheLoader<K , V> cacheLoader) {
LoadingCache<K , V> cache = CacheBuilder
.newBuilder()
.maximumSize(2)
.weakKeys()
.softValues()
.refreshAfterWrite(120, TimeUnit.SECONDS)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(new RemovalListener<K, V>(){
@Override
public void onRemoval(RemovalNotification<K, V> rn) {
System.out.println(rn.getKey()+"被移除");
}})
.build(cacheLoader);
return cache;
}
/**
* 通过key获取value
* 调用方式 commonCache.get(key) ; return String
* @param key
* @return
* @throws Exception
*/
public LoadingCache<String , String> commonCache(final String key) throws Exception{
LoadingCache<String , String> commonCache= cached(new CacheLoader<String , String>(){
@Override
public String load(String key) throws Exception {
return "hello "+key+"!";
}
});
return commonCache;
}
@Test
public void testCache() throws Exception{
LoadingCache<String , String> commonCache=commonCache("peida");
System.out.println("peida:"+commonCache.get("peida"));
commonCache.apply("harry");
System.out.println("harry:"+commonCache.get("harry"));
commonCache.apply("lisa");
System.out.println("lisa:"+commonCache.get("lisa"));
}
输出:
peida:hello peida!
harry:hello harry!
peida被移除
lisa:hello lisa!
基于泛型的Callable Cache实现:
private static Cache<String, String> cacheFormCallable = null;
/**
* 对需要延迟处理的可以采用这个机制;(泛型的方式封装)
* @param <K>
* @param <V>
* @param key
* @param callable
* @return V
* @throws Exception
*/
public static <K,V> Cache<K , V> callableCached() throws Exception {
Cache<K, V> cache = CacheBuilder
.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
return cache;
}
private String getCallableCache(final String userName) {
try {
//Callable只有在缓存值不存在时,才会调用
return cacheFormCallable.get(userName, new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(userName+" from db");
return "hello "+userName+"!";
}
});
} catch (ExecutionException e) {
e.printStackTrace();
return null;
}
}
@Test
public void testCallableCache() throws Exception{
final String u1name = "peida";
final String u2name = "jerry";
final String u3name = "lisa";
cacheFormCallable=callableCached();
System.out.println("peida:"+getCallableCache(u1name));
System.out.println("jerry:"+getCallableCache(u2name));
System.out.println("lisa:"+getCallableCache(u3name));
System.out.println("peida:"+getCallableCache(u1name));
}
输出:
peida from db
peida:hello peida!
jerry from db
jerry:hello jerry!
lisa from db
lisa:hello lisa!
peida:hello peida!
getCallableCache(u1name)直接返回缓存中的值
guava Cache数据移除:
guava做cache时候数据的移除方式,在guava中数据的移除分为被动移除和主动移除两种。 被动移除数据的方式,guava默认提供了三种方式: 1.基于大小的移除:看字面意思就知道就是按照缓存的大小来移除,如果即将到达指定的大小,那就会把不常用的键值对从cache中移除。 定义的方式一般为 CacheBuilder.maximumSize(long),还有一种一种可以算权重的方法,个人认为实际使用中不太用到。就这个常用的来看有几个注意点, 其一,这个size指的是cache中的条目数,不是内存大小或是其他; 其二,并不是完全到了指定的size系统才开始移除不常用的数据的,而是接近这个size的时候系统就会开始做移除的动作; 其三,如果一个键值对已经从缓存中被移除了,你再次请求访问的时候,如果cachebuild是使用cacheloader方式的,那依然还是会从cacheloader中再取一次值,如果这样还没有,就会抛出异常 2.基于时间的移除:guava提供了两个基于时间移除的方法 expireAfterAccess(long, TimeUnit) 这个方法是根据某个键值对最后一次访问之后多少时间后移除 expireAfterWrite(long, TimeUnit) 这个方法是根据某个键值对被创建或值被替换后多少时间移除 3.基于引用的移除: 这种移除方式主要是基于java的垃圾回收机制,根据键或者值的引用关系决定移除 主动移除数据方式,主动移除有三种方法: 1.单独移除用 Cache.invalidate(key) 2.批量移除用 Cache.invalidateAll(keys) 3.移除所有用 Cache.invalidateAll() 如果需要在移除数据的时候有所动作还可以定义Removal Listener,但是有点需要注意的是默认Removal Listener中的行为是和移除动作同步执行的,如果需要改成异步形式,可以考虑使用RemovalListeners.asynchronous(RemovalListener, Executor)
Guava学习笔记目录
Guava 是一个 Google 的基于java1.6的类库集合的扩展项目,包括 collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, 等等. 这些高质量的 API 可以使你的JAVa代码更加优雅,更加简洁,让你工作更加轻松愉悦。下面是学习过程中的一些笔记和知识点的记录。
1.Guava学习笔记:Google Guava 类库简介
2.Guava学习笔记:Optional优雅的使用null
3.Guava学习笔记:Preconditions优雅的检验参数
4.Guava学习笔记:复写的Object常用方法
5.Guava学习笔记:简化异常处理的Throwables类
6.Guava学习笔记:Immutable(不可变)集合
7.Guava学习笔记:Guava新增集合类型-Multiset
8.Guava学习笔记:Guava新增集合类型-Multimap
9.Guava学习笔记:Guava新增集合类型-Bimap
10.Guava学习笔记:Guava新集合-Table等
11.Guava学习笔记:Guava cache
12.Guava学习笔记:EventBus
13.Guava学习笔记:Range
学习过程中的参考资料。感谢分享的网友们。
1.http://www.letonlife.com/
2.http://vipcowrie.iteye.com/
3.http://blog.sina.com.cn/u/2128988613
4.http://buru.iteye.com/
5.
Guava缓存器源码分析——CacheBuilder
CacheBuilder作为LoadingCache 与 Cache实例的创建者,具有以下特征:
1、自动载入键值至缓存;
2、当缓存器溢出时,采用最近最少使用原则进行替换。
3、过期规则可基于最后读写时间。
4、设置键值引用级别。
5、元素移出通知。
6、缓存访问统计。
示例:
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(10)
.expireAfterWrite(10, TimeUnit.SECONDS)
.build(
new CacheLoader<String, Integer>() {
public Integer load(String key) throws Exception {
return loadKey(key);
}
});
例子中设定了缓存大小为10,写后10秒过期,命中失败时会通过load方法将查询键值加入缓存。
缓存的移出策略主要有以下几种:
1)基于缓存权重
LoadingCache<String, Integer> loadingCache = CacheBuilder.newBuilder()
.maximumWeight(10)
.weigher(new Weigher<String, Integer>() {
public int weigh(String k, Integer v) {
return v; //v的权重设为其本身;
}
})
.recordStats()
.build(
new CacheLoader<String, Integer>() {
public Integer load(String key) {
return num++; //num初始值为1;
}
});
System.out.println(loadingCache.get("a"));
System.out.println(loadingCache.get("b"));
System.out.println(loadingCache.get("c"));
System.out.println(loadingCache.get("d"));
System.out.println(loadingCache.get("a"));
输出结果为1 2 3 4 1,当设置为maximumWeight(9)时,输出结果即为1 2 3 4 5,因为在get("d")时,权重值和已超过最大值9,a被移出,get("a")时,需重新加载,此时num为5。
2)基于时间
expireAfterAccess(long, TimeUnit):最后一次访问后的一段时间移出;
expireAfterWrite(long, TimeUnit) :最后一次写入后的一段时间移出;
3 ) 基于引用
键、值默认都是强引用,但键可设置弱引用(weakKeys),值可设置弱(weakValues)或软引用(softValues) 。
强引用:如果一个对象具有强引用,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用:如果一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
弱引用:在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
通过recordStats()函数,还可开启缓存的访问统计,通过调用status()方法,返回包含统计信息的CacheStats对象,可以获取缓存的很多统计信息:hitCount(命中成功次数),missCount(命中失败次数),loadSuccessCount(载入成功次数),loadExceptionCount(载入失败次数),totalLoadTime(总载入时间),evictionCount(移除次数),requestCount() (访问次数),hitRate()(命中成功率),missRate()(命中失败率),loadCount()(载入次数)等。