Google Guava 在实际场景中的应用封装

毕竟西湖六月中,风光不与四时同。java

接天莲叶无穷碧,映日荷花别样红。git

晓出净慈寺送林子方-杨万里redis

周末与小伙伴约了一波西湖,这个时间荷花开的正好...,在开始文章以前先放一张“佛系”美图来镇楼!!!数据库

最近这段时间用了下谷歌的guava,本身封了一个缓存模板方案,特此记录,以备后续所需。缓存

一个缓存定时清除任务带来的GC问题

为何要从这个来讲起,由于不说这个就没guava什么事了!数据结构

最近项目中须要使用缓存来对一查查询频繁的数据作缓存处理;首先咱们也不但愿引入三方的如redis或者memcache这样的服务进来,其次是咱们对于数据一致性的要求并非很高,不须要集群内的查询接口共享到一份缓存数据;因此这样一来咱们只要实现一个基于内存的缓存便可。app

最开始我并无考虑使用guava来作这个事情,而是本身写了一套基于CurrentHashMap的缓存方案;这里须要明确一点,由于缓存在这个场景里面但愿提供超时清除的能力,而基于因此在本身缓存框架中增长了定时清除过时数据的能力。框架

这里我就直接把定时清楚的这段代码放上来:ide

/** * 静态内部类来进行超时处理 */
private class ClearCacheThread extends Thread {
    @Override
    public void run() {
        while (true){
            try {
                long now = System.currentTimeMillis();
                Object[] keys = map.keySet().toArray();
                for (Object key : keys) {
                    CacheEntry entry = map.get(key);
                    if (now - entry.time >= cacheTimeout) {
                        synchronized (map) {
                            map.remove(key);
                            if (LOGGER.isDebugEnabled()){
                                LOGGER.debug("language cache timeout clear");
                            }
                        }
                    }
                }
            }catch (Exception e){
                LOGGER.error("clear out time cache value error;",e);
            }
        }
    }
}
复制代码

这个线程是用来单独处理过时数据的。缓存初始化时就会触发这个线程的start方法开始执行。函数

正式因为这段代码的不合理致使我在发布dev环境以后,机器GC触发的频次高的离谱。在尝试了不一样的修复方案以后,最后选择放弃了;改用guava了!

小伙伴们能够在下面留言来讨论下这里为何会存在频繁GC的问题;我会把结论放在评论回复里面。

guava

为何选用guava呢,很显然,是大佬推荐的!!!

guava是谷歌提供的一个基于内存的缓存工具包,Guava Cache 提供了一种把数据(key-value对)缓存到本地(JVM)内存中的机制,适用于不多会改动的数据。Guava Cache 与 ConcurrentMap 很类似,但也不彻底同样。最基本的区别是 ConcurrentMap 会一直保存全部添加的元素,直到显式地移除。相对地,Guava Cache 为了限制内存占用,一般都设定为自动回收元素。

对于咱们的场景,guava 提供的能力知足了咱们的须要:

  • 数据改动小
  • 基于内存
  • 能够自动回收

既然选择它了,咱们仍是有必要来先对它有个大体的了解;先来看看它提供的一些类和接口:

接口/类 详细解释
Cache 【I】;定义get、put、invalidate等操做,这里只有缓存增删改的操做,没有数据加载的操做。
AbstractCache 【C】;实现Cache接口。其中批量操做都是循环执行单次行为,而单次行为都没有具体定义。
LoadingCache 【I】;继承自Cache。定义get、getUnchecked、getAll等操做,这些操做都会从数据源load数据。
AbstractLoadingCache 【C】;继承自AbstractCache,实现LoadingCache接口。
LocalCache 【C】;整个guava cache的核心类,包含了guava cache的数据结构以及基本的缓存的操做方法。
LocalManualCache 【C】;LocalCache内部静态类,实现Cache接口。其内部的增删改缓存操做所有调用成员变量localCache(LocalCache类型)的相应方法。
LocalLoadingCache 【C】;LocalCache内部静态类,继承自LocalManualCache类,实现LoadingCache接口。其全部操做也是调用成员变量localCache(LocalCache类型)的相应方法
CacheBuilder 【C】;缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。CacheBuilder在build方法中,会把前面设置的参数,所有传递给LocalCache,它本身实际不参与任何计算
CacheLoader 【C】;用于从数据源加载数据,定义load、reload、loadAll等操做。

整个来看的话,guava里面最核心的应该算是 LocalCache 这个类了。

@GwtCompatible(emulated = true)
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> 复制代码

关于这个类的源码这里就不细说了,直接来看下在实际应用中个人封装思路【封装知足我当前的需求,若是有小伙伴须要借鉴,能够本身在作扩展】

private static final int            MAX_SIZE     = 1000;
private static final int            EXPIRE_TIME  = 10;
private static final int            DEFAULT_SIZE = 100;

private int                         maxSize      = MAX_SIZE;
private int                         expireTime   = EXPIRE_TIME;
/** 时间单位(分钟) */
private TimeUnit                    timeUnit     = TimeUnit.MINUTES;
/** Cache初始化或被重置的时间 */
private Date                        resetTime;

/** 分别记录历史最多缓存个数及时间点*/
private long                        highestSize  = 0;
private Date                        highestTime;

private volatile LoadingCache<K, V> cache;
复制代码

这里先是定义了一些常量和基本的属性信息,固然这些属性会提供set&get方法,供实际使用时去自行设置。

public LoadingCache<K, V> getCache() {
    //使用双重校验锁保证只有一个cache实例
    if(cache == null){
        synchronized (this) {
            if(cache == null){
                //CacheBuilder的构造函数是私有的,只能经过其静态方法newBuilder()来得到CacheBuilder的实例
                cache = CacheBuilder.newBuilder()
                        //设置缓存容器的初始容量为100
                        .initialCapacity(DEFAULT_SIZE)
                        //缓存数据的最大条目
                        .maximumSize(maxSize)
                        //定时回收:缓存项在给定时间内没有被写访问(建立或覆盖),则回收。
                        .expireAfterWrite(expireTime, timeUnit)
                        //启用统计->统计缓存的命中率等
                        .recordStats()
                        //设置缓存的移除通知
                        .removalListener((notification)-> {
                            if (LOGGER.isDebugEnabled()){
                                LOGGER.debug("{} was removed, cause is {}" ,notification.getKey(), notification.getCause());
                            }
                        })
                        .build(new CacheLoader<K, V>() {
                            @Override
                            public V load(K key) throws Exception {
                                return fetchData(key);
                            }
                        });
                this.resetTime = new Date();
                this.highestTime = new Date();
                if (LOGGER.isInfoEnabled()){
                    LOGGER.info("本地缓存{}初始化成功.", this.getClass().getSimpleName());
                }
            }
        }
    }

    return cache;
}
复制代码

上面这段代码是整个缓存的核心,经过这段代码来生成咱们的缓存对象【使用了单例模式】。具体的属性参数看注释。

由于上面的那些都是封装在一个抽象类AbstractGuavaCache里面的,因此我又封装了一个CacheManger用来管理缓存,并对外提供具体的功能接口;在CacheManger中,我使用了一个静态内部类来建立当前默认的缓存。

/** * 使用静态内部类实现一个默认的缓存,委托给manager来管理 * * DefaultGuavaCache 使用一个简单的单例模式 * @param <String> * @param <Object> */
private static class DefaultGuavaCache<String, Object> extends AbstractGuavaCache<String, Object> {

    private static AbstractGuavaCache cache = new DefaultGuavaCache();

    /** * 处理自动载入缓存,按实际状况载入 * 这里 * @param key * @return */
    @Override
    protected Object fetchData(String key) {
        return null;
    }

    public static AbstractGuavaCache getInstance() {
        return DefaultGuavaCache.cache;
    }

}
复制代码

大概思路就是这样,若是须要扩展,咱们只须要按照实际的需求去扩展AbstractGuavaCache这个抽象类就能够了。具体的代码贴在下面了。

完整的两个类

AbstractGuavaCache

public abstract class AbstractGuavaCache<K, V> {

    protected final Logger              LOGGER       = LoggerFactory.getLogger(AbstractGuavaCache.class);

    private static final int            MAX_SIZE     = 1000;
    private static final int            EXPIRE_TIME  = 10;
    /** 用于初始化cache的参数及其缺省值 */
    private static final int            DEFAULT_SIZE = 100;

    private int                         maxSize      = MAX_SIZE;

    private int                         expireTime   = EXPIRE_TIME;
    /** 时间单位(分钟) */
    private TimeUnit                    timeUnit     = TimeUnit.MINUTES;
    /** Cache初始化或被重置的时间 */
    private Date                        resetTime;

    /** 分别记录历史最多缓存个数及时间点*/
    private long                        highestSize  = 0;
    private Date                        highestTime;

    private volatile LoadingCache<K, V> cache;

    public LoadingCache<K, V> getCache() {
        //使用双重校验锁保证只有一个cache实例
        if(cache == null){
            synchronized (this) {
                if(cache == null){
                    //CacheBuilder的构造函数是私有的,只能经过其静态方法ne
                    //wBuilder()来得到CacheBuilder的实例
                    cache = CacheBuilder.newBuilder()
                            //设置缓存容器的初始容量为100
                            .initialCapacity(DEFAULT_SIZE)
                            //缓存数据的最大条目
                            .maximumSize(maxSize)
                            //定时回收:缓存项在给定时间内没有被写访问
                            //(建立或覆盖),则回收。
                            .expireAfterWrite(expireTime, timeUnit)
                            //启用统计->统计缓存的命中率等
                            .recordStats()
                            //设置缓存的移除通知
                            .removalListener((notification)-> {
                                if (LOGGER.isDebugEnabled()){
                                   //...
                                }
                            })
                            .build(new CacheLoader<K, V>() {
                                @Override
                                public V load(K key) throws Exception {
                                    return fetchData(key);
                                }
                            });
                    this.resetTime = new Date();
                    this.highestTime = new Date();
                    if (LOGGER.isInfoEnabled()){
                         //...
                    }
                }
            }
        }

        return cache;
    }

    /** * 根据key从数据库或其余数据源中获取一个value,并被自动保存到缓存中。 * * 改方法是模板方法,子类须要实现 * * @param key * @return value,连同key一块儿被加载到缓存中的。 */
    protected abstract V fetchData(K key);

    /** * 从缓存中获取数据(第一次自动调用fetchData从外部获取数据),并处理异常 * @param key * @return Value * @throws ExecutionException */
    protected V getValue(K key) throws ExecutionException {
        V result = getCache().get(key);
        if (getCache().size() > highestSize) {
            highestSize = getCache().size();
            highestTime = new Date();
        }
        return result;
    }

    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public int getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(int expireTime) {
        this.expireTime = expireTime;
    }

    public TimeUnit getTimeUnit() {
        return timeUnit;
    }

    public void setTimeUnit(TimeUnit timeUnit) {
        this.timeUnit = timeUnit;
    }

    public Date getResetTime() {
        return resetTime;
    }

    public void setResetTime(Date resetTime) {
        this.resetTime = resetTime;
    }

    public long getHighestSize() {
        return highestSize;
    }

    public void setHighestSize(long highestSize) {
        this.highestSize = highestSize;
    }

    public Date getHighestTime() {
        return highestTime;
    }

    public void setHighestTime(Date highestTime) {
        this.highestTime = highestTime;
    }
}
复制代码

DefaultGuavaCacheManager

public class DefaultGuavaCacheManager {

    private static final Logger  LOGGER =
    LoggerFactory.getLogger(DefaultGuavaCacheManager.class);
   //缓存包装类
    private static AbstractGuavaCache<String, Object> cacheWrapper;

    /** * 初始化缓存容器 */
    public static boolean initGuavaCache() {
        try {
            cacheWrapper = DefaultGuavaCache.getInstance();
            if (cacheWrapper != null) {
                return true;
            }
        } catch (Exception e) {
            LOGGER.error("Failed to init Guava cache;", e);
        }
        return false;
    }

    public static void put(String key, Object value) {
        cacheWrapper.getCache().put(key, value);
    }

    /** * 指定缓存时效 * @param key */
    public static void invalidate(String key) {
        cacheWrapper.getCache().invalidate(key);
    }

    /** * 批量清除 * @param keys */
    public static void invalidateAll(Iterable<?> keys) {
        cacheWrapper.getCache().invalidateAll(keys);
    }

    /** * 清除全部缓存项 : 慎用 */
    public static void invalidateAll() {
        cacheWrapper.getCache().invalidateAll();
    }

    public static Object get(String key) {
        try {
            return cacheWrapper.getCache().get(key);
        } catch (Exception e) {
            LOGGER.error("Failed to get value from guava cache;", e);
        }
        return null;
    }

    /** * 使用静态内部类实现一个默认的缓存,委托给manager来管理 * * DefaultGuavaCache 使用一个简单的单例模式 * @param <String> * @param <Object> */
    private static class DefaultGuavaCache<String, Object> extends AbstractGuavaCache<String, Object> {

        private static AbstractGuavaCache cache = new DefaultGuavaCache();

        /** * 处理自动载入缓存,按实际状况载入 * @param key * @return */
        @Override
        protected Object fetchData(String key) {
            return null;
        }

        public static AbstractGuavaCache getInstance() {
            return DefaultGuavaCache.cache;
        }

    }

}
复制代码

参考

Google Guava官方教程(中文版)

相关文章
相关标签/搜索