记一次Guava Cache 数据丢失问题排查

程序中访问大量静态数据,并且并发量很高,这个时候使用Guava Cache来做缓存管理,能大大提高服务效率,内存占用率问题。

当时当时在使用过程中发现有数据丢失的问题;程序初始化加载表中数据根据查询条件作为key值存放到Guava Cache中,但是线上实际使用一段时间后发现数据有丢失的情况,但是本地验证又正常;

Guava Cache 部分代码:

 Cache<String, Map<String, Map<String, Map<String, IvrNodeDetail>>>> poiCacheFlowNodeDetail = CacheBuilder.newBuilder().maximumSize(8000).build();

    public Map<String, Map<String, Map<String, IvrNodeDetail>>> getFlowNodeDetailCache(String key) {
        Map<String, Map<String, Map<String, IvrNodeDetail>>> map = new HashMap<>();
        try {
            map = poiCacheFlowNodeDetail.get(key, new Callable<Map<String, Map<String, Map<String, IvrNodeDetail>>>>() {
                @Override
                public Map<String, Map<String, Map<String, IvrNodeDetail>>> call() {
                    return getPoiCacheFlowNodeDetail(key);
                }
            });
        } catch (Exception e) {
            return null;
        }
        return Optional.fromNullable(map).or(Collections.EMPTY_MAP);
    }

其中 getPoiCacheFlowNodeDetail(key) 这个方法其实可以理解为只会返回空(不是null),不会返回其他结果(避免查库消耗时间);

本地测试正常,但是一到线上隔一段时间就会出现问题,有数据丢失的情况。当时发现这个问题的第一想法是Guava Cache的缓存机制帮我们帮数据移除了;

Guava Cache 中我们设置了maximumSize(8000),那么在你存入的数据量在逼近这个值的时候,Guava Cache会自动帮我们清理掉当前内存中使用频率底的这一部分数据来保证总的数据量不超过我们设置的最大值8000;但是这个时候就有问题了,我们缓存的数据是在初始化的时候加入的,也就5000条左右,但是总量是不变的,那为什么内存中的数据会超过8000条呢?于是我们动手测试一下,找一个内存中不存在的数据去查询,然后通过 poiCacheFlowNodeDetail.size() 方法在查询前和查询后打印当前缓存中的总数据量,通过对比我们发现,查询后总数据量增加了1;

这就很奇怪,为啥不存在的key查询完会增加1呢?我们查看Guava Cache的API,发现,当缓存中存在改key对应的值时,直接从缓存中取出来返回,当传入的key在缓存中查询不到的情况下,会走get方法:

 map = poiCacheFlowNodeDetail.get(key, new Callable<Map<String, Map<String, Map<String, IvrNodeDetail>>>>() {
                @Override
                public Map<String, Map<String, Map<String, IvrNodeDetail>>> call() {
                    return getPoiCacheFlowNodeDetail(key);
                }
            });

当从这方法中找到值后,会把这个值返回,并且!!!会把这个值加入缓存中,那么下次再传入这个值就直接从缓存中取;那么为什么我们这个会把传到的也加入进去呢?首先想到的就是返回为空,当返回值为空的时候(空字符串:""),Guava Cache 插件会认为这个空字符串就是我们要查询的结果,于是这个key和空串存入缓存中。那如何解决这个问题呢,很简单只需要我们就get方法的返回值为null就可以了;Guava Cache 中是可以识别null的;修改过后的代码:

 Cache<String, Map<String, Map<String, Map<String, IvrNodeDetail>>>> poiCacheFlowNodeDetail = CacheBuilder.newBuilder().maximumSize(8000).build();

    public Map<String, Map<String, Map<String, IvrNodeDetail>>> getFlowNodeDetailCache(String key) {
        Map<String, Map<String, Map<String, IvrNodeDetail>>> map = new HashMap<>();
        try {
            map = poiCacheFlowNodeDetail.get(key, new Callable<Map<String, Map<String, Map<String, IvrNodeDetail>>>>() {
                @Override
                public Map<String, Map<String, Map<String, IvrNodeDetail>>> call() {
                    return null;
                }
            });
        } catch (Exception e) {
            return null;
        }
        return Optional.fromNullable(map).or(Collections.EMPTY_MAP);
    }
发布了11 篇原创文章 · 获赞 29 · 访问量 6732

猜你喜欢

转载自blog.csdn.net/qq_22331931/article/details/91455317