Guava缓存list集合进行滤操作导致多次获取相同key返回数据不一致问题

背景

项目中很多地方使用了Guava Cache,用于加速读取频繁访问的热点数据。
最近项目组中遇到一个"诡异"的问题,多次获取Cache中相同key的数据,返回值不同。

分析

通过查看日志和排查代码,发现有多个地方获取缓存,有的地方获取缓存数据还进行了过滤处理
正是这些处理改变缓存值,导致其它地方获取数据发生变更,跟预期不一致。

模拟

LoadingCache<String, List<Integer>> cache = CacheBuilder.newBuilder()
    .maximumSize(1)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .build(new CacheLoader<String, List<Integer>>() {
        @Override
        public List<Integer> load(String s) throws Exception {
            return Lists.newArrayList(1, 2, 3);
        }
    });

首先定义一个LoadingCache,key是String类型,返回List<Integer>类型;
通过maximumSize(1)限制缓存中只有1个值;
返回模拟的列表[1,2,3]。

// test case1
List<Integer> list1 = cache.get("all");
System.out.println(list1);
list1.removeIf(o -> o.equals(1));
System.out.println(list1);
List<Integer> list2 = cache.get("all");
System.out.println(list2);

输出结果为:

[1, 2, 3]
[2, 3]
[2, 3]

第1次获取缓存后通过removeIf去掉了元素1,再次获取缓存发现结果集跟着改变了。

// test case2
list1 = cache.get("all");
System.out.println(list1);
list1 = Lists.newArrayList(1);
System.out.println(list1);
list2 = cache.get("all");
System.out.println(list2);

输出结果为:

[1, 2, 3]
[1]
[1, 2, 3]

第1次获取缓存后通过=号赋值修改list1变量引用地址,再次获取缓存结果集不变。

// test case3
System.out.println(StringUtils.center("case3", 50, "-"));
list1 = cache.get("all");
System.out.println(list1);
list1 = list1.stream().filter(o -> !o.equals(1)).collect(Collectors.toList());
System.out.println(list1);
list2 = cache.get("all");
System.out.println(list2);

输出结果为:

[1, 2, 3]
[2, 3]
[1, 2, 3]

第1次获取缓存后,通过stream流filter方式过滤,并生成新的list,再次获取缓存结果集不变。

完整代码如下:

/**
 * @author cdfive
 */
public class CacheTest2 {

    public static void main(String[] args) throws Exception {
        LoadingCache<String, List<Integer>> cache = CacheBuilder.newBuilder()
                .maximumSize(1)
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .build(new CacheLoader<String, List<Integer>>() {
                    @Override
                    public List<Integer> load(String s) throws Exception {
                        return Lists.newArrayList(1, 2, 3);
                    }
                });

        // test case1
        System.out.println(StringUtils.center("case1", 50, "-"));
        List<Integer> list1 = cache.get("all");
        System.out.println(list1);
        list1.removeIf(o -> o.equals(1));
        System.out.println(list1);
        List<Integer> list2 = cache.get("all");
        System.out.println(list2);

        list1 = null;
        cache.invalidateAll();

        // test case2
        System.out.println(StringUtils.center("case2", 50, "-"));
        list1 = cache.get("all");
        System.out.println(list1);
        list1 = Lists.newArrayList(1);
        System.out.println(list1);
        list2 = cache.get("all");
        System.out.println(list2);

        // test case3
        System.out.println(StringUtils.center("case3", 50, "-"));
        list1 = cache.get("all");
        System.out.println(list1);
        list1 = list1.stream().filter(o -> !o.equals(1)).collect(Collectors.toList());
        System.out.println(list1);
        list2 = cache.get("all");
        System.out.println(list2);
    }
}

总结

使用Guava Cache时应注意:

  • 当缓存类似list集合或者对象时,获取缓存后如果修改了缓存对象,会实际作用到缓存值,对再次获取缓存有影响
  • 如缓存list集合数据获取缓存后需要过滤,应慎用removeIf方法,可考虑stream流filter方式过滤
posted @ 2022-05-17 20:21  cdfive  阅读(397)  评论(0编辑  收藏  举报