A comparison of local caches (2) 【本地缓存之比较 (2)】
接上一篇: A comparison of local caches (1) 【本地缓存之比较 (1)】
This article will compare the asynchronous local caches.
Currently Spring @Cacheable doesn't support async cache by default (we can use some tricks to achieve the goal though).
Guava and Caffiene support async cache with refresh method.
本文会对异步刷新的本地缓存做个介绍。
目前 Spring 的 @Cacheable 注解默认不支持异步刷新(可以使用一些特殊的技巧来实现,这里暂时不提)
Guava 和 Caffiene 的 refresh 方法都对缓存的异步刷新提供了很好的支持。
1. Guava
public class GuavaAsyncCacheExample {
static AtomicInteger counter = new AtomicInteger(0);
static ListeningExecutorService exe = MoreExecutors.listeningDecorator(Executors.newWorkStealingPool());
static LoadingCache<Integer, Integer> refreshCache = CacheBuilder.newBuilder()
.refreshAfterWrite(3, TimeUnit.SECONDS)
.recordStats()
.build(new CacheLoader<Integer, Integer>() {
@Override
public Integer load(Integer key) throws Exception {
return getIntegerCache();
}
public ListenableFuture<Integer> reload(Integer key, Integer oldValue) throws Exception {
return exe.submit(() -> getIntegerCache());
}
});
private static Integer getIntegerCache() throws Exception {
System.out.println("populate cache " + LocalDateTime.now());
Thread.sleep(2000);
//if (counter.incrementAndGet() >= 2) throw new Exception("error");
return counter.incrementAndGet() * 10;
}
public static void main(String[] args) throws Exception {
useAsyncCache();
}
static void useAsyncCache() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(refreshCache.get(0) + " -- " + LocalDateTime.now());
Thread.sleep(1000);
}
}
}
Console output:
populate cache 2017-06-12T15:21:11.026
10 -- 2017-06-12T15:21:13.028
10 -- 2017-06-12T15:21:14.029
10 -- 2017-06-12T15:21:15.029
populate cache 2017-06-12T15:21:16.046
10 -- 2017-06-12T15:21:16.068
10 -- 2017-06-12T15:21:17.069
20 -- 2017-06-12T15:21:18.069
20 -- 2017-06-12T15:21:19.069
20 -- 2017-06-12T15:21:20.069
20 -- 2017-06-12T15:21:21.069
populate cache 2017-06-12T15:21:21.069
20 -- 2017-06-12T15:21:22.070
Be very careful that if we omit the reload method when implementing CacheLoader, the cache will not be load in async way anymore.
注意下,如果不小心忘了重写 reload 方法,缓存将不再被异步刷新。
// public ListenableFuture<Integer> reload(Integer key, Integer oldValue) throws Exception {
// return exe.submit(() -> getIntegerCache());
// }
populate cache 2017-06-12T15:30:58.924 10 -- 2017-06-12T15:31:00.928 10 -- 2017-06-12T15:31:01.928 10 -- 2017-06-12T15:31:02.928 populate cache 2017-06-12T15:31:03.929 20 -- 2017-06-12T15:31:05.960 20 -- 2017-06-12T15:31:06.960 20 -- 2017-06-12T15:31:07.960 populate cache 2017-06-12T15:31:08.961 30 -- 2017-06-12T15:31:10.961 30 -- 2017-06-12T15:31:11.962 30 -- 2017-06-12T15:31:12.962 populate cache 2017-06-12T15:31:13.962 40 -- 2017-06-12T15:31:15.963
Previously cache can be retrieved immediately. After removing the reload method, the get method will be blocked until load method completes.
上图的日志就是在注释掉 reload 方法的情况下得到的。可以明显的看到当缓存过期之后,再次取缓存会被缓慢的 load 方法阻塞掉。
Hmmm, what happens if an error occurs during cache loading? Let's modify the loading part a little bit.
对了,如果 load 方法执行过程中不小心出错会怎么样呢? 我们来稍微修改下加载逻辑。
private static Integer getIntegerCache() throws Exception {
System.out.println("populate cache " + LocalDateTime.now());
Thread.sleep(2000);
if (counter.incrementAndGet() >= 2) throw new Exception("error");
return counter.get() * 10;
}
Accordign to the output, guava gives warning message, but the old value is still returned.
通过输出日志可以看到,Guava会抛出警告日志,返回旧值。
populate cache 2017-06-12T15:37:08.437
10 -- 2017-06-12T15:37:10.440
10 -- 2017-06-12T15:37:11.441
10 -- 2017-06-12T15:37:12.441
populate cache 2017-06-12T15:37:13.441
Jun 12, 2017 3:37:15 PM com.google.common.cache.LocalCache$Segment$1 run
WARNING: Exception thrown during refresh
java.util.concurrent.ExecutionException: java.lang.Exception: error
at com.google.common.util.concurrent.AbstractFuture$Sync.getValue(AbstractFuture.java:299)
at com.google.common.util.concurrent.AbstractFuture$Sync.get(AbstractFuture.java:286)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:116)
at com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly(Uninterruptibles.java:135)
at com.google.common.cache.LocalCache$Segment.getAndRecordStats(LocalCache.java:2346)
at com.google.common.cache.LocalCache$Segment$1.run(LocalCache.java:2329)
at com.google.common.util.concurrent.MoreExecutors$SameThreadExecutorService.execute(MoreExecutors.java:297)
at com.google.common.util.concurrent.ExecutionList.executeListener(ExecutionList.java:156)
at com.google.common.util.concurrent.ExecutionList.add(ExecutionList.java:101)
at com.google.common.util.concurrent.AbstractFuture.addListener(AbstractFuture.java:170)
at com.google.common.cache.LocalCache$Segment.loadAsync(LocalCache.java:2324)
at com.google.common.cache.LocalCache$Segment.refresh(LocalCache.java:2387)
at com.google.common.cache.LocalCache$Segment.scheduleRefresh(LocalCache.java:2365)
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2185)
at com.google.common.cache.LocalCache.get(LocalCache.java:3934)
at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3938)
at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4821)
at cachecmp.GuavaAsyncCacheExample.useAsyncCache(GuavaAsyncCacheExample.java:49)
at cachecmp.GuavaAsyncCacheExample.main(GuavaAsyncCacheExample.java:44)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.Exception: error
at cachecmp.GuavaAsyncCacheExample.getIntegerCache(GuavaAsyncCacheExample.java:39)
at cachecmp.GuavaAsyncCacheExample.access$000(GuavaAsyncCacheExample.java:17)
at cachecmp.GuavaAsyncCacheExample$1.load(GuavaAsyncCacheExample.java:28)
at cachecmp.GuavaAsyncCacheExample$1.load(GuavaAsyncCacheExample.java:25)
at com.google.common.cache.CacheLoader.reload(CacheLoader.java:97)
at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527)
at com.google.common.cache.LocalCache$Segment.loadAsync(LocalCache.java:2323)
... 13 more
10 -- 2017-06-12T15:37:15.475
populate cache 2017-06-12T15:37:16.476
Jun 12, 2017 3:37:18 PM com.google.common.cache.LocalCache$Segment$1 run
10 -- 2017-06-12T15:37:18.478
2. Caffiene
public class CaffeineAsyncCacheExample {
static AtomicInteger counter = new AtomicInteger(0);
static LoadingCache<Integer, Integer> refreshCache = Caffeine.newBuilder()
.refreshAfterWrite(3, TimeUnit.SECONDS)
.recordStats()
.build(new CacheLoader<Integer, Integer>() {
@Override
public Integer load(Integer key) throws Exception {
return getIntegerCache();
}
});
private static Integer getIntegerCache() throws Exception {
System.out.println("populate cache " + LocalDateTime.now());
Thread.sleep(2000);
return counter.incrementAndGet() * 10;
}
public static void main(String[] args) throws Exception {
useAsyncCache();
}
static void useAsyncCache() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(refreshCache.get(0) + " -- " + LocalDateTime.now());
Thread.sleep(1000);
}
}
}
Opps, again, the reload method is omitted. But the output looks perfectly fine, what happened?
啊哦,一不小心又忘记写 reload 方法了。 然而仔细一看输出日志,竟毫无阻塞的问题。
populate cache 2017-06-12T15:49:48.043 10 -- 2017-06-12T15:49:50.047 10 -- 2017-06-12T15:49:51.047 10 -- 2017-06-12T15:49:52.047 populate cache 2017-06-12T15:49:53.063 10 -- 2017-06-12T15:49:53.086 10 -- 2017-06-12T15:49:54.087 20 -- 2017-06-12T15:49:55.088 20 -- 2017-06-12T15:49:56.088 20 -- 2017-06-12T15:49:57.089 20 -- 2017-06-12T15:49:58.089 populate cache 2017-06-12T15:49:58.089 20 -- 2017-06-12T15:49:59.090
In order to dig out the truth, let's take a look at the source code of CacheLoader.
为了探索背后的原因,我们来看看CacheLoader的源码
Guava
public abstract class CacheLoader<K, V> {
protected CacheLoader() {
}
public abstract V load(K var1) throws Exception;
@GwtIncompatible("Futures")
public ListenableFuture<V> reload(K key, V oldValue) throws Exception {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(oldValue);
return Futures.immediateFuture(this.load(key));
}
......
Caffiene
@FunctionalInterface
@ThreadSafe
public interface CacheLoader<K, V> extends AsyncCacheLoader<K, V> {
@CheckForNull
V load(@Nonnull K var1) throws Exception;
@CheckForNull
default V reload(@Nonnull K key, @Nonnull V oldValue) throws Exception {
return this.load(key);
}
@Nonnull
default CompletableFuture<V> asyncReload(@Nonnull K key, @Nonnull V oldValue, @Nonnull Executor executor) {
Objects.requireNonNull(key);
Objects.requireNonNull(executor);
return CompletableFuture.supplyAsync(() -> {
try {
return this.reload(key, oldValue);
} catch (RuntimeException var4) {
throw var4;
} catch (Exception var5) {
throw new CompletionException(var5);
}
}, executor);
}
...
By default, Guava calls reload method while Caffiene calls asyncReload method, that's why we only need to rewrite load method in Caffiene in stead. It saves us some coding effort, but more importantly, it decreases the possibility to make mistakes. For example, some one may forgot to update the reload method after making some changes in load block.
默认情况下,Guava 调用 reload 方法,reload 又会去阻塞调用 load 方法, 而Caffiene 默认调用 asyncReload 方法,并不阻塞,这就是为什么使用 Caffiene 的时候只需要重写 load 方法即可。这不光是节省了一点代码,更重要的,它降低了之后出错的几率。很有可能某位别的程序员日后修改了 load 方法的实现 却忘记了 reload 方法,于是出现错误。
A comparison of local caches (2) 【本地缓存之比较 (2)】的更多相关文章
- A comparison of local caches (1) 【本地缓存之比较 (1)】
1. Spring local cache [Spring 本地缓存] Spring provided cacheable annotation since 3.1. It's very supe ...
- 八、React实战:可交互待办事务表(表单使用、数据的本地缓存local srtorage、生命同期函数(页面加载就会执行函数名固定为componentDidMount()))
一.项目功能概述 示例网址:http://www.todolist.cn/ 功能: 输入待做事项,回车,把任务添加到 [正在进行] [正在进行] 任务,勾选之后,变成已[经完成事项] [已完成事务], ...
- spring boot: 用redis的消息订阅功能更新应用内的caffeine本地缓存(spring boot 2.3.2)
一,为什么要更新caffeine缓存? 1,caffeine缓存的优点和缺点 生产环境中,caffeine缓存是我们在应用中使用的本地缓存, 它的优势在于存在于应用内,访问速度最快,通常都不到1ms就 ...
- spring boot:使用spring cache+caffeine做进程内缓存(本地缓存)(spring boot 2.3.1)
一,为什么要使用caffeine做本地缓存? 1,spring boot默认集成的进程内缓存在1.x时代是guava cache 在2.x时代更新成了caffeine, 功能上差别不大,但后者在性能上 ...
- Java8简单的本地缓存实现
原文出处:lukaseder Java8简单的本地缓存实现 这里我将会给大家演示用ConcurrentHashMap类和lambda表达式实现一个本地缓存.因为Map有一个新的方法,在 ...
- iOS五种本地缓存数据方式
iOS五种本地缓存数据方式 iOS本地缓存数据方式有五种:前言 1.直接写文件方式:可以存储的对象有NSString.NSArray.NSDictionary.NSData.NSNumber,数据 ...
- ImageLoader(多线程网络图片加载)+本地缓存 for windowsphone 7
搞了好长一阵子wp,做点好事. C/S手机app中应用最多的是 获取网络图片,缓存到本地,展示图片 本次主要对其中的delay:LowProfileImageLoader进行修改,在获取图片的时候, ...
- lua模块demo(redis,http,mysql,cjson,本地缓存)
1. lua模块demo(redis,http,mysql,cjson,本地缓存) 1.1. 配置 在nginx.conf中设置lua_shared_dict my_cache 128m; 开启ngi ...
- ASP.NET MVC深入浅出(被替换) 第一节: 结合EF的本地缓存属性来介绍【EF增删改操作】的几种形式 第三节: EF调用普通SQL语句的两类封装(ExecuteSqlCommand和SqlQuery ) 第四节: EF调用存储过程的通用写法和DBFirst模式子类调用的特有写法 第六节: EF高级属性(二) 之延迟加载、立即加载、显示加载(含导航属性) 第十节: EF的三种追踪
ASP.NET MVC深入浅出(被替换) 一. 谈情怀-ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态 ...
随机推荐
- windows和linux双系统修改启动项
在windows系统的基础上再装linux系统的时候,电脑启动就会出现linux的启动引导项,默认的是进入linux.要使电脑默认进入windows只需要修改linux系统的启动引导文件(grub.c ...
- Linux 基础(2)
Linux 基础(二) 用户 组 及权限的相关操作 一.useradd命令选项:–u:指定用户的UID useradd –u 100 mu #指定mu的UID为100–g:指定用户所属的群组 user ...
- bzoj4800 [Ceoi2015]Ice Hockey World Championship
Description 有n个物品,m块钱,给定每个物品的价格,求买物品的方案数. Input 第一行两个数n,m代表物品数量及钱数 第二行n个数,代表每个物品的价格 n<=40,m<=1 ...
- python 错误之SyntaxError: Missing parentheses in call to 'print'
SyntaxError: Missing parentheses in call to 'print' 由于python的版本差异,造成的错误. python2: print "hello ...
- Vue 项目实战系列 (一)
最近一直在学习Vue,基本的文档看完后就需要进行具体的项目进行练手了,本系列文章主要是将我学习过程记录下来,和大家一起学习交流. 我在git上找到了一个淘票票的Vue项目,项目地址: https:// ...
- 【原创】Android 5.0 BLE低功耗蓝牙从设备应用
如果各位觉得有用,转载+个出处. 现如今安卓的低功耗蓝牙应用十分普遍了,智能手环.手表遍地都是,基本都是利用BLE通信来交互数据.BLE基本在安卓.IOS两大终端设备上都有很好支持,所以有很好发展前景 ...
- hadoop集群间的hdfs文件拷贝
1.背景 部门有个需求,在网络互通的情况下,把现有的hadoop集群(未做Kerberos认证,集群名为:bd-stg-hadoop)的一些hdfs文件拷贝到新的hadoop集群(做了Kerberos ...
- 从零开始构建一个的asp.net Core 项目
最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到"He ...
- thinkphp 框架中的一部分方法解析
1 thinkphp 框架 中判断输入的数值和数据库中的数值是否一致 首先 需要在view文件夹下建一个模板 名为zhuce.html <html> <head> &l ...
- 1013 Realtime Status
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission( ...