本地缓存Google Guava

Cache与ConcurrentMap很相似,但也不完全一样。
最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。

Guava 开发和使用介绍

基本容量的回收 设置了初始值和最大容量上限,如果逼近容量上限,就会触发回收机制。

.maximumSize(200000)// 最大缓存数据量
.initialCapacity(100000)// 初始容量

定时回收

expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。

expireAfterWrite(long,TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。

如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

pom文件:

 
        
            com.google.guava
            guava
            20.0
        

demo代码展示

package com.yaoex.contract.common;

import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.RandomStringUtils;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

/**
 * @author xushuai
 * @date 2019年3月26日
 * @note 可以参考  https://www.jianshu.com/p/f4b99b70bd76
 */
public class GuavaCacheUtil {

    /*
     *
     * 相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache
     * 不回收元素,它也是很有用的,因为它会自动加载缓存。
     * 
     * 通常来说,Guava Cache适用于:
     * 
     * 你愿意消耗一些内存空间来提升速度。 
     * 你预料到某些键会被查询一次以上。 
     * 缓存中存放的数据总量不会超出内存容量。
     * (GuavaCache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
     * memcached是一套分布式的高速缓存系统
     * 如果你的场景符合上述的每一条,Guava Cache就适合你。
     * 注:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。
     *
     */

    // 新建了一个线程池,用来执行缓存刷新任务。并且重写了CacheLoader的reload方法,在该方法中建立缓存刷新的任务并提交到线程池。
    private static ListeningExecutorService backgroundRefreshPools = MoreExecutors
            .listeningDecorator(Executors.newFixedThreadPool(20));

    private static Cache localCache = CacheBuilder.newBuilder()
            .maximumSize(200000)// 最大缓存数据量
            .initialCapacity(100000)// 初始容量
            .recordStats()// 用来开启Guava Cache的统计功能
            .expireAfterWrite(5, TimeUnit.SECONDS)// 过期清除
            /*
             * 1.缓存值定时刷新:更新线程调用load方法更新该缓存,其他请求线程返回该缓存的旧值。这里的定时并不是真正意义上的定时。
             * 2.Guava
             * cache的刷新需要依靠用户请求线程,让该线程去进行load方法的调用,所以如果一直没有用户尝试获取该缓存值,则该缓存也并不会刷新
             * 3.这样对于某个key的缓存来说,只会有一个线程被阻塞,用来生成缓存值,而其他的线程都返回旧的缓存值,不会被阻塞。
             */
            .refreshAfterWrite(50, TimeUnit.SECONDS)// 每隔十s缓存值则会被刷新。防止缓存穿透
            .removalListener(new RemovalListener() { // 设置监听事件,就是在
                                                                        // 删除key的时候触发这个事件
                @Override
                public void onRemoval(
                        RemovalNotification notification) {
                    String email = notification.getValue();
                    String key = notification.getKey();
                    RemovalCause cause = notification.getCause();
                    System.out.println("结果====" + email);
                    System.out.println("key====" + key);
                    System.out.println("cause====" + cause);
                }
            }).build(new CacheLoader() {
                @Override
                public String load(String key) throws Exception {
                    return generateValueByKey(key);
                }
                /*
                 * Guava Cache异步刷新:
                 * 
                 * 如上的使用方法,解决了同一个key的缓存过期时会让多个线程阻塞的问题,只会让用来执行刷新缓存操作的一个用户线程会被阻塞。
                 * 由此可以想到另一个问题,当缓存的key很多时,高并发条件下大量线程同时获取不同key对应的缓存,此时依然会造成大量线程阻塞
                 * ,并且给数据库带来很大压力。
                 * 这个问题的解决办法就是将刷新缓存值的任务交给后台线程,所有的用户请求线程均返回旧的缓存值,这样就不会有用户线程被阻塞了。
                 * 详细做法如下:
                 */

                // 注意此时缓存的刷新依然需要靠用户线程来驱动,只不过和上面不同之处在于该用户线程触发刷新操作之后,会立马返回旧的缓存值。
                @Override
                public ListenableFuture reload(final String key,
                        String oldValue) throws Exception {
                    return backgroundRefreshPools
                            .submit(new Callable() {

                                @Override
                                public String call() throws Exception {
                                    return generateValueByKey(key);
                                }
                            });
                }

            });

    public static void main(String[] args) {
        add();
        asMap();
        
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        tongji();
        System.out.println("2>>>>>" + localCache.getIfPresent("username"));

        /*
         * 结果: 1>>>>>xushuai 2>>>>>null
         */

        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        get();

        try {
            Thread.sleep(15000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        tongji();
        del();
    }

    /**
     * 
     */
    private static void asMap() {
        ConcurrentMap currentMap = localCache.asMap();
        Set set = currentMap.keySet();
        for (String s : set) {
            System.out.println(localCache.getIfPresent(s));
        }
    }

    /**
     * 获取
     */
    private static void get() {
        for (int i = 0; i <= 100; i++) {
            if (i % 20 == 0) {
                System.out.println(
                        i + ">>>>>" + localCache.getIfPresent("username"));
            }
        }
    }

    /**
     * 清除
     */
    private static void del() {
        // 个别清除:Cache.invalidate(key) 批量清除:Cache.invalidateAll(keys)
        // 清除所有缓存项:Cache.invalidateAll()

        localCache.invalidate("username2");
    }

    /**
     * 
     */
    private static void add() {
        localCache.put("username", "xushuai");

        localCache.put("username2", "xushuai");
        System.out.println("1>>>>>" + localCache.getIfPresent("username"));
    }

    /**
     * Guava Cache统计
     */
    private static void tongji() {
        CacheStats stats = localCache.stats();
        // 缓存命中率;
        System.out.println("缓存命中率:" + stats.hitRate());
        // 缓存项被回收的总数,不包括显式清除。
        System.out.println("缓存项被回收的总数,不包括显式清除:" + stats.evictionCount());
    }

    public static String generateValueByKey(String key) {
        return key + ":缓存值定时刷新 防止有缓存穿透"
                + RandomStringUtils.randomAlphabetic(10);
    }
}


你可能感兴趣的:(本地缓存Google Guava)