Guava的使用指南之Cache

创建cache

可以通过两种方式来创建cache:

cacheLoader

callable callback

通过这两种方法创建的cache,和通常用map来缓存的做法比,不同在于,这两种方法都实现了一种逻辑——从缓存中取key X的值,如果该值已经缓存过了,则返回缓存中的值,如果没有缓存过,可以通过某个方法来获取这个值。

但不同的在于cacheloader的定义比较宽泛,是针对整个cache定义的,可以认为是统一的根据key值load value的方法。

而callable的方式较为灵活,允许你在get的时候指定。

下面是两种方法的例子:

首先是基于cacheloader的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testCacheBuilder() throws ExecutionException {
  LoadingCache<String, String> graphs = CacheBuilder.newBuilder().maximumSize(1000)
            .build(new CacheLoader<String, String>() {
                public String load(String key) {
                    // 这里是key根据实际去取值的方法,例如根据这个key去数据库或者properties文件中取值
                    ApplicationContext context = new FileSystemXmlApplicationContext("E:/WorkDir/struts2practice/GuavaTest/WebRoot/WEB-INF/xml/springConfig.xml");
                    JdbcCustomerDAO aJdbcCustomerDAO = context.getBean(JdbcCustomerDAO.class);
                    System.out.println("load method has been invoked");
                    return aJdbcCustomerDAO.findValue(key);
                }
            });
    String resultVal = graphs.get("testKey");
    System.out.println("first time value is: " + resultVal);
    String resultVal1 = graphs.get("testKey");
    System.out.println("second time values is: " + resultVal1);
}

其次是基于实现callable的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
    public void testCallable() throws ExecutionException {
        // 没有使用CacheLoader
        Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();
      String resultVal = cache.get("testKey", new Callable<String>() {
            public String call() {
                // 这里先根据key实际去取值的方法,例如根据这个key去数据库或者properties文件中取值
                ApplicationContext context = new FileSystemXmlApplicationContext("E:/WorkDir/struts2practice/GuavaTest/WebRoot/WEB-INF/xml/springConfig.xml");
                JdbcCustomerDAO aJdbcCustomerDAO = context.getBean(JdbcCustomerDAO.class);
                System.out.println("resultVal call method is invoked");
                return aJdbcCustomerDAO.findValue("testKey");
            }
        });
        System.out.println("first time value is: " + resultVal);
        String resultVal1 = cache.get("testKey", new Callable<String>() {
            public String call() {
                // 这里先根据key实际去取值的方法,例如根据这个key去数据库或者properties文件中取值
                ApplicationContext context = new FileSystemXmlApplicationContext("E:/WorkDir/struts2practice/GuavaTest/WebRoot/WEB-INF/xml/springConfig.xml");
                JdbcCustomerDAO aJdbcCustomerDAO = context.getBean(JdbcCustomerDAO.class);
                System.out.println("resultVal1 call method is invoked");
                return aJdbcCustomerDAO.findValue("testKey");
            }
        });
        System.out.println("second time values is: " + resultVal1);
    }

缓存数据的移除

基于大小的移除

看字面意思就知道就是按照缓存的大小来移除,如果即将到达指定的大小,那就会把不常用的键值对从cache中移除。

定义的方式一般为 CacheBuilder.maximumSize(long),官方还介绍了一种可以算权重的方法,个人认为实际使用中不太用到,暂不讨论。

就这个常用的来看有几个注意点,

其一,这个size指的是cache中的条目数,不是内存大小或是其他;

其二,并不是完全到了指定的size系统才开始移除不常用的数据的,而是接近这个size的时候系统就会开始做移除的动作;

其三,如果一个键值对已经从缓存中被移除了,你再次请求访问的时候,如果cachebuild是使用cacheloader方式的,那依然还是会从cacheloader中再取一次值,如果这样还没有,就会抛出异常

基于时间的移除

guava提供了两个基于时间移除的方法

expireAfterAccess(long, TimeUnit) 这个方法是根据某个键值对最后一次访问之后多少时间后移除

expireAfterWrite(long, TimeUnit) 这个方法是根据某个键值对被创建或值被替换后多少时间移除

基于引用的移除

这种移除方式主要是基于java的垃圾回收机制,根据键或者值的引用关系决定移除,个人对垃圾回收这块不是非常了解,窃以为不太可靠。。也不常用。。所以没有研究,欢迎补充。

主动移除有三种方法:

单独移除用 Cache.invalidate(key)

批量移除用 Cache.invalidateAll(keys)

移除所有用 Cache.invalidateAll()

如果需要在移除数据的时候有所动作还可以定义Removal Listener,但是有点需要注意的是默认Removal Listener中的行为是和移除动作同步执行的,如果需要改成异步形式,可以考虑使用RemovalListeners.asynchronous(RemovalListener, Executor)

刷新操作

refresh 操作, 与evict不同, 是给key一个new value, 同时如果在refresh时有访问,那么将会返回old value, 而evict则会等待evict结束返回new value 定义refresh一般使用异步的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Some keys don't need refreshing, and we want refreshes to be done asynchronously.
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .refreshAfterWrite(1, TimeUnit.MINUTES)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) { // no checked exception
               return getGraphFromDatabase(key);
             }
             public ListenableFuture<Graph> reload(final Key key, Graph prevGraph) {
               if (neverNeedsRefresh(key)) {
                 return Futures.immediateFuture(prevGraph);
               } else {
                 // asynchronous!
                 return ListenableFutureTask.create(new Callable<Graph>() {
                   public Graph call() {
                     return getGraphFromDatabase(key);
                   }
                 });
               }
             }
           });

在refreshAfterWrite方法,会调用reload

统计

提供了一些数据采集的方法

  • CacheBuilder.recordStats() 方法启动了 cache的数据收集

  • Cache.stats() 返回了一个CacheStats对象, 提供一些数据方法

  • hitRate(), 请求点击率

  • averageLoadPenalty(), 加载new value,花费的时间, 单位nanosecondes

  • evictionCount(), 清除的个数