[Java 缓存] Java Cache之 Guava Cache的简单应用.
前言
今天第一次使用MarkDown的形式发博客. 准备记录一下自己对Guava Cache的认识及项目中的实际使用经验.
一: 什么是Guava
Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。 所有这些工具每天都在被Google的工程师应用在产品服务中。
//Guava Cache的使用
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return createExpensiveGraph(key);
}
});
...
return graphs.getUnchecked(key);
二: 使用场景
当我们使用一种新工具的时候 我们总要先弄清楚它到底适用于什么样的场景.
- 你愿意消耗一些内存空间来提升速度。
- 你预料到某些键会被查询一次以上。
- 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
如果你的场景符合上述的每一条,Guava Cache就适合你。
三: 核心类图

四: 使用实例
前面说了这么多, 都不如如何使用来的实在. 现在直接贴出来使用的实例, 具体实现的逻辑大家可以看下源码, 这里也会有一些实际的讲解.
在pom文件中引入Guava Cache的坐标:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
下面拿我们实际项目中使用的一个GuavaCache来举例:
public abstract class BaseCacheService<K,V> {
private LoadingCache<K,V> cache;
public BaseCacheService(){
cache = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES)
.build(new CacheLoader<K, V>() {
@Override
public V load(K k) throws Exception {
return loadData(k);
}
});
}
public BaseCacheService(long duration){
cache = CacheBuilder.newBuilder()
.expireAfterWrite(duration, TimeUnit.MINUTES)
.build(new CacheLoader<K, V>() {
@Override
public V load(K k) throws Exception {
return loadData(k);
}
});
}
protected abstract V loadData(K k);
public V getCache(K param){
return cache.getUnchecked(param);
}
//更新缓存中数据
public void refresh(K k){
cache.refresh(k);
}
}
这里我是抽象出来了一个BaseCacheService, 当我们使用时则可以继承这个抽象类:
如果我们第一次请求, 那么这会执行这里面的load方法去数据库中查询相应的值, 当第二次请求时这会从缓存中直接返回了.
@Service
public class MaterialInfoCacheService extends BaseCacheService<Long, List<MaterialInfoDto>> {
@Override
protected List<MaterialInfoDto> loadData(Long key) {
//具体的查询数据库得到数据的逻辑.
return materialInfoDtos;
}
}
这里面有关于缓存的回收(expireAfterWrite), 有关于缓存的刷新(refresh)等, 这些东西会一一来介绍.
缓存的回收:
1, 基于容量的回收(size-based eviction)
如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。
另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。在权重限定场景中,除了要注意回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumWeight(100000)
.weigher(new Weigher<Key, Graph>() {
public int weigh(Key k, Graph g) {
return g.vertices().size();
}
})
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return createExpensiveGraph(key);
}
});
2, 定时回收(Timed Eviction)
CacheBuilder提供两种定时回收的方法:
- expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
- expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
3, 基于引用的回收(Reference-based Eviction)
通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:
- CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用键的缓存用而不是equals比较键。
- CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用值的缓存用而不是equals比较值。
- CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。
其实这里使用最多的还是基于时间的定时回收, 其他的两种回收方式大家可以根据自己的项目而定.
缓存的显示刷新和清除:
(任何时候,你都可以显式地清除缓存项,而不是等到它被回收)
这里需要说明下刷新(refresh)和清除(invalidate)的区别:
刷新和回收不太一样。正如LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,
缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。
如果刷新过程抛出异常,缓存将保留旧值,而异常会在记录到日志后被丢弃 .
- 刷新: Cache.refresh(K k)
- 个别清除:Cache.invalidate(key)
- 批量清除:Cache.invalidateAll(keys)
- 清除所有缓存项:Cache.invalidateAll()
三: 使用实例
这里更新下我在项目中常用的guava cache的实例. 更新于2016年12月14日.
LoadingCache<String, Map<Long, CarAttentionDTO>> cache = CacheBuilder.newBuilder()
.expireAfterAccess(30, TimeUnit.MINUTES)
.build(new CacheLoader<String, Map<Long, CarAttentionDTO>>() {
public Map<Long, CarAttentionDTO> load(String key) { // no checked exception
LOGGER.info("loading car week attention data......");
long startTime = System.currentTimeMillis();
List<String> groupBy = Lists.newArrayList();
groupBy.add("key2");
Map<String, String> where = Maps.newHashMap();
where.put("group_name", String.valueOf(CommonConstants.CounterGroup.ATTENTION));
where.put("key1", String.valueOf(CommonConstants.DataType.CAR));
Calendar cal = Calendar.getInstance();
Date dateTo = DateUtils.addDays(cal.getTime(), -1);
Date dateFrom = DateUtils.addDays(cal.getTime(), -8);
int dayTo = Integer.valueOf(DateFormatUtils.format(dateTo, "yyyyMMdd"));
int dayFrom = Integer.valueOf(DateFormatUtils.format(dateFrom, "yyyyMMdd"));
List<CountDayUvEntity> list = uvEntityDao.countByParams(groupBy, where, dayFrom, dayTo);
int multiple = configReader.getInt(CommonConstants.SystemConfigKey.ATTENTION_MULTIPLE, 53);
Map<Long, CarAttentionDTO> tempMap = Maps.newHashMap();
for (CountDayUvEntity uvEntity : list) {
CarAttentionDTO attentionDTO = new CarAttentionDTO();
attentionDTO.setCarId(Long.valueOf(uvEntity.getKey2()));
attentionDTO.setAttention(uvEntity.getCount() * multiple + RandomUtils.nextInt(0, 10));
tempMap.put(attentionDTO.getCarId(), attentionDTO);
}
LOGGER.info("load car week attention finished. useTime=" + (System.currentTimeMillis() - startTime));
return tempMap;
}
});
private Cache<String, Object> carIndexCache = CacheBuilder.newBuilder().expireAfterAccess(20, TimeUnit.MINUTES).build();
public Map<Long, Long> getCarAttentions() throws ExecutionException {
String key = "getCarAttentions";
return (Map<Long, Long>) carIndexCache.get(key, new Callable<Map<Long, Long>>() {
@Override
public Map<Long, Long> call() throws Exception {
List<CarIndexEntity> carIndexs = carIndexEntityDao.findAll(
CarIndexEntity.Fields.type.eq(CommonConstants.CarIndexStatus.ATTENTION));
Map<Long, Long> data = Maps.newHashMapWithExpectedSize(carIndexs.size());
for (CarIndexEntity carIndex : carIndexs) {
data.put(carIndex.getCarId(), carIndex.getCount());
}
return data;
}
});
}
public Map<Long, Long> getCarSales() throws ExecutionException {
String key = "getCarSales";
return (Map<Long, Long>) carIndexCache.get(key, new Callable<Map<Long, Long>>() {
@Override
public Map<Long, Long> call() throws Exception {
List<CarIndexEntity> carIndexs = carIndexEntityDao.findAll(
CarIndexEntity.Fields.type.eq(CommonConstants.CarIndexStatus.SALES));
Map<Long, Long> data = Maps.newHashMapWithExpectedSize(carIndexs.size());
for (CarIndexEntity carIndex : carIndexs) {
data.put(carIndex.getCarId(), carIndex.getCount());
}
return data;
}
});
}
其实两种情况都是一样的, 第二个是使用场景是一个service有多个方法都需要用到guava cache.
好了 知道了这些就可以在项目中直接使用了, 更多的内容请看Guava Cache官方文档(翻译版):http://ifeve.com/google-guava-cachesexplained/
[Java 缓存] Java Cache之 Guava Cache的简单应用.的更多相关文章
- [Java 缓存] Java Cache之 DCache的简单应用.
前言 上次总结了下本地缓存Guava Cache的简单应用, 这次来继续说下项目中使用的DCache的简单使用. 这里分为几部分进行总结, 1)DCache介绍; 2)DCache配置及使用; 3)使 ...
- google guava cache缓存基本使用讲解
代码地址:https://github.com/vikde/demo-guava-cache 一.简介 guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存 ...
- 一个缓存使用案例:Spring Cache VS Caffeine 原生 API
最近在学习本地缓存发现,在 Spring 技术栈的开发中,既可以使用 Spring Cache 的注解形式操作缓存,也可用各种缓存方案的原生 API.那么是否 Spring 官方提供的就是最合适的方案 ...
- 自定义缓存管理器 或者 Spring -- cache
Spring Cache 缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 c ...
- Spring配置cache(concurrentHashMap,guava cache、redis实现)附源码
在应用程序中,数据一般是存在数据库中(磁盘介质),对于某些被频繁访问的数据,如果每次都访问数据库,不仅涉及到网络io,还受到数据库查询的影响:而目前通常会将频繁使用,并且不经常改变的数据放入缓存中,从 ...
- Guava Cache相关
官方:http://ifeve.com/google-guava-cachesexplained/ 理解:https://segmentfault.com/a/1190000007300118 项目中 ...
- Guava Cache 原理分析与最佳实践
前言 目前大部分互联网架构 Cache 已经成为了必可不少的一环.常用的方案有大家熟知的 NoSQL 数据库(Redis.Memcached),也有大量的进程内缓存比如 EhCache .Guava ...
- Java缓存
Java中要用到缓存的地方很多,首当其冲的就是持久层缓存,针对持久层谈一下: 要实现java缓存有很多种方式,最简单的无非就是static HashMap,这个显然是基于内存缓存,一个map就可以搞定 ...
- 浅谈java缓存
java中要用到缓存的地方很多,首当其冲的就是持久层缓存,针对持久层谈一下: 要实现java缓存有很多种方式,最简单的无非就是static HashMap,这个显然是基于内存缓存,一个map就可以搞定 ...
随机推荐
- ASP.NET Core HTTP 管道中的那些事儿
前言 马上2016年就要过去了,时间可是真快啊. 上次写完 Identity 系列之后,反响还不错,所以本来打算写一个 ASP.NET Core 中间件系列的,但是中间遇到了很多事情.首先是 NPOI ...
- C语言 · 奇偶判断
问题描述 能被2整除的数称为偶数,不能被2整除的数称为奇数.给一个整数x,判断x是奇数还是偶数. 输入格式 输入包括一个整数x,0<=x<=100000000. 输出格式 如果x是奇数,则 ...
- JavaScript 开发规范
本篇主要介绍JS的命名规范.注释规范以及框架开发的一些问题. 目录 1. 命名规范:介绍变量.函数.常量.构造函数.类的成员等等的命名规范 2. 注释规范:介绍单行注释.多行注释以及函数注释 3. 框 ...
- ASP.NET Core的路由[2]:路由系统的核心对象——Router
ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据以路由参数的形式解析出来供后续请求处理流 ...
- 谈谈一些有趣的CSS题目(二)-- 从条纹边框的实现谈盒子模型
开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉 ...
- 【Machine Learning】Python开发工具:Anaconda+Sublime
Python开发工具:Anaconda+Sublime 作者:白宁超 2016年12月23日21:24:51 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现 ...
- 基于SignalR的消息推送与二维码描登录实现
1 概要说明 使用微信扫描登录相信大家都不会陌生吧,二维码与手机结合产生了不同应用场景,基于二维码的应用更是比较广泛.为了满足ios.android客户端与web短信平台的结合,特开发了基于Singl ...
- linux中kvm的安装及快照管理
一.kvm的安装及状态查看 1.安装软件 yum -y install kvm virt-manager libvirt2.启动libvirtd 报错,升级device-mapper-libs yum ...
- 安装devtoolset
在运维的工作内,经常要编译安装各种开源组件,以CentOS 6的用户来说,大部分时候用到gcc的时候都是4.4.7版本的,在绝大多数情况下编译一些东西还是够用的,但还是有个别软件对gcc的版本是有要求 ...
- Linux下用netstat查看网络状态、端口状态(转)
转:http://blog.csdn.net/guodongdongnumber1/article/details/11383019 在linux一般使用netstat 来查看系统端口使用情况步. ...