Guava Cache介绍-面试用

news2024/11/20 14:44:18

一、Guava Cache简介

1、简介

Guava Cache是本地缓存,数据读写都在一个进程内,相对于分布式缓存redis,不需要网络传输的过程,访问速度很快,同时也受到 JVM 内存的制约,无法在数据量较多的场景下使用。

基于以上特点,本地缓存的主要应用场景为以下几种:

  1. 对于访问速度有较大要求
  2. 存储的数据不经常变化
  3. 数据量不大,占用内存较小
  4. 需要访问整个集合
  5. 能够容忍数据不是实时的

2、对比

二、Guava Cache使用

下面介绍如何使用
1、先引入jar

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>27.0-jre</version>
</dependency>

案例1

1、创建Cache对象,在使用中,我们只需要操作loadingCache对象就可以了。

LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder()
        .initialCapacity(5)//内部哈希表的最小容量,也就是cache的初始容量。
        .concurrencyLevel(3)//并发等级,也可以定义为同时操作缓存的线程数,这个影响segment的数组长度,原理是当前数组长度为1如果小于并发等级且素组长度乘以20小于最大缓存数也就是10000,那么数组长度就+1,依次循环
        .maximumSize(10000)//cache的最大缓存数。应该是数组长度+链表上所有的元素的总数
        .expireAfterWrite(20L, TimeUnit.SECONDS)//过期时间,过期就会触发load方法
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) {
                //缓存不存在,会进到load方法,该方法返回值就是最终要缓存的数据。
                log.info("进入load缓存");
                return "手机号";
            }
        });

2、通过缓存获取数据

//获取缓存,如果数据不存在,触发load方法。
loadingCache.get(key);

案例2:使用reload功能

1、生成缓存对象

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());

LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder()
        .initialCapacity(5)
        .concurrencyLevel(3)
        .maximumSize(10000)
        .expireAfterWrite(20L, TimeUnit.SECONDS)//超这个时间,触发的是load方法
        .refreshAfterWrite(5L, TimeUnit.SECONDS) //刷新,超过触发的是reload方法
        //.expireAfterAccess(...): //当缓存项在指定的时间段内没有被读或写就会被回收。
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) {
                //缓存不存在或者缓存超过expireAfterWrite设置的时间,进到load方法
                log.info("进入load缓存");
                return "手机号";
            }

            @Override
            public ListenableFuture<String> reload(String key, String oldValue) throws Exception {
                //超过refreshAfterWrite时间,但是没有超过expireAfterWrite时间,进到reload方法
                log.info("进入reload缓存");
				
                //这里是异步执行任务
                return executorService.submit(() ->  {
                    Thread.sleep(1000L);
                    return "relad手机号";
                });
            }
        });

1、expireAfterWrite、refreshAfterWrite可以同时一起使用当然,不同组合应对不同场景。
2、需要说明,当缓存时间当超过refreshAfterWrite的时间,但是小于expireAfterWrite设置的时间,请求进来执行的是reload方法,当时间超过expireAfterWrite时间,那么执行的是load方法。

2、使用缓存对象

String value = loadingCache.get(key); //获取缓存
loadingCache.invalidate(key); //删除具体某个key的缓存
loadingCache.invalidateAll(Arrays.asList("key1","key2","key3"));//删除多个
loadingCache.invalidateAll(); //删除所有

三、源码

缓存对象底层是LocalLoadingCache类,里面有个很重要的属性segments,缓存数据都存在这个里面

//1、缓存对象
LocalLoadingCache{
	//segments是一个数组,每一个元素都是Segment类型
	final Segment<K, V>[] segments;	
}

//2、下面介绍下Segment这个类有哪些重要的属性
class Segment<K, V> extends ReentrantLock{ //继承了重入锁
	//首先Segment里面有一个属性table,这个table是AtomicReferenceArray类型
	AtomicReferenceArray<ReferenceEntry<K, V>> table;
}

//3、下面看下AtomicReferenceArray到底有什么
AtomicReferenceArray{
	//其实就是包裹了一个数组。每个元素都ReferenceEntry类型
	private final Object[] array;
}

AtomicReferenceArray特别之处在于下

提供了可以原子读取、写入,底层引用数组的操作,并且还包含高级原子操作。比较特别的就是put操作,就是我们在给该数组某个元素设置值的时候可以使用比较的方式来设置值。
例如:AtomicReferenceArray.compareAndSet(2,10,20)
2下标位置,10是新的值,20是原来期望的值,只有原来的值为20才会更新为10。

在回到上面AtomicReferenceArray里面的属性array里面每一个元素都是ReferenceEntry类型,ReferenceEntry的实现类是StrongAccessWriteEntry

StrongAccessWriteEntry{
	final K key;
	//value存到了ValueReference对象里面,只是ValueReference包装了一下,这个在并发的时候会用到。
	volatile ValueReference<K, V> value; 
	final int hash;
	final ReferenceEntry<K, V> next;    //针对hash碰撞时拓展的链表。	
}

结构图如下:

四、下面介绍常用功能及其原理

1、获取数据

1、通过key生成hash,根据hash从segments这个数组中得到具体下标,该元素是Segment类型
2、从Segment里面的table里面获取,也就是AtomicReferenceArray的array从里面获取数据,此时拿到的是一个key所对应的StrongAccessWriteEntry对象。
3、StrongAccessWriteEntry里面会存下该hash碰撞所对应的其他key-value数据集合,StrongAccessWriteEntry对象保存了这个元素所对应的hash,key,和value,next,还有过期时间,如果过期了也会返回return, 如果没有过期,会进行key对比,只有一致才会返回。
4、获取的时候,如果数据不存在就会调用下面的put方法。获取数据时是不使用lock()的。

2、put数据

在put前先lock(),为什么可以使用锁,因为继承了ReentrantLock

Segment<K, V> extends ReentrantLock {
	.....
}

首先是通过Load方法拿到数据,拿到后再通过storeLoadedValue方法来把结果写到缓存数据里面去,在写入的时候,也是用到了锁lock(); 所以这里就双重锁了。
先是通过hash结合算法,得到下标,在根据下标从AtomicReferenceArray数组中获取元素,那么这个元素ReferenceEntry是一个有next的链表,所以我们要遍历,这个链表,如果有key一致的,我么就要把他覆盖掉。如果没有就使用set方法设置值。

3、删除数据

删除数据也是一样,先lock();
拿到找到这个元素所在的位置,然后删除掉

4、过期策略(重点)

过期配置主要包含着三个expireAfterWrite、refreshAfterWrite、expireAfterAccess,下面分别介绍下这个三个作用

expireAfterWrite:当缓存项在指定时间后就会被回收(主动),需要等待获取新值才会返回。
expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。
refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。第一个请求进来,执行load把数据加载到内存中(同步过程),指定的过期时间内比如10秒,都是从cache里读取数据。过了10秒后,没有请求进来,不会移除key。再有请求过来,才则执行reload,在后台异步刷新的过程中,如果当前是刷新状态,访问到的是旧值。刷新过程中只有一个线程在执行刷新操作,不会出现多个线程同时刷新同一个key的缓存。在吞吐量很低的情况下,如很长一段时间内没有请求,再次请求有可能会得到一个旧值(这个旧值可能来自于很长时间之前),这将会引发问题。(可以使用expireAfterWrite和refreshAfterWrite搭配使用解决这个问题)

//是否更新过期时间判断条件
void recordWrite(ReferenceEntry<K, V> entry, int weight, long now) {
    // we are already under lock, so drain the recency queue immediately
    drainRecencyQueue();
    totalWeight += weight;

    if (map.recordsAccess()) {    //这个判断条件是expireAfterAccess>0
        entry.setAccessTime(now);   //设置过期时间
    }
    if (map.recordsWrite()) {        //这个判断条件是expireAfterWrite>0
        entry.setWriteTime(now);   //设置过期时间
    }
    accessQueue.add(entry);
    writeQueue.add(entry);
}

后续每次读的时候,如果存在设置了expireAfterAccess,就会每次把过期时间更新掉。

if (map.recordsAccess()) {
    entry.setAccessTime(now);
}

那么在获取数据的时候,是如何判断是否执行reload的,源码里面有明确的判断

V scheduleRefresh(ReferenceEntry<K, V> entry, K key, int hash, V oldValue, long now, CacheLoader<? super K, V> loader) {
    if (map.refreshes() && (now - entry.getWriteTime() > map.refreshNanos)) {
        V newValue = refresh(key, hash, loader, true);
    }
}
//map.refreshes()主要是判断 refreshNanos > 0
//也就是代码中build的refreshAfterWrite(2L, TimeUnit.MINUTES)代码。设置了refreshNanos的时间。

场景1: 当前时间减去缓存到期时间结果大于过期时间,才会执行refresh方法,就会从reload里面获取数据。
场景2:当然还有一种情况就是既设置了refreshAfterWrite,又设置了expireAfterWrite,这个情况是,优先判断,数据是否过期了,如果并且过期时间超了,那么就执行load方法,如果没有超过过期时间,超过了refresh的过期时间,那么就执行reload方法,代码如下

//判断是否过期
if (map.isExpired(entry, now)) {
    return null;
}
//看下isExpired的逻辑
boolean isExpired(ReferenceEntry<K, V> entry, long now) {
	//这里忽略
    if (expiresAfterAccess() && (now - entry.getAccessTime() >= expireAfterAccessNanos)) {
        return true;
    }
    //判断是否设置了expireAfterWriteNanos时间,且当前时间减去过期时间是否超过expireAfterWriteNanos,超过则说明数据已经过期了。
    if (expiresAfterWrite() && (now - entry.getWriteTime() >= expireAfterWriteNanos)) {
        return true;
    }
    return false;
}

2、解决缓存击穿

缓存击穿:假如在缓存过期的那一瞬间,有大量的并发请求过来。他们都会因缓存失效而去加载执行db操作,可能会给db造成毁灭性打击。

解决方案: 采用expireAfterWrite+refreshAfterWrite 组合设置来防止缓存击穿,expire则通过一个加锁的方式,只允许一个线程去回源,有效防止了缓存击穿,但是可以从源代码看出,在有效防止缓存击穿的同时,会发现多线程的请求同样key的情况下,一部分线程在waitforvalue,而另一部分线程在reentantloack的阻塞中。

//当数据过期了,先拿到数据的状态,如果是正在执行load方法,则其他线程就先等待,
ValueReference<K, V> valueReference = e.getValueReference();
if (valueReference.isLoading()) {
    return waitForLoadingValue(e, key, valueReference);
}
//关键在于valueReference对象,他有2种两类,不同类型代表不同状态。

1、一开始创建的时候valueReference是LoadingValueReference类型对象。这个在刚创建entity的时候会用到。也就是load方法被执行前LoadingValueReference固定是返回true

2、当load方法被加载完valueReference类型就变成StrongValueReference。load执行完后,更新entity的类型。StrongValueReference的isLoading方法固定是false
3、当数据过期时

3、刷新时拿到的不一定是最新数据

因为如果因为过期执行刷新的方法也就是reload方法,那么从缓存里面拿到的数据不一定是新数据,可能是老数据,为什么,因为刷新时异步触发reload,不像load同步这种,源码如果reload的返回null,那么会优先使用oldValue数据。

V scheduleRefresh(ReferenceEntry<K, V> entry, K key, int hash, V oldValue, long now, CacheLoader<? super K, V> loader) {
    if (map.refreshes() && (now - entry.getWriteTime() > map.refreshNanos)) {
        V newValue = refresh(key, hash, loader, true); //执行刷新的方法,也就reload方法,下面看下refresh做了什么操作
        if (newValue != null) {
            return newValue;
        }
    }
    return oldValue;
}

refresh(K key, int hash, CacheLoader<? super K, V> loader, boolean checkTime) {
    //通过异步去执行reload方法,注意是异步,此时没有完成,那么直接返回null,那么上面的scheduleRefresh方法直接返回的是oldValue,也就是老数据。
    ListenableFuture<V> result = loadAsync(key, hash, loadingValueReference, loader);
    if (result.isDone()) {
        try {
            return Uninterruptibles.getUninterruptibly(result);
        } catch (Throwable t) {

        }
    }
    return null;
}

所以缓存失效第一次数据不一定是最新的数据。可能是老的数据,因为是异步执行reload方法不知道耗时会有多久,所以主线程不会一直去等子线程完成。关注下,主线程在子线程执行reload会等多久?

4、总结

1、refreshAfterWrites是异步去刷新缓存的方法,可能会使用过期的旧值快速响应。

2、expireAfterWrites缓存失效后线程需要同步等待加载结果,可能会造成请求大量堆积的问题。

四、注意点

在重写load的时候,如果数据是空要写成"",不能是null,因为在put的时候,会判断返回的值如果是null就会抛出下面异常

@Override
public String load(String key) {
    return ...
}

//当load返回为空时会抛出异常
if (value == null) {
  throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + ".");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1031762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

按文件大小批量分类保存,高效管理你的文件!

作为一名文件管理者&#xff0c;你是否经常为如何高效地管理大量文件而烦恼&#xff1f;现在&#xff0c;我们为你提供了一种简单、快速、准确的方法&#xff0c;帮助你轻松解决这个问题&#xff01; 首先。进入文件批量改名高手主页面&#xff0c;并在板块栏里选择“文件智能…

Android 启动优化案例:WebView非预期初始化排查

去年年底做启动优化时&#xff0c;有个比较好玩的 case 给大家分享下&#xff0c;希望大家能从我的分享里 get 到我在做一些问题排查修复时是怎么看上去又low又土又高效的。 1. 现象 在我们使用 Perfetto 进行app 启动过程性能观测时&#xff0c;在 UI 线程发现了一段 几十毫…

当语文课本上的古诗词遇上拓世AI,文生图绘就东方美学画卷

东方美学自古以来一直以其深刻的内涵和精致的表现形式&#xff0c;吸引着世界各地的文化爱好者。在中国的文学传统中&#xff0c;古诗词扮演着不可或缺的角色。诗词是我国文学宝库中的瑰宝&#xff0c;也是我们民族的文化精髓。如今随着人工智能技术的崛起&#xff0c;古诗词与…

基于微信小程序付费自习室系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明用户微信端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取 前言 &#x1f497;博主介绍&#x…

文心一言 VS 讯飞星火 VS chatgpt (98)-- 算法导论9.3 4题

四、用go语言&#xff0c;对一个包含n个元素的集合&#xff0c;假设一个算法只使用比较来确定第i小的元素&#xff0c;证明:无需额外的比较操作&#xff0c;它也能找到第 i-1 小的元素和第 n-i大的元素。 文心一言&#xff1a; 在这个问题中&#xff0c;我们要使用一个称为&q…

做影视特效本地电脑配置不够怎么办?

影视特效对电脑要求高&#xff0c;往往本地电脑配置不足的情况下&#xff0c;会导致电脑卡顿等造成工作效率低下等问题&#xff0c;再加上现在异地协同的云电脑需求越来越高&#xff0c;更多的企业和个人开始选择做影视特效的云电脑&#xff0c;那么今天就来看看租一台云电脑如…

Swift SwiftUI 修改 List 背景颜色

Preview: Code: .listRowBackground(Color(.yellow)).scrollContentBackground(.hidden) .background(.linearGradient(colors: [.white, .accentColor], startPoint: .top, endPoint: .bottom))喜欢或对你有帮助&#xff0c;点个赞吧&#xff0c;自己先点个嘿嘿。 有错误或者…

【面试题】面试官问你前端性能优化时,他想问什么?

一直以来&#xff0c;前端性能优化都是面试过程中考察的热点题目。 相关的技术博客也层不出穷&#xff0c;我们总是能找到很多这样的文章&#xff0c; 从一个应用的各个层面开始分析&#xff0c;优化的种种手段&#xff0c;取得的种种效果。 往往篇幅越长&#xff0c;讲得越…

MySQL 远程连接1130问题

通过后台进入mysql 1,切换到mysql库 2.查询user表信息 3.更新你想远程登录的用户的host信息,我这里想用root进行远程登录,所以修改如下 4.刷新权限 5.大功告成 快来和博主打成一片吧^_^

使用人工智能聊天机器人时要注意这些!(配提问技巧)

这两年ChatGPT可谓是火遍了互联网&#xff0c;在微博等社交平台上能看到很多网友晒出了与ChatGPT对话的截图&#xff0c;精准、恰当的回答让网友们都倍感新鲜。但是有很多人把人工智能聊天机器人当成玩具&#xff0c;有很多经典犯错回答&#xff0c;不少网友戏称是“人工智障”…

如何优雅的退出线程(condition_variable )

C多线程并发编程入门&#xff08;目录&#xff09; 使用条件变量来实现在析构函数中再次向线程发出一次notify&#xff0c;之后join 线程&#xff0c;等等线程安全退出。 #pragma once #include <iostream> #include <fstream> #include <string> #include …

PerformanceOne一站式性能测试平台

PerformanceOne&#xff08;简称&#xff1a;P-One&#xff09;是泽众软件自主研发的一套一站式性能测试平台软件产品。 该产品采用 B/S 架构开发&#xff0c;实现了集管理、设计、压测、监控以及分析于一体的全方位性能测试解决方案。可有效提升性能测试技术能力&#xff0c;…

拥抱数字化时代SOP电子作业指导书系统助力企业差异化竞争

在如今的竞争激烈的市场环境中&#xff0c;企业要想在同等条件下脱颖而出&#xff0c;差异化竞争成为了关键。然而&#xff0c;与硬件相比&#xff0c;软件的差异化更具有决定性的作用。而软件的差异化往往体现在细节上&#xff0c;而不是大的战略方面。而如何将这些细节进行量…

深入理解C#中委托的使用及不同类型委托的应用示例

在C#中&#xff0c;委托是一种强大而灵活的机制&#xff0c;可以引用一个或多个方法&#xff0c;并允许以类似函数指针的方式进行调用。委托在事件处理、回调函数和多线程编程等场景中非常有用。本文将深入探讨C#中委托的使用&#xff0c;并介绍不同类型委托的应用示例。 目录…

怎么解决IT运维管理痛点?运维工单系统如何提高企业运营效率?

随着企业信息化的飞速发展&#xff0c;IT系统的规模和复杂性也在迅速增长。然而&#xff0c;传统的IT运维管理方式往往难以满足现代企业的需求&#xff0c;存在着许多痛点。   首先&#xff0c;传统的运维管理方式效率低下&#xff0c;缺乏有效的IT资产管理和监控手段&#x…

高效畅通的iOS平台S5配置指南

在iOS平台上&#xff0c;使用S5代理ip访问互联网是一种非常有用的技巧。无论是为了保证隐私安全&#xff0c;还是解决网络限制问题&#xff0c;S5代理ip都能为您提供更快、更稳定的互联网访问体验。本文将为您详细介绍如何在iOS平台上配置和使用S5代理ip&#xff0c;让您的网络…

jarvisoj_level3_x64

jarvisoj_level3_x64 Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)64位&#xff0c;只开了nx ssize_t vulnerable_function() {char buf[128]; // [rsp0h] [rbp-80h] BYREFwrite(1, "Inp…

铁路设备屡遭破坏!RFID电子锁实现铁路防护网破坏实时报警管理

铁路防护网是铁路运输中保障安全的重要组成部分&#xff0c;然而&#xff0c;铁路设备被破坏的情况时有发生&#xff0c;给铁路运输带来了严重的安全隐患和经济损失。 一、铁路防护网面临的挑战 铁路防护网作为铁路运输的重要保障措施&#xff0c;时刻面临着破坏行为的威胁。…

win10 关闭edge跳转IE浏览器

按下windows键&#xff0c;搜索控制面板 右上角输入IE 点击IE 高级中取消下红框选择即可

mapbox鼠标滑过高亮要素

成果图 实现方法 这里借鉴了官网这个例子 https://docs.mapbox.com/mapbox-gl-js/example/hover-styles/ 这里是图层的样式配置&#xff0c;通过改变select的true和false&#xff0c;来控制渲染的颜色和宽度 paint: {line-opacity: 1,line-color: [case,[boolean, [feature-st…