Guava Cache介绍-面试用
一、Guava Cache简介
1、简介
Guava Cache是本地缓存,数据读写都在一个进程内,相对于分布式缓存redis,不需要网络传输的过程,访问速度很快,同时也受到 JVM 内存的制约,无法在数据量较多的场景下使用。
基于以上特点,本地缓存的主要应用场景为以下几种:
- 对于访问速度有较大要求
- 存储的数据不经常变化
- 数据量不大,占用内存较小
- 需要访问整个集合
- 能够容忍数据不是实时的
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>() {@Overridepublic 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>() {@Overridepublic String load(String key) {//缓存不存在或者缓存超过expireAfterWrite设置的时间,进到load方法log.info("进入load缓存");return "手机号";}@Overridepublic 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 immediatelydrainRecencyQueue();totalWeight += weight;if (map.recordsAccess()) { //这个判断条件是expireAfterAccess>0entry.setAccessTime(now); //设置过期时间}if (map.recordsWrite()) { //这个判断条件是expireAfterWrite>0entry.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 + ".");
}
相关文章:
Guava Cache介绍-面试用
一、Guava Cache简介 1、简介 Guava Cache是本地缓存,数据读写都在一个进程内,相对于分布式缓存redis,不需要网络传输的过程,访问速度很快,同时也受到 JVM 内存的制约,无法在数据量较多的场景下使用。 基…...
ARM 汇编指令作业(求公约数、for循环实现1-100之间和、从SVC模式切换到user模式简单写法)
1、求两个数最大公约数 .text .globl _start_start:mov r0, #9mov r1, #15 Loop: 循环cmp r0,r1 比较r0和r1的大小beq stop 当r0和r1相等时,跳到stop标签subhi r0,r0,r1 r0-r1>0 时,证明r0>r1,将r0-r1的值赋给r0&…...
Go - 【字符串,数组,哈希表】常用操作
一. 字符串 字符串长度: s : "hello" l : len(s) fmt.Println(l) // 输出 5遍历字符串: s : "hello" for i, c : range s {fmt.Printf("%d:%c ", i, c) } // 输出:0:h 1:e 2:l 3:l 4:ofor i : 0; i < le…...
vue 普通组件的 局部注册
vue 普通组件的 注册 11 Vue2_3入门到实战-配套资料\01-随堂代码素材\day03\素材\00-准备代码\小兔鲜首页静态页\src...
医疗虚拟仿真和虚拟现实有什么区别?哪个更好?
随着我们在仿真教育中越来越多地使用新技术,区分虚拟模式的类型很重要。虚拟仿真是一个统称,用来概括术语来描述各种基于仿真的体验,从基于屏幕的平台到沉浸式虚拟现实。然而,各虚拟平台在保真度、沉浸感和临场感的水平上有很大差…...
【.net core】yisha框架使用nginx代理swagger接口无法访问问题
后端代码配置 #在StartUp.cs文件中Configure方法中增加以下代码 app.UseSwagger(c >{//代理路径访问c.PreSerializeFilters.Add((doc, item) >{//根据代理服务器提供的协议、地址和路由,生成api文档服务地址doc.Servers new List<OpenApiServer>{ new…...
uniapp录音功能和音频播放功能制作
录音功能 在 UniApp 中,你可以使用 uni.getRecorderManager() 方法来创建一个录音管理器实例,从而实现录音功能。 以下是一个示例,演示了如何在 UniApp 中使用 uni.getRecorderManager() 实现录音功能: // 在需要录音的页面或组…...
服务器数据恢复-LINUX操作系统下各文件系统误删除/格式化数据的恢复方案
服务器数据恢复环境: 基于EXT2/EXT3/EXT4/Reiserfs/Xfs文件系统的Linux操作系统。 服务器故障: LINUX操作系统下误删除/格式化数据。 服务器数据恢复过程: 1、首先会检测服务器是否存在硬件故障,如果检测出硬件故障,交…...
python/C++二分查找库函数(lower_bound() 、upper_bound,bisect_left,bisect_right)
二分查找是一种经典的搜索算法,广泛应用于有序数据集中。它允许在大型数据集中高效地查找目标元素,减少了搜索的时间复杂度。本文将介绍在 C 和 Python 中内置的二分查找函数,让二分查找变得更加容易。 c lower_bound() 、upper_bound 定义…...
爬虫 — App 爬虫(二)
目录 一、Appium介绍二、node.js 安装三、Java 的 SDK 安装以及配置1、安装步骤2、配置环境变量 四、安卓环境的配置1、配置环境变量 五、Appium 安装1、安装2、打开 APP3、使用 六、Appium 使用1、定位数据(方法一,不常用)2、定位数据&#…...
汽车电子相关术语
SOA SOA(Service-Oriented Architecture,面向服务的架构)是一种在计算机环境中设计、开发、部署和管理离散模型的方法。是由Garnter1996年提出的概念,将应用程序的不同功能单元(称为服务)进行拆分…...
Python 找出最大数
"""在输入的三个数中找出最大知识点:1、条件嵌套语句if/else2.字符串分割函数split()3、列表元素索引4、数据类型转换举一反三:1、如何控制只能输入三个数,否则重新输入2、如何避免输入无效字母"""# 定义一个变…...
Spring Security 用了那么久,你对它有整体把控吗?
文章目录 1.Servlet Filter:守门人的角色2.DelegatingFilterProxy:桥接 Servlet 和 Spring 的神器3.FilterChainProxy:Spring Security 过滤器链的管家3.SecurityFilterChain:Security 过滤器的串绳4.Spring Security 中的过滤器机…...
vue+minio实现文件上传操作
vueminio实现文件上传操作 minio文件上传vueminio实现文件上传操作 minio文件上传 minio文件上传有两种方法: 第一种是通过ak,sk,调用minio的sdk putObject进行文件上传;该方法支持go,java,js等各种语言&…...
使用JavaScript实现无限滚动的方法
前言 在网页设计中,无限滚动是一种常见的交互方式,用户可持续地加载更多内容而无需刷新页面,提高用户体验。本文将介绍如何运用JavaScript实现无限滚动的效果,使网页能够自动加载更多数据,减轻用户加载新页的负担&…...
html学习综合案例1
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>个人简介</title> </head> <body>…...
神经节苷脂抗体——博迈伦
神经节苷脂抗体是指人体免疫系统中产生的一类抗体,其主要作用是攻击神经节苷脂抗原物质。神经节苷脂是一种存在于神经细胞表面的重要分子,参与了神经细胞间的信号传导和细胞黏附等重要功能。正常情况下,人体免疫系统不会对神经节苷脂产生抗体…...
【Unity】简单的深度虚化shader
【Unity】简单的深度虚化shader 实现效果 可以用于对地图场景边界的白模处理 实现方法 1.关键方法 UnityObjectToClipPos:将物体坐标转换为屏幕坐标 LinearEyeDepth:将屏幕坐标中的z值转换为实际的深度值 saturate:将值规范到0~1之间&a…...
启动 React APP 后经历了哪些过程
本文作者为 360 奇舞团前端开发工程师 前言 本文中使用的React版本为18,在摘取代码的过程中删减了部分代码,具体以源代码为准。 在React 18里,通过ReactDOM.createRoot创建根节点。并且通过调用原型链上的render来渲染。 本文主要是从以下两个…...
带自动采集小说网站源码 小说听书网站源码 小说网站源码 带教程
PTCMS可听书可下载的小说站源码 带自动采集和搭建视频教程 必装环境:Nginx(apache.iis也可),mysql,php5.6,memcached php5.6安装扩展memcache新建站点,注意新建时,PHP版本必须选择PHP5.6 安装教程 1.上传网站文件到网站目录&…...
MySQL学习笔记2
MySQL glibc版本安装: 下载相应的安装包。 学会查看mysql的官方文档: 1) 2)点击“Reference Manual”按钮: 3)选择5.7版本: 4)点击Installing MySQL on Unix/Linux Using Generic …...
【python爬虫】—星巴克产品
文章目录 需求爬取星巴克产品以及图片,星巴克菜单 python爬虫爬取结果 需求 爬取星巴克产品以及图片,星巴克菜单 网页分析: 首先,需要分析星巴克官方网站的结构,了解菜单栏的位置、布局以及菜单项的标签或类名等信息…...
算法 矩阵最长递增路径-(递归回溯+动态规划)
牛客网: BM61 求矩阵的最长递增路径 解题思路: 1. 遍历二维矩阵每个位置,max求出所有位置分别为终点时的最长路径 2. 求某个位置为终点的最长路径时,使用动态规划dp对已经计算出的位置进行记录 3. 处理某个位置的最长路径时,如果dp[i][j]位…...
四、数学建模之图与网络模型
1.定义 2.例题及软件代码求解 一、定义 1.图和网络是相关概念 (1)图(Graph):图是数学和计算机科学中的一个抽象概念,它由一组节点(顶点)和连接这些节点的边组成。图可以是有向的&…...
php在header增加key,sign,timestamp,实现鉴权
在PHP中,您可以通过在HTTP请求的Header中增加Key、Sign和Timestamp等信息来进行安全性鉴权。 以下是一种基本的思路和示例,用于说明如何实现这种鉴权机制: 生成Key和Sign: 服务端和客户端之间共享一个密钥(Key&#x…...
Spring实例化源码解析之ConfigurationClassParser(三)
前言 上一章我们分析了ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法的源码逻辑,其中核心逻辑do while中调用parser.parse(candidates)方法,解析candidates中的候选配置类。然后本章我们主要分析ConfigurationClassParser的…...
在 Substance Painter中实现Unity Standard Shader
由于有需要在Substance Painter中显示什么样的效果,在Unity就要显示什么样的效果的需求,最近研究了几天,总算在Substance Painter中实现Unity standard的材质的渲染效果。具体效果如下: 在Unity中: Substance Painte…...
第二证券:个人开证券账户要开户费吗?
随着互联网和移动端东西的遍及,越来越多的人开端涉足股票投资,开立证券账户也成为一个热门话题。但是,许多初学者或许会有疑问,个人开证券账户是否需求支付开户费呢?这个问题的答案并不是那么简略,需求考虑…...
大厂面试-16道面试题
1 java集合类有哪些? List是有序的Collection,使用此接口能够精确的控制每个元素的插入位置,用户能根据索引访问List中元素。常用的实现List的类有LinkedList,ArrayList,Vector,Stack。 ArrayList是容量…...
搭建GraphQL服务
js版 GraphQL在 NodeJS 服务端中使用最多 安装graphql-yoga: npm install graphql-yoga 新建index.js: const {GraphQLServer} require("graphql-yoga")const server new GraphQLServer({ typeDefs: type Query { hello(name:String):String! …...
网站建设提供书面资料清单/企业品牌推广营销方案
#include<bits/stdc.h> #define inf 0x3f3f3f3f using namespace std; const int maxn17; /*这里参考大佬的代码,去掉初始位进行二进制枚举*/ int n,m,maxs,lim,cnt; int x[maxn],y[maxn],c[maxn]; int dp[1<<maxn];///某状态下的最少使用教练 int ds[1…...
武汉网站制作与建设/网页制作模板的网站
具体实现方法 第一步: 我们打开想要转载的博客,然后鼠标右键就会出现下面的菜单: 第二步: 点击【审查元素】,就会出现HTML页面的代码,或者:我们使用快捷键,直接按下F12 选中“article_content”&#…...
乌鲁木齐网站建设公司/优化推广seo
找到一个好玩的小东西,记录一下打怪通关的过程。 游戏地址: http://www.cn-hack.cn/qs/5.htm 第一关 查看网页源码,找到一段js:function PassConfirm() {var xdocument.password.pass.valueif (x"go ") {alert("恭…...
建设厅投诉网站/seo自动点击排名
量子计算的领导者之一IonQ公司最近宣布,其新的量子数据中心正式开幕,并宣布有两位神秘新成员加入到关键职位,竞争力再升级。 新血液 IonQ首席执行官兼总裁Peter Chapman宣布,著名的谷歌量子工程团队的Dave Bacon,正式…...
汾阳做网站/谷歌浏览器搜索引擎入口
转载于:https://www.cnblogs.com/chengmuyu/p/10707109.html...
优秀网站建设方案/免费培训seo
###**Statement安全漏洞(sql注入问题)****产生原因:**因为SQL语句拼接,传入了SQL语句的关键字,绕过了安全检查.客户端利用JDBC-【Statement】的缺点,传入非法的参数,从而让JDBC返回不合法的值,我们将这种情况下,统称为…...