精华内容
下载资源
问答
  • 主要介绍了springboot使用GuavaCache做简单缓存处理的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 主要介绍了SpringBoot加入Guava Cache实现本地缓存代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了springboot整合GuavaCache缓存过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Go中的Guava Cache的Mango Cache部分实现。 支持的缓存替换策略:LRU分段LRU(默认)TinyLFU(实验性)TinyLFU实现的灵感源于Ben Manes的Caffeine和Go中的Guava Cache的Mango Cache Partial实现。 支持的缓存替换...
  • Guava cache

    千次阅读 2019-03-17 08:42:43
    Guava cache,一个小巧的本地缓存使用详解

    Guava cache,一个小巧的本地缓存实战案例

    完整文章请关注技术号阅读。

    **技术号【程猿薇茑】**

    推荐 极简代码展示RPC 核心原理

    什么是缓存?

      想必大家第一次听到“缓存”这个概念,还是在大学的计算机专业课上。学过操作系统原理、计算机组成原理的同学都知道,在计算机系统中,存储层级可按其作用分为高速缓冲存储器(Cache)、主存储器、辅助存储器三级。当然了,这里的“高速缓存”并非本文要讨论的缓存。今天我们要讨论的缓存是软件层面的。而高速缓存是位于CPU内部的物理器件,是主存与CPU之间的一级存储器,通常由静态存储芯片(SRAM)组成,容量很小但速度比主存快得多,接近于CPU的速度。其主要作用就是缓和CPU和内存之间速度不匹配的问题,使得整机处理速度得以提升。
      尽管不同于硬件层面的高速缓存,但是缓存的思想本质上是一致的,都是将数据放在离使用者最近的位置以及访问速度较快的存储介质上以加快整个系统的处理速度。软件缓存可以认为是为了缓和客户端巨大的并发量和服务端数据库(通常是关系型数据库)处理速度不匹配的问题。缓存本质上是在内存中维护的一个hash数据结构(哈希表),通常是以<key,value>的形式存储。由于hash table查询时间复杂度为O(1),因此性能很高。能够有效地加速应用的读写速度,降低后端负载。

    自行实现一个缓存需要考虑的基本问题

    1)数据结构
      首先要考虑的是数据该如何存储,要选择合适的数据结构。在Java编程中,最简单的就是直接用Map集合来存储数据;复杂一点,像redis提供了多种数据结构:哈希,列表,集合,有序集合等,底层使用了双端链表,压缩列表,跳跃表等数据结构;

    2)更新/清除策略
      缓存中的数据都是有生命周期的,要在指定时间后被删除或更新,这样才能保证缓存空间在一个可控的范围。常用的更新/清除策略有LRU(Least Recently Used最近最少使用)、FIFO( First Input First Output先进先出)、LFU(Least Frequently Used最近最不常用)、SOFT(软引用)、WEAK(弱引用)等策略。

    3)线程安全
      redis是单线程处理模式,就不存在线程安全问题;而本地缓存往往是可以多个线程同时访问的,所以线程安全不容忽视;线程安全问题是不应该抛给使用者去保证的,因此需要缓存自身支持。

    常用的缓存技术有哪些?

    1)分布式缓存
    Memcached
    Memcached 是一个开源的、高性能的、分布式的、基于内存的对象缓存系统。它能够用来存储各种格式的数据,包括字符串、图像、视频、文件等。Memcached 把数据全部存在内存之中,断电后会丢失,因此数据不能超过内存大小。且支持的数据结构较为单一,一般用于简单的key-value形式的存储。

    Redis
    Redis是一个开源的基于内存的数据结构存储组件,可用作数据库(nosql)、缓存和消息队列。它支持诸如字符串、散列、列表、集合、带范围查询的有序集合、位图、hyperloglogs、支持半径查询和流的地理空间索引等多种数据结构。Redis具有内置的复制、Lua脚本、LRU清除、事务和不同级别的磁盘持久化特性,并通过Redis 哨兵机制和基于Redis集群的自动分区提供高可用性。
    和Memcached 相比,Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等多种数据结构的存储。Redis还支持定期把数据持久化到磁盘。

    2)本地缓存
    本地缓存,顾名思义就是在应用本身维护一个缓存结构,比如Java中的Map集合就比较适合做<key,value>缓存。本地应用缓存最大的优点是应用本身和Cache在同一个进程内部,请求缓存非常快速,没有额外的网络I/O开销。本地缓存适合于单应用中不需要集群、各节点无需互相通信的场景。因此,其缺点是缓存跟应用程序耦合,分布式场景下多个独立部署的应用程序无法直接共享缓存,各节点都需要维护自己的单独缓存,既是对物理内存的一种浪费,也会导致数据的不一致性。Guava cache就是一种本地缓存。

    话不多说,进入正题,本文主要介绍Google的缓存组件Guava Cache的实战技巧!

    Guava Cache实战

    1.引入依赖

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

    2.Demo1:简单使用,掌握如何创建使用Cache

    import com.google.common.cache.Cache;
    import com.google.common.cache.CacheBuilder;
    import java.util.concurrent.Callable;
    
    public class CacheService1 {
        public static void main(String[] args) throws Exception {
            Cache<Object, Object> cache = CacheBuilder.newBuilder().build();
            // 写入/覆盖一个缓存
            cache.put("k1", "v1");
            // 获取一个缓存,如果该缓存不存在则返回一个null值
            Object value1 = cache.getIfPresent("k1");
            System.out.println("value1:" + value1);
            // 获取缓存,当缓存不存在时,则通Callable进行加载并返回,该操作是原子的
            Object getValue1 = cache.get("k1", new Callable<Object>() {
                @Override
                public Object call() throws Exception {
    	      //缓存加载逻辑
                    return null;
                }
            });
    
            System.out.println("getValue1:" + getValue1);
    
            Object getValue2 = cache.get("k2", new Callable<Object>() {
    
                /**
                 * 加载缓存的逻辑
                 * @return
                 * @throws Exception
                 */
                @Override
                public Object call() throws Exception {
                    return "v2";
                }
            });
    
            System.out.println("getValue2:" + getValue2);
        }
    }
    

    控制台输出:
    value1:v1
    getValue1:v1
    getValue2:v2

    上述程序演示了Guava Cache的读和写。Guava的缓存有许多配置选项,为了简化缓的创建,使用了Builder设计模式;Builder使用的是链式编程的思想,也就是每次调用方法后返回的是对象本身,这样可以简化配置过程。
    获取缓存值时可以指定一个Callable实例动态执行缓存加载逻辑;也可以在创建Cache实例时直接使用LoadingCache。顾名思义,它能够通过CacheLoader自发的加载缓存。

    import com.google.common.cache.CacheBuilder;
    import com.google.common.cache.CacheLoader;
    import com.google.common.cache.LoadingCache;
    
    public class CacheService2 {
        public static void main(String[] args) throws Exception {
            LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder()
                    .build(new CacheLoader<String, String>() {
                        @Override
                        public String load(String key) throws Exception {
                            // 缓存加载逻辑
                            return "value2";
                        }
                    });
    
            loadingCache.put("k1", "value1");
            String v1 = loadingCache.get("k1");
            System.out.println(v1);
    
            // 以不安全的方式获取缓存,当缓存不存在时,会通过CacheLoader自动加载
            String v2 = loadingCache.getUnchecked("k2");
            System.out.println(v2);
    
            // 获取缓存,当缓存不存在时,会通过CacheLoader自动加载
            String v3 = loadingCache.get("k3");
            System.out.println(v3);
        }
    }
    

    控制台输出:
    value1
    value2
    value2

    3.Demo2:理解Cache的过期处理机制

    import com.google.common.cache.Cache;
    import com.google.common.cache.CacheBuilder;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    public class CacheService3 {
        /**
         * 通过builder模式创建一个Cache实例
         */
    static Cache<Integer, String> cache = CacheBuilder.newBuilder()
            //设置缓存在写入5秒钟后失效
            .expireAfterWrite(5, TimeUnit.SECONDS)
            //设置缓存的最大容量(基于容量的清除)
            .maximumSize(1000)
            //开启缓存统计
            .recordStats()
            .build();
    
        public static void main(String[] args) throws Exception {
            //单起一个线程监视缓存状态
            new Thread() {
                public void run() {
                    while (true) {
                        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
                        System.out.println(sdf.format(new Date()) + " cache size: " + cache.size());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
    e.printStackTrace();
                        }
                    }
                }
            }.start();
    
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            //写入缓存
            cache.put(1, "value1");
            //读取缓存
            System.out.println("write key:1 ,value:" + cache.getIfPresent(1));
            Thread.sleep(10000);
            // when write ,key:1 clear
            cache.put(2, "value2");
            System.out.println("write key:2 ,value:" + cache.getIfPresent(2));
            Thread.sleep(10000);
            // when read other key ,key:2 do not clear
            System.out.println(sdf.format(new Date()) + " after write, key:1 ,value:" + cache.getIfPresent(1));
            Thread.sleep(2000);
            // when read same key ,key:2 clear
            System.out.println(sdf.format(new Date()) + " final, key:2 ,value:" + cache.getIfPresent(2));
            Thread.sleep(2000);
            cache.put(1, "value1");
            cache.put(2, "value2");
            Thread.sleep(3000);
            System.out.println(sdf.format(new Date()) + " write key:1 ,value:" + cache.getIfPresent(1));
            System.out.println(sdf.format(new Date()) + " write key:2 ,value:" + cache.getIfPresent(2));
            Thread.sleep(3000);
            System.out.println(sdf.format(new Date()) + " final key:1 ,value:" + cache.getIfPresent(1));
            System.out.println(sdf.format(new Date()) + " final key:2 ,value:" + cache.getIfPresent(2));
            Thread.sleep(3000);
        }
    }
    

    控制台输出:
    22:07:17 cache size: 0
    write key:1 ,value:value1
    22:07:18 cache size: 1
    22:07:19 cache size: 1
    22:07:20 cache size: 1
    22:07:21 cache size: 1
    22:07:22 cache size: 1
    22:07:23 cache size: 1
    22:07:24 cache size: 1
    22:07:25 cache size: 1
    22:07:26 cache size: 1
    22:07:27 cache size: 1
    write key:2 ,value:value2
    22:07:28 cache size: 1
    22:07:29 cache size: 1
    22:07:30 cache size: 1
    22:07:31 cache size: 1
    22:07:32 cache size: 1
    22:07:33 cache size: 1
    22:07:34 cache size: 1
    22:07:35 cache size: 1
    22:07:36 cache size: 1
    22:07:37 after write, key:1 ,value:null
    22:07:37 cache size: 1
    22:07:38 cache size: 1
    22:07:39 final, key:2 ,value:null
    22:07:39 cache size: 0
    22:07:40 cache size: 0
    22:07:41 cache size: 2
    22:07:42 cache size: 2
    22:07:43 cache size: 2
    22:07:44 write key:1 ,value:value1
    22:07:44 write key:2 ,value:value2
    22:07:44 cache size: 2
    22:07:45 cache size: 2
    22:07:46 cache size: 2
    22:07:47 final key:1 ,value:null
    22:07:47 final key:2 ,value:null
    22:07:47 cache size: 0
    22:07:48 cache size: 0

    运行上述程序,可以得出如下结论:
    (1)缓存项<1,“value1”>的过期时间是5秒,但经过5秒后并没有被清除,因为还是size=1
    (2)发生写操作cache.put(2, “value2”)后,缓存项<1,“value1”>被清除,因为size=1,而不是size=2
    (3)发生读操作cache.getIfPresent(1)后,缓存项<2,“value2”>没有被清除,因为还是size=1,即读操作确实不一定触发清除
    (4)发生读操作cache.getIfPresent(2)后,缓存项<2,“value2”>被清除,因为读的key就是2

    上述机制在Guava Cache中被称为“延迟删除”,即删除总是发生得比较“晚”,并不是真正意义上的定时过期。需要依靠用户请求线程下一次读写缓存才能触发清除/更新。这也是Guava Cache的独到之处。但是这种实现方式也会存在问题:缓存会可能会存活比较长的时间,一直占用着物理内存。如果使用了复杂的清除策略如基于容量的清除,还可能会占用着线程而导致响应时间变长。但优点也是显而易见的,没有启动额外的线程,不管是实现,还是使用都比较简单(轻量)。

    如果我们需要尽可能地降低延迟,

    完整文章请关注技术号阅读。

    **技术号【程猿薇茑】**
    **关注阅读技术文章**
    展开全文
  • 芒果缓存 Go 中部分实现。 支持的缓存替换策略: ... load := func ( k cache. Key ) (cache. Value , error ) { time . Sleep ( 100 * time . Millisecond ) // Slow task return fmt . Sprintf ( "%d" ,
  • Guava Cache

    2021-11-20 19:08:47
    GuavaCache、Tair、EVCache、Aerospike比较 应用场景: 对性能有非常高的要求 不经常变化 占用内存不大 有访问整个集合的需求 数据允许不时时一致 Cuava Cache优势: 缓存过期和淘汰机制,采用LRU..

    Guava是Google提供的一套Java工具包,而Guava Cache是一套非常完善的本地缓存机制(JVM缓 存)。 Guava cache的设计来源于CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。

    GuavaCache、Tair、EVCache、Aerospike比较

    应用场景:

    • 对性能有非常高的要求
    • 不经常变化
    • 占用内存不大
    • 有访问整个集合的需求
    • 数据允许不时时一致

     Cuava Cache优势:

    •  缓存过期和淘汰机制,采用LRU的方式,将不常使用的键值从Cache中删除
    • 并发处理能力,GuavaCache类似CurrentHashMap,是线程安全的。提供了设置并发级别的api,使得缓存支持并发的写入和读取,分离锁是分拆锁定,把一个集合看分成若干partition, 每个partiton一把锁。ConcurrentHashMap 就是分了16个区域,这16个区域之间是可以并发的。GuavaCache采用Segment做分区。
    • 更新锁定,一般情况下,在缓存中查询某个key,如果不存在,则查源数据,并回填缓存。(Cache Aside Pattern) 在高并发下会出现,多次查源并重复回填缓存,可能会造成源的宕机(DB),性能下降 。GuavaCache可以在CacheLoader的load方法中加以控制,对同一个key,只让一个请求去读源并,回填缓存,其他请求阻塞等待。
    • 集成数据源,一般我们在业务中操作缓存,都会操作缓存和数据源两部分,而GuavaCache的get可以集成数据源,在从缓存中读取不到时可以从数据源中读取数据并回填缓存
    • 监控缓存加载/命中情况用作统计业务

    Cuava Cache核心原理 

    Guava Cache的数据结构和CurrentHashMap相似,最基本的区别是 ConcurrentMap会一直保存所有添加的元素,直到显式地移除。 相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。

    • LocalCache为Guava Cache的核心类,包含一个Segment数组组成 
    • Segement数组的长度决定了cache的并发数
    • 每一个Segment使用了单独的锁,其实每个Segment继承了ReentrantLock,对Segment的写操 作需要先拿到锁
    • 每个Segment由一个table和5个队列组成
    • 5个队列:ReferenceQueue keyReferenceQueue : 已经被GC,需要内部清理的键引用队列,ReferenceQueue valueReferenceQueue : 已经被GC,需要内部清理的值引用队列,ConcurrentlinkedQueue> recencyQueue : LRU队列,当segment上达到 临界值发生写操作时该队列会移除数据,Queue> writeQueue:写队列,按照写入时间进行排序的元素队列,写入 一个元素时会把它加入到队列尾部,Queue> accessQueue:访问队列,按照访问时间进行排序的元素队列, 访问(包括写入)一个元素时会把它加入到队列尾部
    • AtomicReferenceArray> table:AtomicReferenceArray可以用原子方式 更新其元素的对象引用数组
    • ReferenceEntry<key,value>  ReferenceEntry是Guava Cache中对一个键值对节点的抽象,每个ReferenceEntry数组项都是一 条ReferenceEntry链。并且一个ReferenceEntry包含key、hash、valueReference、next字段 (单链) Guava Cache使用ReferenceEntry接口来封装一个键值对,而用ValueReference来封装Value值

    GuavaCache核心原理之回收机制 

    • 基于容量回收:  在缓存项的数目达到限定值之前,采用LRU的回收方式
    • 定时回收 : expireAfterAccess:缓存项在给定时间内没有被读/写访问,则回收。回收顺序和基于大小回收一 样(LRU),定时回收 expireAfterAccess:缓存项在给定时间内没有被读/写访问,则回收。回收顺序和基于大小回收一 样(LRU)
    • 基于引用回收:通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以垃圾回收

    GuavaCache构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没 有诸如此类的清理机制。 GuavaCache是在每次进行缓存操作的时候,惰性删除 如get()或者put()的时候,判断缓存是否过期 

    Cuava Cache并发操作

    1,并发的设置

    GuavaCache通过设置 concurrencyLevel 使得缓存支持并发的写入和读取,concurrencyLevel=Segment数组的长度,同ConcurrentHashMap类似Guava cache的并发也是通过分离锁实现

    LoadingCache<String,Object> cache = CacheBuilder.newBuilder()
    // 最大3个 同时支持CPU核数线程写缓存
    .maximumSize(3).concurrencyLevel(Runtime.getRuntime().availableProcessors()).bui
    ld();
    
    V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    int hash = this.hash(Preconditions.checkNotNull(key));
    //通过hash值确定该key位于哪一个segment上,并获取该segment
    return this.segmentFor(hash).get(key, hash, loader);
    }

    LoadingCache采用了类似ConcurrentHashMap的方式,将映射表分为多个segment。segment之间 可以并发访问,这样可以大大提高并发的效率,使得并发冲突的可能性降低了。

    2,更新锁定

    GuavaCache提供了一个refreshAfterWrite定时刷新数据的配置项

    如果经过一定时间没有更新或覆盖,则会在下一次获取该值的时候,会在后台异步去刷新缓存刷新时只有一个请求回源取数据,其他请求会阻塞(block)在一个固定时间段,如果在该时间段内没 有获得新值则返回旧值。<防止都去访问数据库,回圆导致缓存击穿>

     3,网站首页案例(访问更多,并发访问明显)

    面试问题:

    1,GuavaCache会oom(内存溢出)吗?

    会,当我们设置缓存永不过期(或者很长),缓存的对象不限个数(或者很大)时,不断向GuavaCache加入大字符串,最终将会oom。

    解决方案:缓存时间设置相对小些,使用弱引用方式存储对象

    2,GuavaCache缓存到期就会立即清除吗?

    不是的,GuavaCache是在每次进行缓存操作的时候,如get()或者put()的时候,判断缓存是否过期,一个如果一个对象放入缓存以后,不在有任何缓存操作(包括对缓存其他key的操作),那么该缓存不 会主动过期的。

    3,GuavaCache如何找出最久未使用的数据?

    用accessQueue,这个队列是按照LRU的顺序存放的缓存对象(ReferenceEntry)的。会把访问过的对象放到队列的最后。并且可以很方便的更新和删除链表中的节点,因为每次访问的时候都可能需要更新该链表,放入到链表 的尾部。这样,每次从access中拿出的头节点就是最久未使用的。对应的writeQueue用来保存最久未更新的缓存队列,实现方式和accessQueue一样。

    展开全文
  • Guava Cache探索及spring项目整合GuavaCache实例 背景  对于高频访问但是低频更新的数据我们一般会做缓存,尤其是在并发量比较高的业务里,原始的手段我们可以使用HashMap或者ConcurrentHashMap来存储.  这样没...

    Guava Cache探索及spring项目整合GuavaCache实例

    背景

      对于高频访问但是低频更新的数据我们一般会做缓存,尤其是在并发量比较高的业务里,原始的手段我们可以使用HashMap或者ConcurrentHashMap来存储.

      这样没什么毛病,但是会面临一个问题,对于缓存中的数据只有当我们显示的调用remove方法,才会移除某个元素,即便是高频的数据,也会有访问命中率的高低之分,内存总是有限的,我们不可能无限地去增加Map中的数据.

      我希望的比较完美的场景时.对于一个业务,我只想分配给你2k的内存,我们假设map中一条数据(键值对)是1B,那么最多它能存2048条数据,当数据达到这个量级的时候,需要淘汰一些访问率比较低的数据来给新的数据腾地方,使用传统的HashMap比较难实现,因为我们不知道哪些数据访问率低(除非专门去记录),那么Guava针对内存缓存优化的一个组件就闪亮登场了.

    准备

      上面说到我们需要一种淘汰策略来自动筛选缓存数据,下面简单了解下,几种淘汰算法

      先进先出算法(FIFO):这种淘汰策略顾名思义,先存的先淘汰.这样简单粗暴,但是会错杀一些高频访问的数据

      最近最少使用算法(LRU):这个算法能有效优化FIFO的问题,高频访问的数据不太容易被淘汰掉,但也不能完全避免.GuavaCache一些特性符合这种算法

          最近最少频率算法(LFU): 这个算法又对LRU做了优化,会记录每个数据的访问次数,综合访问时间和访问次数来淘汰数据.

    Guava Cache基础

      GuavaCache提供了线程安全的实现机制,简单易用,上手成本很低,在需要使用内存做缓存的业务场景时可以考虑使用.

      GuavaCache缓存机制有两个接口,Cache和LoadingCache,后者也是一个接口,继承自Cache,并额外多了几个接口,如果我们想实例化一个Cache对象,还需要了解一个CacheBuilder类,这个类就是雨从来构建Cache对象的,我们先来用CacheBuilder实例化一个Cache对象再学习它的一些字段含义.

    复制代码

    public static void main(String[] args) {
            Cache<String,String> myMap = CacheBuilder.newBuilder()
                    .expireAfterAccess(30L, TimeUnit.SECONDS)
                    .expireAfterWrite(3L,TimeUnit.MINUTES)
                    .concurrencyLevel(6)
                    .initialCapacity(100)
                    .maximumSize(1000)
                    .softValues()
                    .build();
    
            myMap.put("name", "张三");
    
            System.out.println(myMap.getIfPresent("name"));
    
    }

    复制代码

         这样我们就创建一个类似map接口的Cache对象,描述一下上面创建的这个对象:

         创建了一个Cache对象,这个对象有这样的特性,初始大小为100(能存100个键值对),最大size为1000,在数据写入3分钟后会被自动移除,并且数据如果在30秒内,没有被访问则会被移除,另外这Map结构的对象支持最多6个调用方同时更新这个缓存结构的数据,即并发更新操作最大数量为6.

      我们看到还有一个softValues()属性没有讲,会放在下面说明,其实CacheBuilder并不只有这么几个属性可设置,下面我们具体讲一下.

    CacheBuilder中一些常用的属性字段:

      concurrencyLevel(int):指定允许同时更新的操作数,若不设置CacheBuilder默认为4,这个参数会影响缓存存储空间的分块,可以简单理解为,默认会创建指定size个map,每个map称为一个区块,数据会分别存到每个map里,我们根据实际需要设置这个值的大小.

      initialCapacity(int):指定缓存初始化的空间大小,如果设置了40,并且concurrencyLevel取默认,会分成4个区块,每个区块最大的size为10,当更新数据时,会对这个区块进行加锁,这就是为什么说,允许同时更新的操作数为4,延伸一点,在淘汰数据时,也是每个区块单独维护自己的淘汰策略.也就是说,如果每个区块size太大,竞争就会很激烈.

      maximumSize(long):指定最大缓存大小.当缓存数据达到最大值时,会按照策略淘汰掉一些不常用的数据,需要注意的是,在缓存数据量快要到达最大值的时候,就会开始数据的回收,简单理解为"防患于未然"吧

    下边三个参数分别是,SoftValues(),weakKeys(),weakValues(),在解释这三个参数前,需要我们先了解一下java中的软引用,和弱引用.

      和弱引用对应的是强引用,也是我们在编码过程中最常使用的,我们声明的变量,对象,基本都是强引用,这样的对象,jvm在GC时不会回收,哪怕是抛出OOM.

      而弱引用就不一样了,在java中,用java.lang.ref.WeakReference标示声明的值,jvm在垃圾回收的时候会将它回收掉,那么软引用呢?就是用SoftReference标示的,声明为弱引用的对象,会在jvm的内存不足时回收掉.

      看出区别了吗,简单总结下就是,软引用,只有在内存不足时才可能被回收,在正常的垃圾回收时不会被回收,弱引用,会在jvm进行垃圾回收的时候被删除.

      softValues():将缓存中的数据设置为softValues模式。数据使用SoftReference类声明,就是在SoftReference实例中存储真实的数据。设置了softValues()的数据,会被全局垃圾回收管理器托管,按照LRU的原则来定期GC数据。数据被GC后,可能仍然会被size方法计数,但是对其执行read或write方法已经无效

      weakKeys()和weakValues():当设置为weakKey或weakValues时,会使用(==)来匹配key或者value值(默认强引用时,使用的是equals方法),这种情况下,数据可能会被GC,数据被GC后,可能仍然会被size方法计数,但是对其执行read或write方法已经无效

    Guava cache在spring项目中的使用

      下面以一个我在项目中的实际应用梳理一下在spring项目中应该如果整合guava cache

     1.引入guava的maven依赖

    复制代码

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

    复制代码

      上面使用的版本是我在写这篇笔记时的最新版本.

     2.在application-context.xml加入配置

    复制代码

    <!--开启缓存注解-->
        <cache:annotation-driven />
    
        <bean id="cacheManager" class="org.springframework.cache.guava.GuavaCacheManager">
            <property name="cacheSpecification" value="initialCapacity=500,maximumSize=5000,expireAfterAccess=2m,softValues" />
            <property name="cacheNames">
                <list>
                    <value>questionCreatedTrack</value>
                </list>
            </property>
        </bean>

    复制代码

      在上面配置中我们实现了一个cacheManager,这是必须要配置的,默认配置的是org.springframework.cache.support.SimpleCacheManager,我们这里把它改成了Guava的缓存管理器的实现.如果使用其他的实现,比如redis,这里只需要配置成redis的相关缓存管理器即可

      cacheManager可以简单理解为保存Cache的地方,Cache里边有我们具体想要缓存的数据,一般以key-value的键值对形式

      上述配置的bean中声明的两个属性,一个是cacheSpecification,不需要多说了,参考上面的详细参数,需要了解一点的是,这里的参数使用的是CacheBuilderSpec类,以解析代表CacheBuilder配置的字符串的形式来创建CacheBuilder实例

      cacheNames可以根据自己的实际业务命名,可声明多个

     3.在代码中使用spring的cache相关注解

    复制代码

    @Cacheable(value = "questionCreatedTrack",key="#voiceId",condition = "#voiceId>0")
        public Long getQuestionIdByVoiceId(Long anchorId, Long voiceId) {
            String key = String.format(HOMEWORK_QUESTION_ANCHOR_KEY, anchorId);
            String value = redisProxy.getValue(key, String.valueOf(voiceId));
            return StringUtils.isEmpty(value) ? null : Long.parseLong(value);
        }
    
        @CachePut(value = "questionCreatedTrack",key = "#voiceId",condition = "#voiceId>0")
        public Long statCollectionQuestionToCache(Long anchorId, Long voiceId, Long questionId) {
            String key = String.format(HOMEWORK_QUESTION_ANCHOR_KEY, anchorId);
            redisProxy.setOneToHash(key, String.valueOf(voiceId), String.valueOf(questionId));
            return questionId;
        }
    
        @CacheEvict(value = "questionCreatedTrack",key="#voiceId")
        public void removeCollectionQuestionFromCache(Long anchorId, Long voiceId) {
            String key = String.format(HOMEWORK_QUESTION_ANCHOR_KEY, anchorId);
            redisProxy.deleteOneToHash(key, String.valueOf(voiceId));
        }

    复制代码

     先简单说一下这里的逻辑,我主要是使用内存做一个一级缓存,redis做第二级的缓存,上面三个方法的作用分别是

      getQuestionIdByVoiceId(..):通过voiceId查询questionId,使用@Cacheable注解标记的意思是,代码执行到这个方法时,会先去guava cache里去找,有的话直接返回不走方法,没有的话再去执行方法,返回后同时加入Cache,缓存结构中的value是方法的返回值,key是方法入参中的vocieId,这里的缓存结构是,key=voiceId,value=questionId

      statCollectionQuestionToCache():方法的逻辑是将voiceId和questionId保存进redis里,使用@CachePut注解标记的意思是,不去缓存里找,直接执行方法,执行完方法后,将键值对加入Cache.

      removeCollectionQuestionFromCache():方法的逻辑是删除redis中的key为voiceId的数据,使用@CacheEvict注解标记的意思是,清除Cache中key为voiceId的数据

      通过以上三个注解,可以实现这样的功能,当查询voiceId=123的对应questionId时,会先去Cache里查,Cache里如果没有再去redis里查,有的话同时加入Cache,(没有的话,也会加入Cache,这个下面会说),然后在新增数据以及移除数据的时候,redis和Cache都会同步.

      @Cacheable方法查询结果为null怎么处理

      这个需要我们根据实际需要,决定要不要缓存查询结果为null的数据,如果不需要,需要使用下面的注解

        @Cacheable(value = "questionCreatedTrack",key="#voiceId",condition = "#voiceId>0",unless = "#result==null")

    参考资料

    http://www.voidcn.com/article/p-pvvfgdga-bos.html

    https://www.cnblogs.com/fashflying/p/6908028.html

    展开全文
  • Guava Cache官方文档

    2021-02-25 12:45:51
    LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .removalListener(MY_LISTENER) .build( new CacheLoader<Key, Graph>...

    示例

    LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
           .maximumSize(1000)
           .expireAfterWrite(10, TimeUnit.MINUTES)
           .removalListener(MY_LISTENER)
           .build(
               new CacheLoader<Key, Graph>() {
                 @Override
                 public Graph load(Key key) throws AnyException {
                   return createExpensiveGraph(key);
                 }
               });
    

    适用性

    缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。

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

    通常来说,Guava Cache适用于:

    你愿意消耗一些内存空间来提升速度。
    你预料到某些键会被查询一次以上。
    缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
    如果你的场景符合上述的每一条,Guava Cache就适合你。

    如同范例代码展示的一样,Cache实例通过CacheBuilder生成器模式获取,但是自定义你的缓存才是最有趣的部分。

    注:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。

    加载

    在使用缓存前,首先问自己一个问题:有没有合理的默认方法来加载或计算与键关联的值?如果有的话,你应当使用CacheLoader。如果没有,或者你想要覆盖默认的加载运算,同时保留"获取缓存-如果没有-则计算"get-if-absent-compute的原子语义,你应该在调用get时传入一个Callable实例。缓存元素也可以通过Cache.put方法直接插入,但自动加载是首选的,因为它可以更容易地推断所有缓存内容的一致性。

    CacheLoader

    LoadingCache是附带CacheLoader构建而成的缓存实现。创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法。例如,你可以用下面的代码构建LoadingCache:

    LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
           .maximumSize(1000)
           .build(
               new CacheLoader<Key, Graph>() {
                 public Graph load(Key key) throws AnyException {
                   return createExpensiveGraph(key);
                 }
               });
    
    ...
    try {
      return graphs.get(key);
    } catch (ExecutionException e) {
      throw new OtherException(e.getCause());
    }
    

    从LoadingCache查询的正规方式是使用get(K)方法。这个方法要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值。由于CacheLoader可能抛出异常,LoadingCache.get(K)也声明为抛出ExecutionException异常。如果你定义的CacheLoader没有声明任何检查型异常,则可以通过getUnchecked(K)查找缓存;但必须注意,一旦CacheLoader声明了检查型异常,就不可以调用getUnchecked(K)。

    LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
           .expireAfterAccess(10, TimeUnit.MINUTES)
           .build(
               new CacheLoader<Key, Graph>() {
                 public Graph load(Key key) { // no checked exception
                   return createExpensiveGraph(key);
                 }
               });
    
    ...
    return graphs.getUnchecked(key);
    

    getAll(Iterable<? extends K>)方法用来执行批量查询。默认情况下,对每个不在缓存中的键,getAll方法会单独调用CacheLoader.load来加载缓存项。如果批量的加载比多个单独加载更高效,你可以重载CacheLoader.loadAll来利用这一点。getAll(Iterable)的性能也会相应提升。

    注:CacheLoader.loadAll的实现可以为没有明确请求的键加载缓存值。例如,为某组中的任意键计算值时,能够获取该组中的所有键值,loadAll方法就可以实现为在同一时间获取该组的其他键值。校注:getAll(Iterable<? extends K>)方法会调用loadAll,但会筛选结果,只会返回请求的键值对。

    Callable

    所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K, Callable)方法。这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法简便地实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。

    Cache<Key, Value> cache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .build(); // look Ma, no CacheLoader
    ...
    try {
      // If the key wasn't in the "easy to compute" group, we need to
      // do things the hard way.
      cache.get(key, new Callable<Value>() {
        @Override
        public Value call() throws AnyException {
          return doThingsTheHardWay(key);
        }
      });
    } catch (ExecutionException e) {
      throw new OtherException(e.getCause());
    }
    

    显式插入

    使用cache.put(key, value)方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中。进一步说,asMap视图的原子运算在Guava Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K, V),Cache.get(K, Callable) 应该总是优先使用。

    缓存回收

    一个残酷的现实是,我们几乎一定没有足够的内存缓存所有数据。你你必须决定:什么时候某个缓存项就不值得保留了?Guava Cache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。

    基于容量回收(size-based eviction)

    如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。

    另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。在权重限定场景中,除了要注意回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。

    LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
           .maximumWeight(100000)
           .weigher(new Weigher<Key, Graph>() {
              public int weigh(Key k, Graph g) {
                return g.vertices().size();
              }
            })
           .build(
               new CacheLoader<Key, Graph>() {
                 public Graph load(Key key) { // no checked exception
                   return createExpensiveGraph(key);
                 }
               });
    

    定时回收(Timed Eviction)

    CacheBuilder提供两种定时回收的方法:

    • expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
    • expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
      如下文所讨论,定时回收周期性地在写操作中执行,偶尔在读操作中执行。

    测试定时回收
    对定时回收进行测试时,不一定非得花费两秒钟去测试两秒的过期。你可以使用Ticker接口和CacheBuilder.ticker(Ticker)方法在缓存中自定义一个时间源,而不是非得用系统时钟。

    基于引用的回收(Reference-based Eviction)

    通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:

    • CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用键的缓存用而不是equals比较键。
    • CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用值的缓存用而不是equals比较值。
    • CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。

    显式清除

    任何时候,你都可以显式地清除缓存项,而不是等到它被回收:

    • 个别清除:Cache.invalidate(key)
    • 批量清除:Cache.invalidateAll(keys)
    • 清除所有缓存项:Cache.invalidateAll()

    移除监听器

    通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知RemovalNotification,其中包含移除原因RemovalCause、键和值。

    请注意,RemovalListener抛出的任何异常都会在记录到日志后被丢弃swallowed

    CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () {
      public DatabaseConnection load(Key key) throws Exception {
        return openConnection(key);
      }
    };
    RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() {
      public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) {
        DatabaseConnection conn = removal.getValue();
        conn.close(); // tear down properly
      }
    };
    
    return CacheBuilder.newBuilder()
      .expireAfterWrite(2, TimeUnit.MINUTES)
      .removalListener(removalListener)
      .build(loader);
    

    警告:默认情况下,监听器方法是在移除缓存时同步调用的。因为缓存的维护和请求响应通常是同时进行的,代价高昂的监听器方法在同步模式下会拖慢正常的缓存请求。在这种情况下,你可以使用RemovalListeners.asynchronous(RemovalListener, Executor)把监听器装饰为异步操作。

    清理什么时候发生?

    使用CacheBuilder构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。相反,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话。

    这样做的原因在于:如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。

    相反,我们把选择权交到你手里。如果你的缓存是高吞吐的,那就无需担心缓存的维护和清理等工作。如果你的 缓存只会偶尔有写操作,而你又不想清理工作阻碍了读操作,那么可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()ScheduledExecutorService可以帮助你很好地实现这样的定时调度。

    刷新

    刷新和回收不太一样。正如LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。

    如果刷新过程抛出异常,缓存将保留旧值,而异常会在记录到日志后被丢弃swallowed

    重载CacheLoader.reload(K, V)可以扩展刷新时的行为,这个方法允许开发者在计算新值时使用旧的值。

    LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
           .maximumSize(1000)
           .refreshAfterWrite(1, TimeUnit.MINUTES)
           .build(
               new CacheLoader<Key, Graph>() {
                 public Graph load(Key key) { // no checked exception
                   return getGraphFromDatabase(key);
                 }
    
                 public ListenableFuture<Graph> reload(final Key key, Graph prevGraph) {
                   if (neverNeedsRefresh(key)) {
                     return Futures.immediateFuture(prevGraph);
                   } else {
                     // asynchronous!
                     ListenableFutureTask<Graph> task = ListenableFutureTask.create(new Callable<Graph>() {
                       public Graph call() {
                         return getGraphFromDatabase(key);
                       }
                     });
                     executor.execute(task);
                     return task;
                   }
                 }
               });
    

    CacheBuilder.refreshAfterWrite(long, TimeUnit)可以为缓存增加自动定时刷新功能。和expireAfterWrite相反,refreshAfterWrite通过定时刷新可以让缓存项保持可用,但请注意:缓存项只有在被检索时才会真正刷新(如果CacheLoader.refresh实现为异步,那么检索不会被刷新拖慢)。因此,如果你在缓存上同时声明expireAfterWrite和refreshAfterWrite,缓存并不会因为刷新盲目地定时重置,如果缓存项没有被检索,那刷新就不会真的发生,缓存项在过期时间后也变得可以回收。

    其他特性

    统计

    CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回CacheStats对象以提供如下统计信息:

    • hitRate():缓存命中率;
    • averageLoadPenalty():加载新值的平均时间,单位为纳秒;
    • evictionCount():缓存项被回收的总数,不包括显式清除。
      此外,还有其他很多统计信息。这些统计信息对于调整缓存设置是至关重要的,在性能要求高的应用中我们建议密切关注这些数据。

    asMap

    asMap视图提供了缓存的ConcurrentMap形式,但asMap视图与缓存的交互需要注意:

    • cache.asMap()包含当前所有加载到缓存的项。因此相应地,cache.asMap().keySet()包含当前所有已加载键;
    • asMap().get(key)实质上等同于cache.getIfPresent(key),而且不会引起缓存项的加载。这和Map的语义约定一致。所有读写操作都会重置相关缓存项的访问时间,包括Cache.asMap().get(Object)方法和Cache.asMap().put(K, V)方法,但不包括Cache.asMap().containsKey(Object)方法,也不包括在Cache.asMap()的集合视图上的操作。比如,遍历Cache.asMap().entrySet()不会重置缓存项的读取时间。

    中断

    缓存加载方法(如Cache.get)不会抛出InterruptedException。我们也可以让这些方法支持InterruptedException,但这种支持注定是不完备的,并且会增加所有使用者的成本,而只有少数使用者实际获益。详情请继续阅读。

    Cache.get请求到未缓存的值时会遇到两种情况:当前线程加载值;或等待另一个正在加载值的线程。这两种情况下的中断是不一样的。等待另一个正在加载值的线程属于较简单的情况:使用可中断的等待就实现了中断支持;但当前线程加载值的情况就比较复杂了:因为加载值的CacheLoader是由用户提供的,如果它是可中断的,那我们也可以实现支持中断,否则我们也无能为力。

    如果用户提供的CacheLoader是可中断的,为什么不让Cache.get也支持中断?从某种意义上说,其实是支持的:如果CacheLoader抛出InterruptedException,Cache.get将立刻返回(就和其他异常情况一样);此外,在加载缓存值的线程中,Cache.get捕捉到InterruptedException后将恢复中断,而其他线程中InterruptedException则被包装成了ExecutionException。

    原则上,我们可以拆除包装,把ExecutionException变为InterruptedException,但这会让所有的LoadingCache使用者都要处理中断异常,即使他们提供的CacheLoader不是可中断的。如果你考虑到所有非加载线程的等待仍可以被中断,这种做法也许是值得的。但许多缓存只在单线程中使用,它们的用户仍然必须捕捉不可能抛出的InterruptedException异常。即使是那些跨线程共享缓存的用户,也只是有时候能中断他们的get调用,取决于那个线程先发出请求。

    对于这个决定,我们的指导原则是让缓存始终表现得好像是在当前线程加载值。这个原则让使用缓存或每次都计算值可以简单地相互切换。如果老代码(加载值的代码)是不可中断的,那么新代码(使用缓存加载值的代码)多半也应该是不可中断的。

    如上所述,Guava Cache在某种意义上支持中断。另一个意义上说,Guava Cache不支持中断,这使得LoadingCache成了一个有漏洞的抽象:当加载过程被中断了,就当作其他异常一样处理,这在大多数情况下是可以的;但如果多个线程在等待加载同一个缓存项,即使加载线程被中断了,它也不应该让其他线程都失败(捕获到包装在ExecutionException里的InterruptedException),正确的行为是让剩余的某个线程重试加载。为此,我们记录了一个bug。然而,与其冒着风险修复这个bug,我们可能会花更多的精力去实现另一个建议AsyncLoadingCache,这个实现会返回一个有正确中断行为的Future对象。

    参考:https://github.com/google/guava/wiki/CachesExplained
    参考:https://www.jianshu.com/p/88ec858cc021?from=singlemessage
    参考:https://ifeve.com/google-guava-cachesexplained/

    展开全文
  • 前言最近在一个项目中需要用到本地缓存,在网上调研后,发现谷歌的Guva提供的cache模块非常的不错。简单易上手的api;灵活强大的功能,再加上谷歌这块金字招牌,让我毫不犹豫的选择了它。仅以此博客记录我在使用过程...
  • Guava Cache是一个全内存的本地缓存实现,本文将讲述如何将 Guava Cache缓存应用到 Spring Boot应用中。具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • guava cache详细介绍

    千次阅读 2020-09-03 22:53:02
    guava cache是google开源的一款本地缓存工具库,它的设计灵感来源于ConcurrentHashMap,使用多个segments方式的细粒度锁,在保证线程安全的同时,支持高并发场景需求,同时支持多种类型的缓存清理策略,包括基于容量...
  • 本文从源码的角度浅析了Guava Cache的实现机制。主要通过get方法的执行过程介绍了key映射到value、CacheLoader.load的执行、过期策略的执行等三个问题。
  • 高并发之——Guava Cache

    千次阅读 2019-10-27 11:18:02
    最近需要用到缓存来存放临时数据,又不想采用Redis,Java自带的Map功能太少,发现Google的Guava提供的Cache模块功能很强大,于是选择使用它。 本地缓存 本地缓存作用就是提高系统的运行速度,是一种空间换时间的...
  • Guava Cache缓存入门

    2020-07-11 20:18:03
    四、Guava Cache工作方式 GuavaCache的工作流程:获取数据->如果存在,返回数据->计算获取数据->存储返回。由于特定的工作流程,使用者必须在创建Cache或者获取数据时指定不存在数据时应当怎么获取数据。GuavaCache...
  • Google Guava Cache使用 适用场景: 通常来说,Guava Cache适用于: 你愿意消耗一些内存空间来提升速度。 你预料到某些键会被查询一次以上。 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时...
  • 在大部分互联网架构中 Cache 已经成为了必可不少的一环。常用的方案有大家熟知的 NoSQL 数据库(Redis、Memcached),也有大量的进程内缓存比如 EhCache 、Gua...
  • Guava Cache原理

    2020-10-27 10:16:07
    public static void initCache(LoadingCache cache) throws ExecutionException { for(int i =1;i<=3;i++){ //连接数据源 ,如果缓存没有就读取数据源 cache.get(String.valueOf(i)); } } /** * 获得...
  • Springboot集成Guava Cache

    千次阅读 2020-07-13 17:16:03
    一、首先导入依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>...二、guava的配置,那个24指的是24小时
  • 总结请一定要记住GuavaCache的实现代码中没有启动任何线程!!Cache中的所有维护操作,包括清除缓存、写入缓存等,都是通过调用线程来操作的。这在需要低延迟服务场景中使用时尤其需要关注,可能会在某个调用的响应...
  • Guava Cache 简单介绍

    2020-08-28 21:44:26
    Guava Cache Guava Cache介绍 Guava是Google提供的一套Java工具包,而Guava Cache是一套非常完善的本地缓存机制(JVM缓 存)。 Guava cache的设计来源于CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值且...
  • Guava cache使用总结

    2021-03-07 11:46:08
    缓存分为本地缓存和远端缓存。常见的远端缓存有Redis,MongoDB;本地缓存一般使用map的方式保存在本地内存中。...今天说的 Guava Cache 是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存中。他很好
  • Guava Cache缓存 Guava Cache 是Google Fuava中的一个内存缓存模块,用于将数据缓存到JVM内存中。 提供了get、put封装操作,能够集成数据源 ; 线程安全的缓存,与ConcurrentMap相似,但前者增加了更多的元素...
  • Guava Cache 过期回源

    千次阅读 2020-09-29 15:12:58
    缓存的更新有两种方法: 被动更新:先从缓存获取,...guava cache解决办法: guava cache保证单线程回源,对于同一个key,只让一个请求回源load,其他线程阻塞等待结果。同时,在Guava里可以通过配置expireAfterAcces
  • 最近在做业务需求时需要使用本地缓存,故了解了一下本地缓存的框架,最终选择了Guava Cache作为本地缓存的方案 业务场景: 原先业务系统根据id把某个数据进行了缓存,缓存使用的是分布式缓存redis,但是由于缓存的值...
  • Guava Cache 使用学习

    万次阅读 多人点赞 2018-02-05 20:01:51
    缓存框架Guava Cache部分源码分析 概述 缓存是日常开发中经常应用到的一种技术手段,合理的利用缓存可以极大的改善应用程序的性能。 Guava官方对Cache的描述连接 缓存在各种各样的用例中非常有用。例如,当...
  • 详解 Guava Cache

    2019-04-19 17:00:50
    一、Guava Cache 一般在项目中,本地缓存的实现为 ConcurrentHashMap,它具有线程安全、持久有效的特点。但是相较于传统缓存,它不具备缓存过期、缓存移除等特性,Google Guava 包内的 Cache 模块可能会给你一个新的...
  • Guava Cache用法介绍

    万次阅读 2019-08-02 21:54:12
    —扫描二维码—加入架构集结群对技术感兴趣的同学可进群(备注:Java)Guava Cache是在内存中缓存数据,相比较于数据库或redis存储,访问内存中的数据会...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,696
精华内容 5,478
关键字:

GuavaCache