文章7 | 阅读 2763 | 点赞0
缓存是日常开发中使用很频繁的一种提升性能的方式,它其实解决的是硬件层面性能不对等的问题,比如CPU、内存、硬盘之间性能的巨大差异,会严重影响数据的读取与传输,而缓存就是用来平衡这种性能差异的手段
缓存在很多系统和架构中广泛使用,如:
日常开发中的使用场景
在我们日常的开发中,当多次获取同一份数据而数据变化不频繁时,我们可以考虑使用缓存。
这里用到的就是内存和硬盘的性能不对等,我们知道内存的读取速度很快,而硬盘相对来说比较慢
而传统的关系型数据库把数据存储在硬盘上,这样在高并发读写的场景下,就会出现性能瓶颈
这个时候,如果在数据库前面加一层缓存,把数据库里面的热点数据缓存一份到内存中,读取的时候直接从内存中取,这样就可以大大的提升读取的性能
Guava中的缓存是本地缓存的实现,与ConcurrentMap相似,但不完全一样。最基本的区别就是,ConcurrentMap会一直保存添加进去的元素,除非你主动remove掉。而Guava Cache为了限制内存的使用,通常都会设置自动回收
Guava Cache的使用场景:
示例代码
@Test
public void cacheCreateTest(){
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(100) //设置缓存最大容量
.expireAfterWrite(1,TimeUnit.MINUTES) //过期策略,写入一分钟后过期
.build();
cache.put("a","a1");
String value = cache.getIfPresent("a");
}
Cache是Guava提供的最基本缓存接口,创建一个Cache很简单
@Test
public void cacheCreateTest(){
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(100) //设置缓存最大容量
.expireAfterWrite(1,TimeUnit.MINUTES) //过期策略,写入一分钟后过期
.build();
cache.put("a","a1");
String value = cache.getIfPresent("a");
}
Cache是通过CacheBuilder对象来build出来的,build之前可以设置一系列的参数
LoadingCache继承自Cache,当从缓存中读取某个key时,假如没有读取到值,LoadingCache会自动进行加载数据到缓存
@Test
public void loadingCacheTest() throws ExecutionException {
LoadingCache<String,String> loadingCache = CacheBuilder.newBuilder()
.maximumSize(3)
.refreshAfterWrite(Duration.ofMillis(10))//10分钟后刷新缓存的数据
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
Thread.sleep(1000);
System.out.println(key + " load data");
return key + " add value";
}
});
System.out.println(loadingCache.get("a"));
System.out.println(loadingCache.get("b"));
}
运行结果:
a load data
a add value
b load data
b add value
LoadingCache也是通过CacheBuilder创建出来的,只不过创建的时候,需要在build方法里面传入CacheLoader
CacheLoader类的load方法就是在key找不到的情况下,进行数据自动加载的
下面我们看一下Guava Cache在使用时常用的属性,下面的属性对Cache和LoadingCache都适用
1、容量初始化
public void initialCapacityTest(){
Cache<String,String> cache = CacheBuilder.newBuilder()
.initialCapacity(1024) //初始容量
.build();
}
2、最大容量
最大容量可以通过两种维度来设置
@Test
public void maxSizeTest(){
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(2)//缓存最大个数
.build();
cache.put("a","1");
cache.put("b","2");
cache.put("c","3");
System.out.println(cache.getIfPresent("a"));
System.out.println(cache.getIfPresent("b"));
System.out.println(cache.getIfPresent("c"));
Cache<String,String> cache1 = CacheBuilder.newBuilder()
.maximumWeight(1024 * 1024 * 1024)//最大容量为1M
//用来计算容量的Weigher
.weigher(new Weigher<String, String>() {
@Override
public int weigh(String key, String value) {
return key.getBytes().length + value.getBytes().length;
}
})
.build();
cache1.put("x","1");
cache1.put("y","2");
cache1.put("z","3");
System.out.println(cache1.getIfPresent("x"));
System.out.println(cache1.getIfPresent("y"));
System.out.println(cache1.getIfPresent("z"));
}
运行结果:
null
2
3
1
2
3
我们设置缓存的最大记录为2,当我们添加三个元素进去后,会把前面添加的元素覆盖
3、过期时间
@Test
public void expireTest() throws InterruptedException {
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(100)//缓存最大个数
.expireAfterWrite(5,TimeUnit.SECONDS)//写入后5分钟过期
.build();
cache.put("a","1");
int i = 1;
while(true){
System.out.println("第" + i + "秒获取到的数据为:" + cache.getIfPresent("a"));
i++;
Thread.sleep(1000);
}
}
运行结果
第1秒获取到的数据为:1
第2秒获取到的数据为:1
第3秒获取到的数据为:1
第4秒获取到的数据为:1
第5秒获取到的数据为:1
第6秒获取到的数据为:null
第7秒获取到的数据为:null
第8秒获取到的数据为:null
第9秒获取到的数据为:null
从运行结果可以看出来,写入数据后的第6秒就开始获取不到数据了
@Test
public void expireAfterAccessTest() throws InterruptedException {
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(100)//缓存最大个数
.expireAfterAccess(5,TimeUnit.SECONDS)//5秒没有被访问,就过期
.build();
cache.put("a","1");
Thread.sleep(3000);
System.out.println("休眠3秒后访问:" + cache.getIfPresent("a"));
Thread.sleep(4000);
System.out.println("休眠4秒后访问:" + cache.getIfPresent("a"));
Thread.sleep(5000);
System.out.println("休眠5秒后访问:" + cache.getIfPresent("a"));
}
运行结果:
休眠3秒后访问:1
休眠4秒后访问:1
休眠5秒后访问:null
从运行结果可以看出,只要超过了设定的时间没有人访问,缓存的数据就会过期
在上面我们讲了两种回收策略
Guava Cache还支持基于引用级别的回收,这种回收策略是Java特有的,在Java的对象回收机制中,按对象的强弱可以分为强引用、软引用、弱引用、虚引用
强引用
强引用是我们最常用的引用,比如我们直接new一个对象,就是一个强引用
Map<String,String> map = new HashMap<String,String>();
强引用不会被自动回收,当内存不足时直接报内存溢出
软引用
软引用是一种不稳定的引用方式,如果一个对象具有软引用,当内存充足时,GC不会主动回收软引用对象,而当内存不足时软引用对象就会被回收
SoftReference<Object> softRef=new SoftReference<Object>(new Object()); // 软引用
Object object = softRef.get(); // 获取软引用
弱引用
弱引用是一种比软引用更不稳定的引用方式,因为无论内存是否充足,弱引用对象都有可能被回收
WeakReference<Object> weakRef = new WeakReference<Object>(new Object());
Object obj = weakRef.get(); // 获取弱引用
虚引用
虚引用这种引用方式就是形同虚设,因为如果一个对象仅持有虚引用,那么它就和没有任何引用一样。在实践中也几乎没有使用
在Guava中支持软/弱引用的回收方式
Cache<String,String> cache = CacheBuilder.newBuilder()
.weakKeys() //使用弱引用存储键。当键没有其它(强或软)引用时,该缓存可能会被回收。
.weakValues() //使用弱引用存储值。当值没有其它(强或软)引用时,该缓存可能会被回收。
.softValues() //使用软引用存储值。当内存不足并且该值其它强引用引用时,该缓存就会被回收
.build();
手动回收
上面讲的都是缓存自动回收的策略,我们也可以调用Guava Cache提供的方法来手动清除
可以删除单个key,也可以批量删除key,同时也可以清除整个缓存的数据(谨慎使用哦~)
@Test
public void invalidateTest(){
Cache<String,String> cache = CacheBuilder.newBuilder().build();
cache.put("a","1");
cache.put("b","2");
//从缓存中清除key为a的数据
cache.invalidate("a");
System.out.println(cache.getIfPresent("a"));
cache.put("x","x1");
cache.put("y","y1");
System.out.println("x清除之前:"+ cache.getIfPresent("x"));
System.out.println("y清除之前:"+ cache.getIfPresent("y"));
List<String> list = Lists.newArrayList("x","y");
//批量清除
cache.invalidateAll(list);
System.out.println("x清除之后:"+ cache.getIfPresent("x"));
System.out.println("y清除之后:"+ cache.getIfPresent("y"));
cache.put("y","y1");
cache.put("z","z1");
//清空缓存所有的数据
cache.invalidateAll();
System.out.println("全部清除后:" + cache.getIfPresent("y"));
System.out.println("全部清除后:" + cache.getIfPresent("z"));
}
运行结果:
null
x清除之前:x1
y清除之前:y1
x清除之后:null
y清除之后:null
全部清除后:null
全部清除后:null
可以给Cache中的对象加一个监听,当有对象被删除时会有事件通知
@Test
public void removeListenerTest() throws InterruptedException {
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(3)
.expireAfterWrite(Duration.ofSeconds(5))//5秒后自动过期
//添加一个remove的监听器
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
System.out.println("[" +notification.getKey() + ":" + notification.getValue() + "] 被删除了");
}
})
.build();
cache.put("a","1");
Thread.sleep(2000);
cache.put("b","2");
cache.put("c","3");
Thread.sleep(2000);
cache.put("d","4");
Thread.sleep(5000);
cache.put("e","5");
cache.invalidate("e");
}
运行结果:
[a:1] 被删除了
[b:2] 被删除了
[c:3] 被删除了
[d:4] 被删除了
[e:5] 被删除了
创建Cache时removalListener方法传入一个RemovalListener对象,重写onRemoval方法来进行数据清除事件的监听
从运行结果可以看出来,三种情况下的清除数据都会被监听
我们在使用缓存的时候,应该要关心缓存的命中率、加载数据时间等等信息,可以根据这些统计信息来对缓存进行优化调整,让缓存更好的为我们服务。在构建缓存对象时,可以开启统计信息,开启后会对缓存的操作进行统计
@Test
public void recordStatsTest(){
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(3)
.recordStats()
.build();
cache.put("a","1");
cache.put("b","2");
cache.put("c","3");
cache.put("d","4");
cache.put("e","5");
cache.put("f","6");
cache.getIfPresent("a");
cache.getIfPresent("a");
cache.getIfPresent("e");
cache.getIfPresent("f");
cache.getIfPresent("h");
cache.getIfPresent("t");
System.out.println(cache.stats());
}
运行结果:
CacheStats{hitCount=2, missCount=4, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=3}
参考:
https://www.cnblogs.com/fnlingnzb-learner/p/11022152.html
https://blog.csdn.net/l1028386804/article/details/102764951
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/pzjtian/article/details/106797867
内容来源于网络,如有侵权,请联系作者删除!