Guava Cache

当项目中须要使用local cache的时候,通常都会经过基于 ConcurrentHashMap或者LinkedHashMap来实现本身的LRU Cache。在造轮子过程当中,通常都须要解决一下问题:
1. 内存是有限了,因此须要限定缓存的最大容量.
2. 如何清除“太旧”的缓存entry.
3. 如何应对并发读写.
4.缓存数据透明化:命中率、失效率等.
cache的优劣基本取决于如何优雅高效地解决上面这些问题。Guava cache很好地解决了这些问题,是一个很是好的本地缓存,线程安全,功能齐全,简单易用,性能好。总体上来讲Guava cache 是本地缓存的不二之选。
下面是一个简单地例子:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
                .maximumSize(1000).expireAfterWrite(10, TimeUnit.MINUTES)
                .removalListener(MY_LISTENER)                .build(new CacheLoader<Key, Graph>() {
                    public Graph load(Key key) throws AnyException {
                        return createExpensiveGraph(key);                    }                });复制代码
接下来,咱们从多个角度来介绍如何使用Guava cache。
1、建立cache:
通常来讲,在工做中咱们通常这样使用remote cache或者local cache:
User user = cache.get(usernick); if(user == null){ user = userDao.getUser(usernick); cache.put(usernick, user); } return user;即if cached, return; otherwise create/load/compute, cache and return。
而Guava cache 经过下面两种方式以一种更优雅的方式实现了这个逻辑:
1. From A CacheLoader
2. From A Callable
经过这两种方法建立的cache,和普通的用map缓存相比,不一样在于,都实现了上面提到的——“if cached, return; otherwise create/load/compute, cache and return”。但不一样的在于cacheloader的定义比较宽泛,是针对整个cache定义的,能够认为是统一的根据key值load value的方法。而callable的方式较为灵活,容许你在get的时候指定。举两个栗子来介绍如何使用这两种方式
From CacheLoader:

LoadingCache<String, String> graphs = CacheBuilder.newBuilder().maximumSize(1000)
                .build(new CacheLoader<String, String>() {
                    public String load(String key) {
                        // 这里是key根据实际去取值的方法,例如根据这个key去数据库或者经过复杂耗时的计算得出
                        System.out.println("no cache,load from db");                        return "123";                    }                });        String val1 = graphs.get("key");        System.out.println("1 value is: " + val1);        String val2 = graphs.get("key");        System.out.println("2 value is: " + val2);From Callable:               Cache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(1000).build();        String val1 = cache.get("key", new Callable<String>() {
            public String call() {
                // 这里是key根据实际去取值的方法,例如根据这个key去数据库或者经过复杂耗时的计算得出
                System.out.println("val call method is invoked");                return "123";            }        });        System.out.println("1 value is: " + val1);        String val2 = cache.get("testKey", new Callable<String>() {
            public String call() {
                // 这里是key根据实际去取值的方法,例如根据这个key去数据库或者经过复杂耗时的计算得出
                System.out.println("val call method is invoked");                return "123";            }        });        System.out.println("2 value is: " + val2);复制代码
须要注意的是,全部的Guava caches,不管是否是loader模式,都支持get(Key,Callable<V>)方法。
另外,除了上述这两种方式来更新缓存外,Guava cache固然也支持Inserted Directly:Values也能够经过cache.put(key,value)直接将值插入到cache中。该方法将覆盖key对应的entry。
2、缓存移除
内存是有限,因此不能把全部的东西都加载到内存中,过大的local cache对任何java应用来讲都是噩梦。所以local cache必须提供不一样的机制来清除“没必要要”的缓存entry,平衡内存使用率和命中率。Guava Cache提供了3中缓存清除策略:size-based eviction, time-based eviction, and reference-based eviction.
size-based eviction:基于cache容量的移除。若是你的cache不容许扩容,即不容许超过设定的最大值,那么使用CacheBuilder.maxmuSize(long)便可。在这种条件下,cache会本身释放掉那些最近没有或者不常用的entries内存。这里须要注意一下两点:
1.并非在超过限定时才会删除掉那些entries,而是在即将达到这个限定值时,那么你就要当心考虑这种状况了,由于很明显即便没有达到这个限定值,cache仍然会进行删除操做。
2.若是一个key-entry已经被移除了,当你再次调用get(key)时,若是CacheBuilder采用的是CacheLoader模式,那依然会从cacheLoader中加载一次。
此外,若是你的cache里面的entries有着大相径庭的内存占用若是你的cache values有着大相径庭的内存占用,你能够经过CacheBuilder.weigher(Weigher)来为不一样的entry设定weigh,而后使用CacheBuilder.maximumWeight(long)设定一个最大值。在tpn会经过local cache缓存用户对消息类目的订阅信息,有的用户订阅的消息类目比较多,所占的内存就比较多,有的用户订阅的消息类目比较少,天然占用的内存就比较少。那么我就能够经过下面的方法来根据用户订阅的消息类目数量设置不一样的weight,这样就能够在不更改cache大小的状况下,使得缓存尽可能覆盖更多地用户:

LoadingCache<Key, User> Users= CacheBuilder.newBuilder()
       .maximumWeight(100000)       .weigher(new Weigher<Key, User>() {
          public int weigh(Key k, User u) {
               if(u.categories().szie() >5){
                    return 2;               }else{                    return 1;               }          }        })       .build(           new CacheLoader<Key, User>() {
             public Userload(Key key) { // no checked exception
               return createExpensiveUser(key);             }           });复制代码
PS:这个例子可能不是很恰当,当时足以说明weight的用法。
time-based eviction:基于时间的移除。Guava cache提供了两种方法来实现这个逻辑:
1. expireAfterAccess(long, TimeUnit)
从最后一次访问(读或者写)开始计时,过了这段指定的时间就会释放掉该entries。注意:那些被删掉的entries的顺序时和size-based eviction是十分类似的。
2. expireAfterWrite(long,TimeUnit)
从entries被建立或者最后一次被修改值的点来计时的,若是从这个点开始超过了那段指定的时间,entries就会被删除掉。这点设计的很精明,由于数据会随着时间变得愈来愈陈旧。
若是想要测试Timed Eviction,使用Ticker interface和CacheBuilder.ticker(Ticker)方法对你的cache设定一个时间便可,那么你就不须要去等待系统时间了。
reference-based eviction:基于引用的移除。Guava为你准备了entries的垃圾回收器,对于keys或者values可使用weak reference ,对于values可使用soft reference.
1. CacheBuilder.weakKeys(): 经过weak reference存储keys。在这种状况下,若是keys没有被strong或者soft引用,那么entries会被垃圾回收。
2. CacheBuilder.weakValues() : 经过weak referene 存储values.在这种状况下,若是valves没有被strong或者soft引用,那么entries会被垃圾回收。
须要注意的是:这种条件下的垃圾回收器是创建在引用之上的,那么这会形成整个cache是使用==来比较俩个key的,而不是equals();
除了上面这三种方式来移除cache的enties外,还能够经过如下3个方法来主动释放一些enties:
1. 单独移除用: Cache.invalidate(key)
2. 批量移除用 :Cache.invalidateAll(keys)
3. 移除全部用 :Cache.invalidateAll()
若是须要在移除数据的时候有所动做还能够定义Removal Listener,可是有点须要注意的是默认Removal Listener中的行为是和移除动做同步执行的,若是须要改为异步形式,能够考虑使用RemovalListeners.asynchronous(RemovalListener, Executor)。
最后咱们来看一下Guava Cache是何时执行清理动做的。经过CacheBuilder建立的cache既不会自动执行清理和移除entry,也不会在entry过时后立马执行清除操做。相反,其在执行写操做或者读操做的时候(在写操做很是少的状况下)来经过少许的操做来执行清理工做。这样作的缘由是:若是咱们要不断进行缓存的清理和移除,咱们须要建立一个线程,其业务将与用户的操做来争夺共享锁。此外,某些环境限制清理线程的建立,这将使CacheBuilder没法使用在该环境中。 所以,Guava cache将什么时候清理的选择权交给用户。若是你的缓存是面向高吞吐量应用的,那么你没必要担忧执行缓存维护,清理过时的entries等。若是你的缓存是面向读多写少的应用,为了不影响缓存读取,那么就能够建立本身的维护线程每隔一段时间就调用Cache.cleanUp(),如能够用ScheduledExecutorService安排维修。
若是你想安排按期维护缓存的缓存中不多有写,只是用ScheduledExecutorService安排维修。
3、统计功能:
统计功能是Guava cache一个很是实用的特性。能够经过CacheBuilder.recordStats() 方法启动了 cache的数据收集:
1. Cache.stats(): 返回了一个CacheStats对象, 提供一些数据方法
2. hitRate(): 请求点击率
3. averageLoadPenalty(): 加载new value,花费的时间, 单位nanosecondes
4. evictionCount(): 清除的个数 
相关文章
相关标签/搜索