前言

Google 出的 Guava 是 Java 核心增强的库,应用非常广泛。

我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的。

缓存

本次主要讨论缓存。

缓存在日常开发中举足轻重,如果你的应用对某类数据有着较高的读取频次,并且改动较小时那就非常适合利用缓存来提高性能。

缓存之所以可以提高性能是因为它的读取效率很高,就像是 CPU 的 L1、L2、L3 缓存一样,级别越高相应的读取速度也会越快。

但也不是什么好处都占,读取速度快了但是它的内存更小资源更宝贵,所以我们应当缓存真正需要的数据。

其实也就是典型的空间换时间。

下面谈谈 Java 中所用到的缓存。

JVM 缓存

首先是 JVM 缓存,也可以认为是堆缓存。

其实就是创建一些全局变量,如 Map、List 之类的容器用于存放数据。

这样的优势是使用简单但是也有以下问题:

  • 只能显式的写入,清除数据。
  • 不能按照一定的规则淘汰数据,如 LRU,LFU,FIFO 等。
  • 清除数据时的回调通知。
  • 其他一些定制功能等。

Ehcache、Guava Cache

所以出现了一些专门用作 JVM 缓存的开源工具出现了,如本文提到的 Guava Cache。

它具有上文 JVM 缓存不具有的功能,如自动清除数据、多种清除算法、清除回调等。

但也正因为有了这些功能,这样的缓存必然会多出许多东西需要额外维护,自然也就增加了系统的消耗。

分布式缓存

刚才提到的两种缓存其实都是堆内缓存,只能在单个节点中使用,这样在分布式场景下就招架不住了。

于是也有了一些缓存中间件,如 Redis、Memcached,在分布式环境下可以共享内存。

具体不在本次的讨论范围。

Guava Cache 示例

之所以想到 Guava 的 Cache,也是最近在做一个需求,大体如下:

从 Kafka 实时读取出应用系统的日志信息,该日志信息包含了应用的健康状况。

如果在时间窗口 N 内发生了 X 次异常信息,相应的我就需要作出反馈(报警、记录日志等)。

对此 Guava 的 Cache 就非常适合,我利用了它的 N 个时间内不写入数据时缓存就清空的特点,在每次读取数据时判断异常信息是否大于 X 即可。

伪代码如下:


@Value("${alert.in.time:2}")
private int time ; @Bean
public LoadingCache buildCache(){
return CacheBuilder.newBuilder()
.expireAfterWrite(time, TimeUnit.MINUTES)
.build(new CacheLoader<Long, AtomicLong>() {
@Override
public AtomicLong load(Long key) throws Exception {
return new AtomicLong(0);
}
});
} /**
* 判断是否需要报警
*/
public void checkAlert() {
try {
if (counter.get(KEY).incrementAndGet() >= limit) {
LOGGER.info("***********报警***********"); //将缓存清空
counter.get(KEY).getAndSet(0L);
}
} catch (ExecutionException e) {
LOGGER.error("Exception", e);
}
}

首先是构建了 LoadingCache 对象,在 N 分钟内不写入数据时就回收缓存(当通过 Key 获取不到缓存时,默认返回 0)。

然后在每次消费时候调用 checkAlert() 方法进行校验,这样就可以达到上文的需求。

我们来设想下 Guava 它是如何实现过期自动清除数据,并且是可以按照 LRU 这样的方式清除的。

大胆假设下:

内部通过一个队列来维护缓存的顺序,每次访问过的数据移动到队列头部,并且额外开启一个线程来判断数据是否过期,过期就删掉。有点类似于我之前写过的 动手实现一个 LRU cache

胡适说过:大胆假设小心论证

下面来看看 Guava 到底是怎么实现。

原理分析

看原理最好不过是跟代码一步步走了:

示例代码在这里:

https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/CacheLoaderTest.java

为了能看出 Guava 是怎么删除过期数据的在获取缓存之前休眠了 5 秒钟,达到了超时条件。

最终会发现在 com.google.common.cache.LocalCache 类的 2187 行比较关键。

再跟进去之前第 2182 行会发现先要判断 count 是否大于 0,这个 count 保存的是当前缓存的数量,并用 volatile 修饰保证了可见性。

更多关于 volatile 的相关信息可以查看 你应该知道的 volatile 关键字

接着往下跟到:

2761 行,根据方法名称可以看出是判断当前的 Entry 是否过期,该 entry 就是通过 key 查询到的。

这里就很明显的看出是根据根据构建时指定的过期方式来判断当前 key 是否过期了。

如果过期就往下走,尝试进行过期删除(需要加锁,后面会具体讨论)。

到了这里也很清晰了:

  • 获取当前缓存的总数量
  • 自减一(前面获取了锁,所以线程安全)
  • 删除并将更新的总数赋值到 count。

其实大体上就是这个流程,Guava 并没有按照之前猜想的另起一个线程来维护过期数据。

应该是以下原因:

  • 新起线程需要资源消耗。
  • 维护过期数据还要获取额外的锁,增加了消耗。

而在查询时候顺带做了这些事情,但是如果该缓存迟迟没有访问也会存在数据不能被回收的情况,不过这对于一个高吞吐的应用来说也不是问题。

总结

最后再来总结下 Guava 的 Cache。

其实在上文跟代码时会发现通过一个 key 定位数据时有以下代码:

如果有看过 ConcurrentHashMap 的原理 应该会想到这其实非常类似。

其实 Guava Cache 为了满足并发场景的使用,核心的数据结构就是按照 ConcurrentHashMap 来的,这里也是一个 key 定位到一个具体位置的过程。

先找到 Segment,再找具体的位置,等于是做了两次 Hash 定位。

上文有一个假设是对的,它内部会维护两个队列 accessQueue,writeQueue 用于记录缓存顺序,这样才可以按照顺序淘汰数据(类似于利用 LinkedHashMap 来做 LRU 缓存)。

同时从上文的构建方式来看,它也是构建者模式来创建对象的。

因为作为一个给开发者使用的工具,需要有很多的自定义属性,利用构建则模式再合适不过了。

Guava 其实还有很多东西没谈到,比如它利用 GC 来回收内存,移除数据时的回调通知等。之后再接着讨论。

最后插播个小广告:

Java-Interview 截止目前将近 8K star。

这次定个小目标:争取冲击 1W star

感谢各位老铁的支持与点赞。

欢迎关注公众号一起交流:

Guava 源码分析(Cache 原理)的更多相关文章

  1. Guava 源码分析(Cache 原理 对象引用、事件回调)

    前言 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理. 文末提到了回收机制.移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析. 在开 ...

  2. Guava 源码分析之Cache的实现原理

    Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Goog ...

  3. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  4. Spring Boot 揭秘与实战 源码分析 - 工作原理剖析

    文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoCo ...

  5. Tomcat源码分析——请求原理分析(下)

    前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在& ...

  6. Tomcat源码分析——请求原理分析(中)

    前言 在<TOMCAT源码分析——请求原理分析(上)>一文中已经介绍了关于Tomcat7.0处理请求前作的初始化和准备工作,请读者在阅读本文前确保掌握<TOMCAT源码分析——请求原 ...

  7. Tomcat源码分析——请求原理分析(上)

    前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程 ...

  8. wifidog源码分析 - wifidog原理 tiger

    转:http://www.cnblogs.com/tolimit/p/4223644.html wifidog源码分析 - wifidog原理 wifidog是一个用于配合认证服务器实现无线网页认证功 ...

  9. [Guava源码分析]ImmutableCollection:不可变集合

    摘要: 我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3888557.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的 ...

  10. [Guava源码分析]Ordering:排序

    我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3876466.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的阅读体验 ...

随机推荐

  1. react-native自定义TextInput光标颜色

    <TextInput defaultValue="Highlight Color is red" selectionColor={'red'} style={styles.s ...

  2. Do-Now—团队 冲刺博客六

    Do-Now-团队 冲刺博客六 作者:仇夏 前言 终于从四级的考试中解脱了(不过我觉得可能凉凉,呵呵),我们的APP制作也迎来了最后的两天. 自己觉得自己其实没有干成什么事情,代码什么的大都是队友们写 ...

  3. git逻辑和基本命令

    提交和推送的区别 提交(commit):把您做的修改,保存到本地仓库中 推送(push):把您本地仓库的代码推送至服务器(一般是远程服务器及gitlab或github) 拉取和获取的区别 git  p ...

  4. jmeter学习笔记(一)

    1.添加JSON Path Extractor >>下载地址:http://jmeter-plugins.org/downloads/all/,下载 JMeterPlugins-Extra ...

  5. Mac自动化环境

    1. JDK安装 下载JDK for Mac 我这里使用的是  jdk-7u79-macosx-x64.dmg 验证安装open Terminal  java -version java versio ...

  6. Centos7 编译安装 Nginx Mariadb Asp.net Core2 (实测 笔记 Centos 7.3 + Openssl 1.1.0h + Mariadb 10.3.7 + Nginx 1.14.0 + Asp.net. Core 2 )

    环境: 系统硬件:vmware vsphere (CPU:2*4核,内存2G,双网卡) 系统版本:CentOS-7-x86_64-Minimal-1611.iso 安装步骤: 1.准备 1.0 查看硬 ...

  7. java简单的双色球摇号程序

    import java.util.HashSet; import java.util.Random; import java.util.Set; /** * LotteryClient * @auth ...

  8. pip和cmd常用命令

    1.pip常用命令 显示模块的详情  pip    show 安装模块   pip    install    模块名称 卸载模块    pip    uninstall    模块名称 查看当前环境 ...

  9. oracle11G 用户密码180天修改概要文件过程

    oracle11G 用户密码180天修改概要文件过程 原因 创建用户的时候不指定概要文件的默认的概要文件是default, 而默认的概要文件中的设置如下,注意斜体部分 PROFILE RESOURCE ...

  10. Win10系统下在国内访问Tensorflow官网

    1.修改hosts文件 目录:     C:\Windows\System32\drivers\etc 添加: #TensorFlow start64.233.188.121 www.tensorfl ...