前言

今天第一次使用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的简单应用.的更多相关文章

  1. [Java 缓存] Java Cache之 DCache的简单应用.

    前言 上次总结了下本地缓存Guava Cache的简单应用, 这次来继续说下项目中使用的DCache的简单使用. 这里分为几部分进行总结, 1)DCache介绍; 2)DCache配置及使用; 3)使 ...

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

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

  3. Guava Cache相关

    官方:http://ifeve.com/google-guava-cachesexplained/ 理解:https://segmentfault.com/a/1190000007300118 项目中 ...

  4. Java缓存

    Java中要用到缓存的地方很多,首当其冲的就是持久层缓存,针对持久层谈一下: 要实现java缓存有很多种方式,最简单的无非就是static HashMap,这个显然是基于内存缓存,一个map就可以搞定 ...

  5. Java缓存相关memcached、redis、guava、Spring Cache的使用

    随笔分类 - Java缓存相关 主要记录memcached.redis.guava.Spring Cache的使用 第十二章 redis-cluster搭建(redis-3.2.5) 摘要: redi ...

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

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

  7. 使用Guava cache构建本地缓存

    前言 最近在一个项目中需要用到本地缓存,在网上调研后,发现谷歌的Guva提供的cache模块非常的不错.简单易上手的api:灵活强大的功能,再加上谷歌这块金字招牌,让我毫不犹豫的选择了它.仅以此博客记 ...

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

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

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

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

随机推荐

  1. Javascript知识点记录(三)设计模式

    Javascript设计模式记录,这个方面确实是没写过,工作中也没有用到js设计模式的地方. prototype与面向对象取舍 使用prototype原型继承和使用面向对象,都可以实现闭包的效果.那么 ...

  2. 【BZOJ2874】训练士兵(主席树)

    题意:有一个N*M的矩阵,给出一些形如(x1,y1,x2,y2,s)的操作,代表(x1,y1)到(x2,y2)都被加上了s这个数 现在有一些强制在线的询问,询问(x1,y1)到(x2,y2)的和 对于 ...

  3. DB天气app冲刺第十天

    好了 这是第十天了,按照白板任务上的来说的话,今天没有完成,所以等一下还要继续看看今天能不能把他做完,今天出的问题在于我又自己调整了一下UI设计,因为发现以前的设计发面有重复,浪费了屏幕.所以还不如省 ...

  4. APT攻防对抗

    APT(高级持续性威胁)攻击是指近年来,专业甚至是有组织和国家背景支持的黑客,针对重要目标和系统发起的一种攻击手段,主要特征有 1)持续性:攻击者为了重要的目标长时间持续攻击直到攻破为止.攻击成功用上 ...

  5. 【POJ2352】【树状数组】Stars

    Description Astronomers often examine star maps where stars are represented by points on a plane and ...

  6. C语言两个libxml2库使用的问题

    最近使用libxml2想做点东西,翻看一些example后还是有些疑问,去segmentfault问了下,感谢@pingjiang的热心解答,问题解决,记录如下 (一)如下是一个XML文件,p为根结点 ...

  7. SQL中partition关键字的使用

    最近在写后台语句时候,运用到了partition这样一个关键字. 先大致说一下背景,有一种数据表,如下 现在需要取出,每一个人最近的一次打卡时间. 思路是,先把数据按照人名分组,然后在每个组里面按照时 ...

  8. 写了个批量查询qs的软件

    因为需要,自己写了个批量查询qs的小软件.从网站中抓出需要的数据,格式化显示: 对字符串进行检测处理,先用Replace函数去掉字符串的空格,再用正则表达式匹配,返回匹配的字符串,如果没有匹配,则返回 ...

  9. .NET MD5 加密

    using System; using System.Security.Cryptography; using System.Text; namespace Md5Demo { /// <sum ...

  10. iOS中 UITextView文本视图 技术分享

    UITextView: 文本视图相比与UITextField直观的区别就是UITextView可以输入多行文字并且可以滚动显示浏览全文. UITextField的用处多,UITextView的用法也不 ...