guava cache大量的WARN日志的问题分析

1、问题显现

2019-04-21 11:16:32 [http-nio-4081-exec-2] WARN  com.google.common.cache.LocalCache - Exception thrown during refresh
com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key BKCIYear0.
	at com.google.common.cache.LocalCache$Segment.getAndRecordStats(LocalCache.java:2350)
	at com.google.common.cache.LocalCache$Segment$1.run(LocalCache.java:2331)
	at com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:457)
	at com.google.common.util.concurrent.ExecutionList.executeListener(ExecutionList.java:156)
	at com.google.common.util.concurrent.ExecutionList.add(ExecutionList.java:101)
	at com.google.common.util.concurrent.AbstractFuture.addListener(AbstractFuture.java:170)
	at com.google.common.cache.LocalCache$Segment.loadAsync(LocalCache.java:2326)
	at com.google.common.cache.LocalCache$Segment.refresh(LocalCache.java:2389)
	at com.google.common.cache.LocalCache$Segment.scheduleRefresh(LocalCache.java:2367)
	at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2187)
	at com.google.common.cache.LocalCache.get(LocalCache.java:3937)
	at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3941)
	at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4824)
	at com.kcidea.sushibase.Service.Cache.GoogleLocalCache.getCacheByName(GoogleLocalCache.java:42)  

google的这个开发工具里面的缓存是个轻量化的缓存,相似一个HashMap的实现,google在里面加了不少同步异步的操做。使用起来简单,不用额外搭建redis服务,故项目中使用了这个缓存。java

有一天生产环境直接假死了,赶忙上服务器排查,发现日志里面有大量的报WARN错误,只要触发cache的get就会报警告,因为cache的触发频率超高,致使了日志磁盘爆满,一天好几个G的日志里面全是WARN的错误。可是在开发环境下根本不触发这个错误,怎么调试都没有进这段代码里面。先暂时停用了缓存,而后开始排查。redis

 

  2、问题排查

1. 根据报错的堆栈,一点一点往上找,直到找到这一行的时候发现了一些端倪,他想找一个newValue缓存

at com.google.common.cache.LocalCache$Segment.refresh(LocalCache.java:2389)服务器

2. 继续顺着这条线往里面找,直到找到这段代码,为何要找newValue呢,map须要刷新了,过时了,或者主动触发刷新值了。异步

  if (map.refreshes()
          && (now - entry.getWriteTime() > map.refreshNanos)
          && !entry.getValueReference().isLoading()) {
        V newValue = refresh(key, hash, loader, true);
        if (newValue != null) {
          return newValue;
        }
      }

 3. 而后就能够解释问题为何只在生产环境出现,而开发环境不出现了,由于是触发了过时时间,咱们设置的过时时间是30分钟,因此开发环境不多调试超过30分钟的,每次都是从新运行,因此根本触发不到这个超时的地方。ide

4. 而后接着调试,发现会走到咱们一开始初始化cache的代码那边工具

    /**
     * 缓存队列变量
     */
    static LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
            // 给定时间内没有被读/写访问,则回收。
            .refreshAfterWrite(CACHE_OUT_TIME, TimeUnit.MINUTES)
            // 缓存过时时间和redis缓存时长同样
            .expireAfterAccess(CACHE_OUT_TIME, TimeUnit.MINUTES)
            // 设置缓存个数
            .maximumSize(50000).
                    build(new CacheLoader<String, Object>() {
                        @Override
                        public Object load(String key) throws Exception {
                            //找不到就返回null (1)
                            return null;
                        }
                    });

 注意上面的代码,(1)的位置,找不到就返回null,在网上找的代码里面这里一般写的是return null或者return doThingsTheHardWay(key)之类的,可是没有详细的doThingsTheHardWay描述,因此我这里写了个null。开发工具

因此根本的问题就是这里返回null致使的错误了。ui

 

3、解决方案

找到了问题缘由,解决方案就相对来讲容易的不少了google

1. 修改(1)处的代码,将return null修改为return new NullObject()  

    static LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
            // 给定时间内没有被读/写访问,则回收。
            .refreshAfterWrite(CACHE_OUT_TIME, TimeUnit.MINUTES)
            // 缓存过时时间和redis缓存时长同样
            .expireAfterAccess(CACHE_OUT_TIME, TimeUnit.MINUTES)
            // 设置缓存个数
            .maximumSize(50000).
                    build(new CacheLoader<String, Object>() {

                        @Override
                        public Object load(String key) throws Exception {
                            //尝试将这里改为new NullObject,外面进行判断
                            return new NullObject();
                        }
                    });

  

2. 定义一个空白的类就叫NullObject

/**
 * ClassName   NullObject
 * Author      shenjing
 * Date        2019/7/10
 * Version     1.0
 **/
public class NullObject {
}

  

3. 在通用的getCacheByName的方法中进行判断,取到的对象是否是NullObject类型的,若是是,则返回null给外层,进行从新加载。

  private static <T> T getCacheByName(String name) {
        T ret = null;
        try {
            if (cache.asMap().containsKey(name)) {
                ret = (T) cache.get(name);
                if (ret.getClass().equals(NullObject.class)) {
                    //缓存已过时,返回null
                    return null;
                }
                log.debug("缓存读取[{}]成功", name);
            }
        } catch (Exception ex) {
            log.debug("缓存[{}]读取失败:{}", name, ex.getMessage());
        }

        return ret;
    }
相关文章
相关标签/搜索