Guava Cache缓存设计原理(下)

简介: Guava Cache基于ConcurrentHashMap的设计,在高并发场景支持和线程安全上都有相应改进策略,使用Reference引用命令,提升高并发下的数据访问速度并保持了GC的可回收,有效节省空间。write链和access链的设计,能更灵活、高效的实现多种类型的缓存清理策略,包括基于容量的清理、基于时间的清理、基于引用的清理等。编程式的build生成器管理,让使用者有更多的自由度,能够根据不同场景设置合适的模式。还可以显式清除、统计信息、移除事件的监听器、自动加载等功能。

1.png缓存相关操作

Segment的evict清除策略

在每次调用操作的开始和结束时触发清理工作,这样比一般的缓存另起线程监控清理相比,可减少开销。

但若长时间没有调用方法,会导致不能及时清理释放内存空间。

evict主要处理四个Queue:

  • keyReferenceQueue
  • valueReferenceQueue
  • writeQueue
  • accessQueue


前两个queue是因为WeakReference、SoftReference被垃圾回收时加入的,清理时只需遍历整个queue,将对应项从LocalCache移除即可:

  • keyReferenceQueue存放ReferenceEntry
  • valueReferenceQueue存放的是ValueReference


要从Cache中移除需要有key,因而ValueReference需要有对ReferenceEntry的引用。


后两个Queue,只需检查是否配置了相应的expire时间,然后从头开始查找已经expire的Entry,将它们移除。


Segment中的put操作:put操作相对比较简单,首先它需要获得锁,然后尝试做一些清理工作,接下来的逻辑类似ConcurrentHashMap中的rehash,查找位置并注入数据。需要说明的是当找到一个已存在的Entry时,需要先判断当前的ValueRefernece中的值事实上已经被回收了,因为它们可以是WeakReference、SoftReference类型,如果已经被回收了,则将新值写入。并且在每次更新时注册当前操作引起的移除事件,指定相应的原因:COLLECTED、REPLACED等,这些注册的事件在退出的时候统一调用Cache注册的RemovalListener,由于事件处理可能会有很长时间,因而这里将事件处理的逻辑在退出锁以后才做。最后,在更新已存在的Entry结束后都尝试着将那些已经expire的Entry移除。另外put操作中还需要更新writeQueue和accessQueue的语义正确性。


Segment带CacheLoader的get操作

V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
      checkNotNull(key);
      checkNotNull(loader);
      try {
        if (count != 0) { // read-volatile
          // don't call getLiveEntry, which would ignore loading values
          ReferenceEntry<K, V> e = getEntry(key, hash);
          if (e != null) {
            long now = map.ticker.read();
            V value = getLiveValue(e, now);
            if (value != null) {
              recordRead(e, now);
              statsCounter.recordHits(1);
              return scheduleRefresh(e, key, hash, value, now, loader);
            }
            ValueReference<K, V> valueReference = e.getValueReference();
            if (valueReference.isLoading()) {
              return waitForLoadingValue(e, key, valueReference);
            }
          }
        }
        // at this point e is either null or expired;
        return lockedGetOrLoad(key, hash, loader);
      } catch (ExecutionException ee) {
        Throwable cause = ee.getCause();
        if (cause instanceof Error) {
          throw new ExecutionError((Error) cause);
        } else if (cause instanceof RuntimeException) {
          throw new UncheckedExecutionException(cause);
        }
        throw ee;
      } finally {
        postReadCleanup();
      }
    }

1. 先查找table中是否已存在没有被回收、也没有expire的entry,如果找到,并在CacheBuilder中配置了refreshAfterWrite,并且当前时间间隔已经操作这个事件,则重新加载值,否则,直接返回原有的值

2. 如果查找到的ValueReference是LoadingValueReference,则等待该LoadingValueReference加载结束,并返回加载的值

image.png

3. 如果没有找到entry,或者找到的entry的值为null,则加锁后,继续在table中查找已存在key对应的entry,如果找到并且对应的entry.isLoading()为true,则表示有另一个线程正在加载,因而等待那个线程加载完成,如果找到一个非null值,返回该值,否则创建一个LoadingValueReference

image.png

并调用loadSync加载相应的值

image.png

在加载完成后,将新加载的值更新到table中,即大部分情况下替换原来的LoadingValueReference

CacheBuilder

提供Builder模式的CacheBuilder生成器来创建缓存。

各个缓存参数的配置设置,类似函数式编程,可自行设置各类参数选型。

Cache<String,String> cache = CacheBuilder.newBuilder()    
  .maximumSize(1024)
  .expireAfterWrite(60,TimeUnit.SECONDS)
  .weakValues() .build(); 
cache.put("word","Hello Guava Cache"); 
System.out.println(cache.getIfPresent("word"));

它提供三种方式加载到缓存:

  1. 在构建缓存的时候,使用build方法内部调用CacheLoader方法加载数据;
  2. callable 、callback方式加载数据;
  3. 直接Cache.put 加载数据,但自动加载是首选的,因为它更容易推断所有缓存内容的一致性


build生成器的两种方式都实现了一种逻辑:

从缓存中取key的值,如果该值已经缓存过了则返回缓存中的值,如果没有缓存过可以通过某个方法来获取这个值

CacheLoader

针对整个cache定义的,可认为是统一的根据K load V的方法:

1.png

callable

较为灵活,允许你在get时指定load方法

image.png

image.png

总结

Guava Cache基于ConcurrentHashMap的设计,在高并发场景支持和线程安全上都有相应改进策略,使用Reference引用命令,提升高并发下的数据访问速度并保持了GC的可回收,有效节省空间。


write链和access链的设计,能更灵活、高效的实现多种类型的缓存清理策略,包括基于容量的清理、基于时间的清理、基于引用的清理等。


编程式的build生成器管理,让使用者有更多的自由度,能够根据不同场景设置合适的模式。


还可以显式清除、统计信息、移除事件的监听器、自动加载等功能。


目录
相关文章
|
13天前
|
缓存 NoSQL Java
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
Spring Cache 是 Spring 提供的简易缓存方案,支持本地与 Redis 缓存。通过添加 `spring-boot-starter-data-redis` 和 `spring-boot-starter-cache` 依赖,并使用 `@EnableCaching` 开启缓存功能。JetCache 由阿里开源,功能更丰富,支持多级缓存和异步 API,通过引入 `jetcache-starter-redis` 依赖并配置 YAML 文件启用。Layering Cache 则提供分层缓存机制,需引入 `layering-cache-starter` 依赖并使用特定注解实现缓存逻辑。
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
|
23天前
|
缓存 Java Spring
Guava缓存工具类封装和使用
Guava缓存工具类封装和使用
20 0
|
26天前
|
存储 缓存 监控
Redis问题之如何使用Guava Cache来监控缓存的加载/命中情况
Redis问题之如何使用Guava Cache来监控缓存的加载/命中情况
|
26天前
|
存储 缓存 监控
Redis问题之使用Guava Cache相比自己设计本地缓存有哪些优势
Redis问题之使用Guava Cache相比自己设计本地缓存有哪些优势
|
23天前
|
缓存 NoSQL Java
Redis 缓存与数据库数据不一致问题
Redis 缓存与数据库数据不一致问题
51 3
|
1月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
18天前
|
缓存 NoSQL 关系型数据库
(八)漫谈分布式之缓存篇:唠唠老生常谈的MySQL与Redis数据一致性问题!
本文来聊一个跟实际工作挂钩的老生常谈的问题:分布式系统中的缓存一致性。
72 10
|
20天前
|
缓存 NoSQL Serverless
函数计算产品使用问题之如何使用Redis作为缓存插件
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
23天前
|
存储 缓存 NoSQL
Redis 缓存常见问题
Redis 缓存常见问题
31 3