热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Google的CacheBuilder缓存

1:refreshAfterWriteGuavaCache特性:对于同一个key,只让一个请求回源load数据,其他线程阻塞等待结果这种情

1:refreshAfterWrite


Guava Cache特性:对于同一个key,只让一个请求回源load数据,其他线程阻塞等待结果这种情况:如果缓存过期,恰好有多个线程读取同一个key的值,那么guava只允许一个线程去加载数据,其余线程阻塞。这虽然可以防止大量请求穿透缓存,但是效率低下。使用refreshAfterWrite可以做到:只阻塞加载数据的线程,其余线程返回旧数据。


2:之前常用 ConcurrentMap来做缓存,那它们有什么区别呢?


Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素,可设置过期时间。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

三种基于时间的清理或刷新缓存数据的方式:

expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。

expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。

refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。


考虑到时效性,我们可以使用expireAfterWrite,使每次更新之后的指定时间让缓存失效,然后重新加载缓存。guava cache会严格限制只有1个加载操作,这样会很好地防止缓存失效的瞬间大量请求穿透到后端引起雪崩效应。

     然而,通过分析源码,guava cache在限制只有1个加载操作时进行加锁,其他请求必须阻塞等待这个加载操作完成;而且,在加载完成之后,其他请求的线程会逐一获得锁,去判断是否已被加载完成,每个线程必须轮流地走一个“”获得锁,获得值,释放锁“”的过程,这样性能会有一些损耗。这里由于我们计划本地缓存1秒,所以频繁的过期和加载,锁等待等过程会让性能有较大的损耗。

     因此我们考虑使用refreshAfterWrite。refreshAfterWrite的特点是,在refresh的过程中,严格限制只有1个重新加载操作,而其他查询先返回旧值,这样有效地可以减少等待和锁争用,所以refreshAfterWrite会比expireAfterWrite性能好。但是它也有一个缺点,因为到达指定时间后,它不能严格保证所有的查询都获取到新值。了解过guava cache的定时失效(或刷新)原来的同学都知道,guava cache并没使用额外的线程去做定时清理和加载的功能,而是依赖于查询请求。在查询的时候去比对上次更新的时间,如超过指定时间则进行加载或刷新。所以,如果使用refreshAfterWrite,在吞吐量很低的情况下,如很长一段时间内没有查询之后,发生的查询有可能会得到一个旧值(这个旧值可能来自于很长时间之前),这将会引发问题。

 

     可以看出refreshAfterWrite和expireAfterWrite两种方式各有优缺点,各有使用场景。那么能否在refreshAfterWrite和expireAfterWrite找到一个折中?比如说控制缓存每1s进行refresh,如果超过2s没有访问,那么则让缓存失效,下次访问时不会得到旧值,而是必须得待新值加载。由于guava官方文档没有给出一个详细的解释,查阅一些网上资料也没有得到答案,因此只能对源码进行分析,寻找答案。经过分析,当同时使用两者的时候,可以达到预想的效果,这真是一个好消息呐!

Guava Cache是单个应用运行时的本地缓存,单机版的缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached或Redis

3:Guava Cache api介绍

guava cache是一个本地缓存。

优点

线程安全的缓存,与ConcurrentMap相似,但前者增加了更多的元素失效策略,后者只能显示的移除元素。
提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收。
监控缓存加载/命中情况。
集成了多部操作,调用get方式,可以在未命中缓存的时候,从其他地方获取数据源(DB,redis),并加载到缓存中。

缺点

Guava Cache的超时机制不是精确的。

public static void main(String[] args) throws ExecutionException, InterruptedException{//缓存接口这里是LoadingCache&#xff0c;LoadingCache在缓存项不存在时可以自动加载缓存LoadingCache studentCache//CacheBuilder的构造函数是私有的&#xff0c;只能通过其静态方法newBuilder()来获得CacheBuilder的实例&#61; CacheBuilder.newBuilder()//设置并发级别为8&#xff0c;并发级别是指可以同时写缓存的线程数.concurrencyLevel(8)//设置写缓存后8秒钟过期.expireAfterWrite(8, TimeUnit.SECONDS)//设置缓存容器的初始容量为10.initialCapacity(10)//设置缓存最大容量为100&#xff0c;超过100之后就会按照LRU最近虽少使用算法来移除缓存项.maximumSize(100)//设置要统计缓存的命中率.recordStats()//设置缓存的移除通知.removalListener(new RemovalListener() {&#64;Overridepublic void onRemoval(RemovalNotification notification) {System.out.println(notification.getKey() &#43; " was removed, cause is " &#43; notification.getCause());}})//build方法中可以指定CacheLoader&#xff0c;在缓存不存在时通过CacheLoader的实现自动加载缓存.build(new CacheLoader() {&#64;Overridepublic Student load(Integer key) throws Exception {System.out.println("load student " &#43; key);Student student &#61; new Student();student.setId(key);student.setName("name " &#43; key);return student;}});for (int i&#61;0;i<20;i&#43;&#43;) {//从缓存中得到数据&#xff0c;由于我们没有设置过缓存&#xff0c;所以需要通过CacheLoader加载缓存数据Student student &#61; studentCache.get(1);System.out.println(student);//休眠1秒TimeUnit.SECONDS.sleep(1);}System.out.println("cache stats:");//最后打印缓存的命中率等 情况System.out.println(studentCache.stats().toString());}

输出结果&#xff1a;

cache stats:
CacheStats{hitCount&#61;17, missCount&#61;3, loadSuccessCount&#61;3, loadExceptionCount&#61;0, totalLoadTime&#61;1348802, evictionCount&#61;2}

原因&#xff1a;看看到在20此循环中命中次数是17次&#xff0c;未命中3次&#xff0c;这是因为我们设定缓存的过期时间是写入后的8秒&#xff0c;所以20秒内会失效两次&#xff0c;另外第一次获取时缓存中也是没有值的&#xff0c;所以才会未命中3次&#xff0c;其他则命中。

不能持久化本地缓存
常用方法

V getIfPresent(Object key) 获取缓存中key对应的value&#xff0c;如果缓存没命中&#xff0c;返回null。
V get(K key) throws ExecutionException 获取key对应的value&#xff0c;若缓存中没有&#xff0c;则调用LocalCache的load方法&#xff0c;从数据源中加载&#xff0c;并缓存。
void put(K key, V value) 如果缓存有值&#xff0c;覆盖&#xff0c;否则&#xff0c;新增
void putAll(Map m);循环调用单个的方法
void invalidate(Object key); 删除缓存
void invalidateAll(); 清楚所有的缓存&#xff0c;相当远map的clear操作。
long size(); 获取缓存中元素的大概个数。为什么是大概呢&#xff1f;元素失效之时&#xff0c;并不会实时的更新size&#xff0c;所以这里的size可能会包含失效元素。
CacheStats stats(); 缓存的状态数据&#xff0c;包括(未)命中个数&#xff0c;加载成功/失败个数&#xff0c;总共加载时间&#xff0c;删除个数等。
asMap()方法获得缓存数据的ConcurrentMap快照
cleanUp()清空缓存
refresh(Key) 刷新缓存&#xff0c;即重新取缓存数据&#xff0c;更新缓存
ImmutableMap getAllPresent(Iterable keys) 一次获得多个键的缓存值
核心类

CacheBuilder&#xff1a;类&#xff0c;缓存构建器。构建缓存的入口&#xff0c;指定缓存配置参数并初始化本地缓存。 
CacheBuilder在build方法中&#xff0c;会把前面设置的参数&#xff0c;全部传递给LocalCache&#xff0c;它自己实际不参与任何计算。这种初始化参数的方法值得借鉴&#xff0c;代码简洁易读。
CacheLoader&#xff1a;抽象类。用于从数据源加载数据&#xff0c;定义load、reload、loadAll等操作。
Cache&#xff1a;接口&#xff0c;定义get、put、invalidate等操作&#xff0c;这里只有缓存增删改的操作&#xff0c;没有数据加载的操作。
AbstractCache&#xff1a;抽象类&#xff0c;实现Cache接口。其中批量操作都是循环执行单次行为&#xff0c;而单次行为都没有具体定义。
LoadingCache&#xff1a;接口&#xff0c;继承自Cache。定义get、getUnchecked、getAll等操作&#xff0c;这些操作都会从数据源load数据。
AbstractLoadingCache&#xff1a;抽象类&#xff0c;继承自AbstractCache&#xff0c;实现LoadingCache接口。
LocalCache&#xff1a;类。整个guava cache的核心类&#xff0c;包含了guava cache的数据结构以及基本的缓存的操作方法。
LocalManualCache&#xff1a;LocalCache内部静态类&#xff0c;实现Cache接口。 
其内部的增删改缓存操作全部调用成员变量 localCache&#xff08;LocalCache类型&#xff09;的相应方法。
LocalLoadingCache&#xff1a;LocalCache内部静态类&#xff0c;继承自LocalManualCache类&#xff0c;实现LoadingCache接口。 
其所有操作也是调用成员变量localCache&#xff08;LocalCache类型&#xff09;的相应方法。
CacheStats&#xff1a;缓存加载/命中统计信息。


推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 如何搭建Java开发环境并开发WinCE项目
    本文介绍了如何搭建Java开发环境并开发WinCE项目,包括搭建开发环境的步骤和获取SDK的几种方式。同时还解答了一些关于WinCE开发的常见问题。通过阅读本文,您将了解如何使用Java进行嵌入式开发,并能够顺利开发WinCE应用程序。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 本文介绍了OkHttp3的基本使用和特性,包括支持HTTP/2、连接池、GZIP压缩、缓存等功能。同时还提到了OkHttp3的适用平台和源码阅读计划。文章还介绍了OkHttp3的请求/响应API的设计和使用方式,包括阻塞式的同步请求和带回调的异步请求。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
得不到的最美丽
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有