官方:http://ifeve.com/google-guava-cachesexplained/

理解:https://segmentfault.com/a/1190000007300118

项目中用到cache的例子:

 public class TokenCache {

     //打印日志
private static Logger logger = LoggerFactory.getLogger(TokenCache.class); public static final String TOKEN_PREFIX = "token_"; //LRU算法
//initialCapacity(1000)设置cache的初始大小为1000
//maximumSize(10000)设置缓存个数为10000,当个数超过10000会利用LRU算法删除部分缓存
//expireAfterAccess(12, TimeUnit.HOURS)设置cache中的数据在写入之后的存活时间为12小时,TimeUnit.HOURS表示12的单位是小时
//key和value都是String类型
private static LoadingCache<String, String> localCache = CacheBuilder.newBuilder().initialCapacity(1000).maximumSize(10000).expireAfterAccess(12, TimeUnit.HOURS).build(
new CacheLoader<String, String>() {
//默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法进行加载
//当本地缓存命没有中时,调用load方法获取结果并将结果缓存
@Override
public String load(String s) throws Exception {
//因为key和value都是String类型,在下面调用getKey时返回的value值也是String类型,虽然有可能是null值但是也是string类型,所以这里返回"null"而不是null
return "null";
}
}
); public static void setKey(String key, String value) {
localCache.put(key, value);
} public static String getKey(String key) {
String value = null;
try {
value = localCache.get(key);
//这里用"null"字符串来进行判空
if("null".equals(value)) {
return null;
}
return value;
} catch (Exception e) {
logger.error("localCache get error", e);
}
return null;
}
}

存入cache:

 //把token放入本地cache中,然后设置其有效期
TokenCache.setKey(TokenCache.TOKEN_PREFIX + username, forgetToken);

读出cache:

        //从cache中获取token,根据key拿到value值
String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX + username);
//校验cache中的token是否有效
if(StringUtils.isBlank(token)) {
return ServerResponse.createByErrorMessage("token无效或者过期");
}

使用解释:

 final static Cache<Integer, String> cache = CacheBuilder.newBuilder()
//设置cache的初始大小为10,要合理设置该值
.initialCapacity(10)
//设置并发数为5,即同一时间最多只能有5个线程往cache执行写入操作
.concurrencyLevel(5)
//设置cache中的数据在写入之后的存活时间为10秒
.expireAfterWrite(10, TimeUnit.SECONDS)
//构建cache实例
.build();

据说GuavaCache的实现是基于ConcurrentHashMap的,因此上面的构造过程所调用的方法,通过查看其官方文档也能看到一些类似的原理。比如通过initialCapacity(5)定义初始值大小,要是定义太大就好浪费内存空间,要是太小,需要扩容的时候就会像map一样需要resize,这个过程会产生大量需要gc的对象,还有比如通过concurrencyLevel(5)来限制写入操作的并发数,这和ConcurrentHashMap的锁机制也是类似的(ConcurrentHashMap读不需要加锁,写入需要加锁,每个segment都有一个锁)。

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

  Guava Cache适用场景:

  • 你愿意消耗一部分内存来提升速度;
  • 你已经预料某些值会被多次调用;
  • 缓存数据不会超过内存总量;

 Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。

接下来看看Cache提供哪些方法(只列了部分常用的):

 /**
* 该接口的实现被认为是线程安全的,即可在多线程中调用
* 通过被定义单例使用
*/
public interface Cache<K, V> { /**
* 通过key获取缓存中的value,若不存在直接返回null
*/
V getIfPresent(Object key); /**
* 通过key获取缓存中的value,若不存在就通过valueLoader来加载该value
* 整个过程为 "if cached, return; otherwise create, cache and return"
* 注意valueLoader要么返回非null值,要么抛出异常,绝对不能返回null
*/
V get(K key, Callable<? extends V> valueLoader) throws ExecutionException; /**
* 添加缓存,若key存在,就覆盖旧值
*/
void put(K key, V value); /**
* 删除该key关联的缓存
*/
void invalidate(Object key); /**
* 删除所有缓存
*/
void invalidateAll(); /**
* 执行一些维护操作,包括清理缓存
*/
void cleanUp();
}

清除缓存的策略

任何Cache的容量都是有限的,而缓存清除策略就是决定数据在什么时候应该被清理掉。GuavaCache提了以下几种清除策略:
 1.基于存活时间的清除(Timed Eviction)
这应该是最常用的清除策略,在构建Cache实例的时候,CacheBuilder提供两种基于存活时间的构建方法:
(1)expireAfterAccess(long, TimeUnit):缓存项在创建后,在给定时间内没有被读/写访问,则清除。
(2)expireAfterWrite(long, TimeUnit):缓存项在创建后,在给定时间内没有被写访问(创建或覆盖),则清除。

expireAfterWrite()方法有些类似于redis中的expire命令,但显然它只能设置所有缓存都具有相同的存活时间。若遇到一些缓存数据的存活时间为1分钟,一些为5分钟,那只能构建两个Cache实例了。
2.基于容量的清除(size-based eviction)
在构建Cache实例的时候,通过CacheBuilder.maximumSize(long)方法可以设置Cache的最大容量数,当缓存数量达到或接近该最大值时,Cache将清除掉那些最近最少使用的缓存。
以上是这种方式是以缓存的“数量”作为容量的计算方式,还有另外一种基于“权重”的计算方式。比如每一项缓存所占据的内存空间大小都不一样,可以看作它们有不同的“权重”(weights)。你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。
3.显式清除
任何时候,你都可以显式地清除缓存项,而不是等到它被回收,Cache接口提供了如下API:
(1)个别清除:Cache.invalidate(key)
(2)批量清除:Cache.invalidateAll(keys)
(3)清除所有缓存项:Cache.invalidateAll()

4.基于引用的清除(Reference-based Eviction)

在构建Cache实例过程中,通过设置使用弱引用的键、或弱引用的值、或软引用的值,从而使JVM在GC时顺带实现缓存的清除,不过一般不轻易使用这个特性。
(1)CacheBuilder.weakKeys():使用弱引用存储键
(2)CacheBuilder.weakValues():使用弱引用存储值
(3)CacheBuilder.softValues():使用软引用存储值

清除什么时候发生?

也许这个问题有点奇怪,如果设置的存活时间为一分钟,难道不是一分钟后这个key就会立即清除掉吗?我们来分析一下如果要实现这个功能,那Cache中就必须存在线程来进行周期性地检查、清除等工作,很多cache如redis、ehcache都是这样实现的。
但在GuavaCache中,并不存在任何线程!它实现机制是在写操作时顺带做少量的维护工作(如清除),偶尔在读操作时做(如果写操作实在太少的话),也就是说在使用的是调用线程,参考如下示例:
 public class CacheService {
static Cache<Integer, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build(); public static void main(String[] args) throws Exception {
new Thread() { //monitor
public void run() {
while(true) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) +" size: "+cache.size());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
};
}.start();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
cache.put(1, "Hi");
System.out.println("write key:1 ,value:"+cache.getIfPresent(1));
Thread.sleep(10000);
// when write ,key:1 clear
cache.put(2, "bbb");
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));
}
}

控制台输出:

 00:34:17 size: 0
write key:1 ,value:Hi
00:34:19 size: 1
00:34:21 size: 1
00:34:23 size: 1
00:34:25 size: 1
write key:2 ,value:bbb
00:34:27 size: 1
00:34:29 size: 1
00:34:31 size: 1
00:34:33 size: 1
00:34:35 size: 1
00:34:37 after write, key:1 ,value:null
00:34:37 size: 1
00:34:39 final, key:2 ,value:null
00:34:39 size: 0
通过分析发现:
(1)缓存项<1,"Hi">的存活时间是5秒,但经过5秒后并没有被清除,因为还是size=1
(2)发生写操作cache.put(2, "bbb")后,缓存项<1,"Hi">被清除,因为size=1,而不是size=2
(3)发生读操作cache.getIfPresent(1)后,缓存项<2,"bbb">没有被清除,因为还是size=1,看来读操作确实不一定会发生清除
(4)发生读操作cache.getIfPresent(2)后,缓存项<2,"bbb">被清除,因为读的key就是2

这在GuavaCache被称为“延迟删除”,即删除总是发生得比较“晚”,这也是GuavaCache不同于其他Cache的地方!这种实现方式的问题:缓存会可能会存活比较长的时间,一直占用着内存。如果使用了复杂的清除策略如基于容量的清除,还可能会占用着线程而导致响应时间变长。但优点也是显而易见的,没有启动线程,不管是实现,还是使用起来都让人觉得简单(轻量)。
如果你还是希望尽可能的降低延迟,可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp(),ScheduledExecutorService可以帮助你很好地实现这样的定时调度。不过这种方式依然没办法百分百的确定一定是自己的维护线程“命中”了维护的工作。

总结

请一定要记住GuavaCache的实现代码中没有启动任何线程!!Cache中的所有维护操作,包括清除缓存、写入缓存等,都是通过调用线程来操作的。这在需要低延迟服务场景中使用时尤其需要关注,可能会在某个调用的响应时间突然变大。
GuavaCache毕竟是一款面向本地缓存的,轻量级的Cache,适合缓存少量数据。如果你想缓存上千万数据,可以为每个key设置不同的存活时间,并且高性能,那并不适合使用GuavaCache。

Guava Cache相关的更多相关文章

  1. Spring cache简单使用guava cache

    Spring cache简单使用 前言 spring有一套和各种缓存的集成方式.类似于sl4j,你可以选择log框架实现,也一样可以实现缓存实现,比如ehcache,guava cache. [TOC ...

  2. 第七章 企业项目开发--本地缓存guava cache

    1.在实际项目开发中,会使用到很多缓存技术,而且数据库的设计一般也会依赖于有缓存的情况下设计. 常用的缓存分两种:本地缓存和分布式缓存. 常用的本地缓存是guava cache,本章主要介绍guava ...

  3. google guava cache缓存基本使用讲解

    代码地址:https://github.com/vikde/demo-guava-cache 一.简介 guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存 ...

  4. guava cache使用和源码分析

    guava cache的优点和使用场景,用来判断业务中是否适合使用此缓存 介绍常用的方法,并给出示例,作为使用的参考 深入解读源码. guava简介 guava cache是一个本地缓存.有以下优点: ...

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

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

  6. 分布式系统缓存系列之guava cache

      guava是google的一个开源java框架,其github地址是 https://github.com/google/guava.guava工程包含了若干被Google的 Java项目广泛依赖 ...

  7. Spring Boot 揭秘与实战(二) 数据缓存篇 - Guava Cache

    文章目录 1. Guava Cache 集成 2. 个性化配置 3. 源代码 本文,讲解 Spring Boot 如何集成 Guava Cache,实现缓存. 在阅读「Spring Boot 揭秘与实 ...

  8. Google guava cache源码解析1--构建缓存器(3)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 下面介绍在LocalCache(CacheBuilder, CacheLoader)中调用的一些方法: Ca ...

  9. Google guava cache源码解析1--构建缓存器(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHas ...

随机推荐

  1. 树莓派的WIFI配置

    参考网址: http://www.cnblogs.com/iusmile/archive/2013/03/30/2991139.html http://my.oschina.net/pikeman/b ...

  2. LeetCode 24——两两交换链表中的节点

    1. 题目 2. 解答 新建一个哨兵结点作为头结点,然后每次交换相邻两个结点.并依次将它们连接到新链表中去,再将原链表中后面的结点也串到新链表后面.直至到达链尾或者剩余一个节点,则此时返回新链表的头结 ...

  3. visionpro halcon 哪个好

    visionpro halcon 哪个好 很多朋友会问到visionpro和halcon这两款机器视觉软件,到底学哪个好呢,今天众寻网就给大家讲一讲: 首先比较下两者的优缺点: halcon: 提供的 ...

  4. Drools 7.4.1.Final参考手册(十四)集成Spring

    集成Spring Drools 6.0重要变更 Drools Spring集成经历了与Drools 6.0的变化完全一致的改造. 以下是一些主要的变化: T*推荐的Drools Spring的前缀已经 ...

  5. windows2008 R2 系统 安装wampserver提示“缺少msvcr110.dll文件”处理办法

    windows2008 R2 系统 安装wampserver提示“缺少msvcr110.dll文件”处理办法 原因分析: 因缺少Visual C++ Redistributable for Visua ...

  6. 子组件通过$emit触发父组件的事件时,参数的传递

    子组件.vue <template> <div> <el-table :data="comSchemaData" highlight-current- ...

  7. java生成和解析二维码

    前言 现在,二维码的应用已经非常广泛,在线生成器也是诸多,随手生成. 所以就和大家分享一个小案例,用zxing来做一个的二维码生成器,当然这个例子是比较简单,若是写的不好请多多包涵. ZXING项目是 ...

  8. 日期时间选择器datetimepicker.js

    在做项目中,往往会遇到需要用户输入2014-07-19 09:55:53这样的格式的数据.就是典型的年月日时分秒这样的格式.这个时候,使用datetimepicker会比较简单. DateTimePi ...

  9. 【算法】Prüfer编码 —— HNOI2004树的计数

    的确,如果不知道这个编码的话的确是一脸懵逼.在这里放一篇认为讲的很详细的 BLOG,有关于编码的方式 & 扩展在里面都有所提及. 欢迎点此进入 --> 大佬的博客 在这里主要想推导一下最 ...

  10. 安装全局webpack

    npm ls webpack 和npm ls webpack -g 查看本地和全局版本 npm install webpack@1.15.0 -g 全局 然后到项目里面 npm install npm ...