文档章节

Guava Cache 更新缓存相关源码解读

o
 osc_bkdv2it5
发布于 2019/08/18 16:23
字数 1204
阅读 14
收藏 0

精选30+云产品,助力企业轻松上云!>>>

背景

  前段时间,运营反馈某管理后台偶发响应时间超长。通过查看日志,定位到问题主要是在权限验证时发生了大量的数据库查询,该后台的权限管理使用了Guava中的Cache组件缓存用户信息,同时设置了缓存过期时间为20s,当缓存过期后的第一个请求会触发缓存刷新,从数据库中获取大量的用户信息到内存中,这就导致了响应时间过长。因此趁着这个机会,阅读下Guava Cache更新缓存的相关源码。

更新缓存的方法

  通过查阅Guava Cache的官方文档了解到,更新缓存的方式有两种

  • 缓存定时过期
    • CacheBuilder.expireAfterAccess(long, TimeUnit):缓存在设置时间内没有被读/写访问,则过期。
    • CacheBuilder.expireAfterWrite(long, TimeUnit):缓存键值创建后并经过设置的时间,则过期。
  • 缓存定时刷新
    • CacheBuilder.refreshAfterWrite(long, TimeUnit):缓存键值创建后并经过设置的时间,则刷新。

  而这两种方法的具体实现原理及区别是什么呢,下面我们通过阅读源码进行了解。

源码解读

  通过调用Cache的get方法进行debug,逐行往下看,我们不难定位到下面截取的源码的第10行这里,前面先是获取了该key值对应的entry,然后将该entry和当前时间now作为参数调用getLiveValue方法,这个方法的作用是从entry中获取value,若已过期,则返回null。

 1 V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
 2       checkNotNull(key);
 3       checkNotNull(loader);
 4       try {
 5         if (count != 0) {
 6           ReferenceEntry<K, V> e = getEntry(key, hash);
 7           if (e != null) {
 8             long now = map.ticker.read();
 9             // 从entry中获取value,若已过期,则返回null
10             V value = getLiveValue(e, now);
11             if (value != null) {
12               recordRead(e, now);
13               statsCounter.recordHits(1);
14               // 定时刷新
15               return scheduleRefresh(e, key, hash, value, now, loader);
16             }
17             ValueReference<K, V> valueReference = e.getValueReference();
18             if (valueReference.isLoading()) {
19               return waitForLoadingValue(e, key, valueReference);
20             }
21           }
22         }
23         // 返回null或者缓存过期时会调用这个方法,加锁获取或load
24         return lockedGetOrLoad(key, hash, loader);
25     }

   我们接着往下走,发现方法下面的在第13行进行了是否过期的判断,接着进入isExpired(ReferenceEntry<K, V> entry, long now)方法中,这个方法比较简单,先判断是否有设置expireAfterAccess或expireAfterWrite,然后通过当前时间和对应的访问或写操作时间的时间差值与设置的过期时间进行对比。当缓存过期时,getLiveValue返回null,最后会调用lockedGetOrLoad方法加载新值。

 1     V getLiveValue(ReferenceEntry<K, V> entry, long now) {
 2       if (entry.getKey() == null) {
 3         tryDrainReferenceQueues();
 4         return null;
 5       }
 6       V value = entry.getValueReference().get();
 7       if (value == null) {
 8         tryDrainReferenceQueues();
 9         return null;
10       }
11 
12       // 判断entry是否过期
13       if (map.isExpired(entry, now)) {
14         tryExpireEntries(now);
15         return null;
16       }
17       return value;
18     }
 1   boolean isExpired(ReferenceEntry<K, V> entry, long now) {
 2     checkNotNull(entry);
 3     if (expiresAfterAccess() && (now - entry.getAccessTime() >= expireAfterAccessNanos)) {
 4       return true;
 5     }
 6     if (expiresAfterWrite() && (now - entry.getWriteTime() >= expireAfterWriteNanos)) {
 7       return true;
 8     }
 9     return false;
10   }

   当缓存没过期或没设置过期时间时,getLiveValue返回不为null,这时会调用scheduleRefresh()方法。这个方法首先去判断是否设置了定时刷新和是否超过了设定的刷新时间,然后判断当前的ValueReference是否为LoadingValueReference,条件都成立的话,会调用refresh()方法,这个方法首先会将该entry的ValueReference设为上面提到的LoadingValueReference,表示该缓存项处于loading状态,之后进行load的操作。从这里可以看出,loading标识保证了同一缓存项,只会存在一个线程进行refresh时的load操作,在load未完成期间,其他访问该缓存项的线程都会直接返回oldValue。

 1     V scheduleRefresh(
 2         ReferenceEntry<K, V> entry,
 3         K key,
 4         int hash,
 5         V oldValue,
 6         long now,
 7         CacheLoader<? super K, V> loader) {
 8       if (map.refreshes()
 9           && (now - entry.getWriteTime() > map.refreshNanos)
10           && !entry.getValueReference().isLoading()) {
11         V newValue = refresh(key, hash, loader, true);
12         if (newValue != null) {
13           return newValue;
14         }
15       }
16       return oldValue;
17     }
 1     V refresh(K key, int hash, CacheLoader<? super K, V> loader, boolean checkTime) {
 2       final LoadingValueReference<K, V> loadingValueReference =
 3           insertLoadingValueReference(key, hash, checkTime);
 4       if (loadingValueReference == null) {
 5         return null;
 6       }
 7 
 8       ListenableFuture<V> result = loadAsync(key, hash, loadingValueReference, loader);
 9       if (result.isDone()) {
10         try {
11           return Uninterruptibles.getUninterruptibly(result);
12         } catch (Throwable t) {
13           // don't let refresh exceptions propagate; error was already logged
14         }
15       }
16       return null;
17     }

总结

  从上面的源码阅读可以知道,缓存定时过期和缓存定时刷新这两种更新缓存的方式的主要区别是,前者缓存过期时,可能会存在多个线程同时进行load操作,阻塞多个线程,而后者更新缓存时,会标记loading状态,这时只会阻塞一个线程,其他线程继续返回旧值,同时我们可以通过CacheLoader的reload(K key, V oldValue)方法来进行异步刷新。

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
好文收集(不断更新)

Java基础知识 初始化顺序 Java类初始化顺序 Java向前引用容易出错的地方 Java中的前向引用与类初始化顺序 权限控制 Java 访问权限控制:你真的了解 protected 关键字吗? 枚举 重新认识java(...

osc_42ssirsd
2019/03/08
2
0
Guava 源码分析之Cache的实现原理

前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛。 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的。 缓存 本次主要讨论缓存。缓存在日...

crossoverJie
2018/01/20
0
0
Guava cacha 机制及源码分析

1、ehcahce 什么时候用比较好; 2、问题:当有个消息的key不在guava里面的话,如果大量的消息过来,会同时请求数据库吗?还是只有一个请求数据库,其他的等待第一个把数据从DB加载到Guava中 ...

osc_js57pdnd
2019/10/24
1
0
Guava cache 构建无阻塞缓存

Guava cache 构建无阻塞缓存 适用场景: guava cache是本地缓存,基于JDK ConcurrentHashMap 实现。不适合分布式环境使用(除非可以保证其节点缓存一致性如: 开源中国社区开源的 J2cache)。...

Lien6o
2019/08/17
39
0
Guava 源码分析(Cache 原理)

前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛。 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的。 缓存 本次主要讨论缓存。 缓存在日...

crossoverJie
2018/08/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

在Bash脚本中,如果发生某种情况,如何退出整个脚本?

问题: I'm writing a script in Bash to test some code. 我正在Bash中编写脚本来测试一些代码。 However, it seems silly to run the tests if compiling the code fails in the first pl......

技术盛宴
19分钟前
11
0
Windows安装Python+OpenCV

1、更新PyCharm中pip来源,使用清华和阿里云:https://pypi.tuna.tsinghua.edu.cn/simple/ http://mirrors.aliyun.com/pypi/simple/ 2、PyCharm查看已安装packets,添加新的安装包,从pip云端...

极客行
43分钟前
17
0
tomcat8配置虚拟目录,实现一个tomcat运行两个项目, tomcat配置URL不区分大小写

<?xml version="1.0" encoding="UTF-8"?><!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distri......

青峰Jun19er
49分钟前
19
0
HBase和MySQL存储方式的差别?或者说是,行存储和列存储的区别?

HBase借鉴列存储的思想,但是最底层依然是依靠键值对来存储数据,HBase为非关系型数据库 而MySQL则是行存储,MySQL为关系型数据库 写过程 行存储因为数据是连续的,所以只需要进行追加即可;...

其乐m
53分钟前
25
0
一个老程序员在互联网寒冬下的感悟

1. 你千万不要认为学习技术就可以换来稳定的生活和高的薪水待遇,你更不要认为那些从事市场开发,跑腿的人,没有前途。 不清楚你是不是知道,咱们中国有相当大的一部分软件公司,他们的软件开...

北柠Java
57分钟前
39
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部