前言

今天第一次使用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缓存

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

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

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

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

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

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

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

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

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

随机推荐

  1. CRL 版本2.2.0.0发布

    重要更新: 增加了关联查询 优化了缓存查找效率 关联查询有以下两种形式 返回Select结果,结果为动态对象 将结果附加给当前对象索引值 关联查询有累加效果,可关联多个表可通过匿名对象指定返回的别名, ...

  2. 实验楼 linux 学习

    实验楼 linux 学习     一.Linux 用户管理 1.查看用户 who am i // who mom likes whoami   ====--------====== 输入的第一列表示打 ...

  3. java中变量命名和引用变量的一个坑

    这次有两个主题,第一个太简单啦,就是java中变量的命名规则,纯记忆性东西.第二个主题,就是讨论一下对象引用变量的一个注意点.

  4. [py]导入模块3种方法

        import os <--通过os.system()引用 from os import * <---直接system()引用,不建议使用 from os import argv i ...

  5. jQuery Ajax应用

    jQuery Ajax应用 本章主要了解jQuery的Ajax与传统的Ajax的区别,掌握JQuery的Ajax常用的方法与Ajax相关的函数. 详细内容,请点击jQuery Ajax应用查看:

  6. vxworks

    VxWorks 是美国 Wind River System 公司( 以下简称风河公司 ,即 WRS 公司)推出的一个实时操作系统.Tornado 是WRS 公司推出的一套实时操作系统开发环境,类似Mi ...

  7. apache 不执行PHP,显示代码

    首先检查是否安装PHP,已经安装过的话,先执行 locate libphp5.so 查看APACHE是否有SO文件,如果没有,那就要重装PHP了,先执行php -i | grep configure ...

  8. Spring Boot 2.x基础教程:快速入门

    简介 在您第1次接触和学习Spring框架的时候,是否因为其繁杂的配置而退却了?在你第n次使用Spring框架的时候,是否觉得一堆反复黏贴的配置有一些厌烦?那么您就不妨来试试使用Spring Boot ...

  9. python 多线程threading的学习一

    1. import threading #引入线程模块 2 申明实例 t = threading.Thread(target  = fun, args = (,)) 说明:参数target 是要运行的 ...

  10. css摘要

    由于需要,今天花三个小时了解一下css,在此记录一些摘要: 参考w3school 1. 当同一个 HTML 元素被不止一个样式定义时,会使用哪个样式呢? 一般而言,所有的样式会根据下面的规则层叠于一个 ...