好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Guava Cache的使用简介

1 引入

说到缓存,可能大家最先想到的还是Redis。作为基于键值对的非关系型数据库,Redis具有高性能、丰富的数据结构、持久化、高可用、分布式等特性,使其在业内得到了广泛的认可和使用。但是,使用Redis必然涉及到网络连接,当网络连接不稳定或网络耗时严重时,必然会影响到我们的业务使用。如果我们想提高我们的业务性能,又减少对其他机器的依赖,那么,使用本地缓存会是一个不错的选择。

使用本地缓存时,大多时候我们会采用ConcurrentHashMap来实现。对于本地缓存的使用,现在有一些较为成熟的本地缓存工具,如ehcache、guava cache,以及Caffeine。当需要对缓存进行持久化操作时,可以考虑使用ehcache。如果没有持久化操作,可以考虑使用guava cache或caffeine,caffeine是基于guava cache进行的二次优化,可根据自身业务需要选择使用哪一种本地缓存工具,本文将针对在DRS系统中使用到的 Guava Cache 进行讲解。

在本次项目中,选择的guava cache版本信息如下:

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

2 Guava Cache介绍

Guava cache和ConcurrentHashMap很相似,都是采用的key-value键值对的方式进行数据的存储,都采用了分段加锁的方式来提高多线程下的并发操作。不同的是,使用ConcurrentHashMap进行缓存存储时,会一直保留缓存数据,直到系统重启或显示删除缓存,而guava cache支持缓存过期时间的设置,可以进行缓存清理操作。同时,guava cache还可以自动加载缓存,当我们需要从数据库中加载数据到缓存时,不需要每次在获取缓存时判断缓存是否存在,guava cache会按照我们设置的加载方式进行数据的加载。除此之外,guava cache还可以进行一些统计操作,如统计缓存的命中率、加载新值的平均时间等。正是由于Guava cache的这些特性,我们才选择它应用于DRS系统中。下面,来看看我们是如何使用Guava Cache进行缓存管理操作的吧。

3 缓存的过期时间设置

Guava Cache支持三种过期设置,分别是基于容量的回收、定时回收,以及基于引用的回收。显然,基于容量的回收和基于引用的回收跟缓存时间没关系。当我们需要设置缓存的缓存的过期时间时,使用的必然是定时回收的回收策略,那么,在guava cache中,支持两种定时回收策略,分别是基于读写访问的回收与基于写访问的回收,也就是缓存在设置的时间内没有被读写访问或写访问,缓存将会被回收,这种特性是可以满足我们过期时间设置要求的。这两种方法分别是: expireAfterAccess(long, TimeUnit):基于读写访问的回收,缓存项在给定时间内没有被读/写访问,则回收 expireAfterWrite(long, TimeUnit):基于写访问的回收,即缓存项在给定时间内没有被写访问,则回收 如下,给出了基于写访问的回收策略,过期时间设置的是3秒:

Cache < String , String > cache = CacheBuilder . newBuilder () . expireAfterWrite ( 3 , TimeUnit . SECONDS ) . build ();

但是,在我们的项目中,需要针对不同的key设置不同的过期时间。而上面的示例中,只能满足一种过期时间的设置,所有key的过期时间都是一样的。为了满足不同过期时间的需求,这里我们采用了一个两级结构的guava cache来实现,如下所示:

private Cache < String , Cache < String , String >> cacheCache = CacheBuilder . newBuilder (). build ();   public void init () { Cache < String , String > cache1 = CacheBuilder . newBuilder () . expireAfterWrite ( 3 , TimeUnit . SECONDS ) . build (); cache1 . put ( "appName" , "drs-server" ); Cache < String , String > cache2 = CacheBuilder . newBuilder () . expireAfterWrite ( 2 , TimeUnit . SECONDS ) . build (); cache2 . put ( "appName" , "drs-ops" ); cacheCache . put ( "key1" , cache1 ); cacheCache . put ( "key2" , cache2 ); }

在上面的示例中,还可以使用Map+guava cache的方式,也就是一个map里面套一个guava cache,定义如下:

Map < String , Cache < String , String >> cacheMap = new HashMap < String , Cache < String , String >>();

那为什么我们要使用一个两级的cache结构而不是Map+cache的方式呢?原因在于,当读取的数据在缓存中没有时,我们是希望能够自动去从数据库加载的,所以使用两级的cache结构更合适。说到这里,就不得不提到guava cache的加载机制了。

4 缓存加载机制

guava cache有两种方式加载,一种是在构建缓存对象的时候,在build方法中设置缓存的加载方式(仅LocalCache对象可用);另一种是在获取缓存对象的时候,通过实现Callable接口方法的方式设置加载方式。 采用如下的方式构建缓存对象:

LoadingCache < String , String > cache = CacheBuilder . newBuilder () . build ( new CacheLoader < String , String >() { @Override public String load ( String s ) throws Exception { return "load: " + UUID . randomUUID (). toString (). substring ( 1 , 6 ); } });

通过上述方式构建完LocalCache的缓存对象,当调用get(K key)方法获取缓存时,如果指定的key对应的缓存值不存在,则会从load的方法中加载对象。但是,如果使用的是getIfPresent方法,则不会从执行load方法中加载缓存值,而是返回null值。当时用get(K key, Callable call) 获取缓存时,如果对应的缓存值不存在,则会从实现的Callable接口方法中来加载缓存。完整的示例代码如下所示:

public static void main ( String [] args ) throws Exception { LoadingCache < String , String > cache = CacheBuilder . newBuilder () . build ( new CacheLoader < String , String >() { @Override public String load ( String s ) throws Exception { return "load: " + UUID . randomUUID (). toString (). substring ( 1 , 6 ); } });   System . out . println ( cache . getIfPresent ( "appName1" )); System . out . println ( cache . get ( "appName2" ));   String loadValue = cache . get ( "appName3" , new Callable < String >() { @Override public String call () throws Exception { return "call: " + UUID . randomUUID (). toString (). substring ( 1 , 6 ); } }); System . out . println ( loadValue ); }

测试结果如下:

5 缓存清理

既然我们给缓存设置了过期时间,那么,缓存过期时间到了后,又是怎么来清理的呢?实际上,过期后的缓存并不会[自动]进行清理,而是在下一次进行读访问或是写访问的时候,通过判断当前时间与缓存加载进来时的时间进行比较,如果时间差大于给定的过期时间,则会清理缓存。如果设置了缓存的加载方式,则会重新加载缓存值,缓存的加载时间也会更新。通过代码调试可以发现,缓存清理是在get方法里实现的,用到的以下几个方法:

get ( Object key , int hash ); // 获取缓存值 getLiveEntry ( Object key , int hash , long now ); // 获取存活的对象 isExpired ( ReferenceEntry < K , V > entry , long now ); // 判断是否过期 expireEntries ( long now ); // 清理缓存

其中,判断是否过期的逻辑如下:

如果缓存已过期,则返回true,然后执行缓存清理删除操作,如下所示:

除此之外,guava cache还支持缓存的一些统计工作,如统计缓存命中率、加载新值的平均时间、缓存项被回收的总数等,还有缓存中断操作和刷新操作,由于本次项目中没有使用到这些特性,没有进行深入的了解,感兴趣的同学如有使用到,可以去官方网站进行深入了解,详见 ifeve.com/google-guav…

以上就是Guava Cache的使用简介的详细内容,更多关于Guava Cache的使用的资料请关注其它相关文章!

原文链接:https://juejin.cn/post/6944952864003325988

查看更多关于Guava Cache的使用简介的详细内容...

  阅读:17次