按大小排除咖啡因似乎不起作用

Caffeine eviction by size seems to not work

我正在使用咖啡因缓存。

我想把它放在大小限制下,但它不能正常工作。

测试 1:

Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(3)
                .build();

        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(3)
                .build();

        for (int i = 1; i <= 10; i ++) {
            String val = String.valueOf(i);
            cache.put(val, val);
        }

        System.out.println("cache size: " + cache.estimatedSize() + ", cache keys: " + cache.asMap().values().stream().collect(Collectors.joining(",")));


result:   cache size: 10, cache keys: 1,2,10

另一个测试:尝试获取密钥并将最大值设置为 1

Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(1)
                .build();

        for (int i = 1; i <= 10; i ++) {
            String val = String.valueOf(i);
            cache.put(val, val);

            if (i % 2 == 0) {
                cache.getIfPresent("5");
            }
        }

        System.out.println("cache size: " + cache.estimatedSize() + ", cache keys: " + cache.asMap().values().stream().collect(Collectors.joining(",")));


cache size: 10, cache keys: 2,3,4,5,6,7,8,9,10

上次测试:运行 100 次,最大尺寸 1

 Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(1)
                .build();

        for (int i = 1; i <= 100; i ++) {
            String val = String.valueOf(i);
            cache.put(val, val);

            if (i % 2 == 0) {
                cache.getIfPresent("5");
            }
        }

        System.out.println("cache size: " + cache.estimatedSize() + ", cache keys: " + cache.asMap().values().stream().collect(Collectors.joining(",")));

cache size: 99, cache keys: 96,97,99,19,23,58

有人可以帮我理解这个以及如何让它正常工作吗?


感谢 Ben Manes,我添加了 .executor(Runnable::run)

现在在这样做之后我只得到了 3 个项目

 Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(3)
                .executor(Runnable::run)
                .build();

        for (int i = 1; i <= 10; i ++) {
            String val = String.valueOf(i);
            cache.put(val, val);

            if (i % 2 == 0) {
                cache.getIfPresent("5");
            }
        }
        cache.cleanUp();
        System.out.println("cache size: " + cache.estimatedSize() + ", cache: " + CodecUtils.toJson(cache.asMap().values()));


cache size: 3, cache: ["3","9","10"]

  1. 这不会阻塞我的线程吗?
  2. 为什么缓存中没有密钥 5,因为我已经多次使用它了?

默认情况下,缓存会异步执行一些操作,例如逐出和通知移除侦听器。这是为了最大限度地减少请求延迟,因为请求本身不需要辅助工作,并且用户提供的回调可能很昂贵。

缓存自身的维护工作非常便宜,因此如果需要,您可以使用 Caffeine.executor(Runnable::run) 在调用者的线程上安全地 运行 缓存。这将通过额外驱逐条目来惩罚调用者,但不会阻止其他操作的发生。这是由于缓存内部使用了多个锁和操作缓冲区,因此它可以在锁忙时调度工作而不是阻塞线程。

关于大小,这是因为条目在被检索之前被逐出,因此它不会增加频率。如果条目不存在,getIfPresent 不会增加频率,而 get(key, /* loading function */) 会因为在未命中时加载值而受到惩罚。驱逐政策在其决策中同时利用新近度和频率,因此它可能会尽可能早地驱逐最近到达的人 "one-hit wonders",也就是缓存污染。

如果我们按原样使用您的代码并输出缓存的状态,我们会看到这一点,

for (int i = 1; i <= 10; i++) {
  String val = String.valueOf(i);
  cache.put(val, val);
  System.out.println(val + " -> " + cache.asMap());
  if (i % 2 == 0) {
    cache.getIfPresent("5");
  }
}
cache.cleanUp();
System.out.println("cache size: " + cache.estimatedSize());
1 -> {1=1}
2 -> {1=1, 2=2}
3 -> {1=1, 2=2, 3=3}
4 -> {2=2, 3=3, 4=4}
5 -> {2=2, 3=3, 5=5}
6 -> {2=2, 3=3, 6=6}
7 -> {2=2, 3=3, 7=7}
8 -> {2=2, 3=3, 8=8}
9 -> {2=2, 3=3, 9=9}
10 -> {2=2, 3=3, 10=10}
cache size: 3

如果我们在每次迭代中访问密钥 5,那么它会被保留,

for (int i = 1; i <= 10; i++) {
  String val = String.valueOf(i);
  cache.put(val, val);
  System.out.println(val + " -> " + cache.asMap());
  cache.getIfPresent("5");
}
cache.cleanUp();
System.out.println("cache size: " + cache.estimatedSize());
1 -> {1=1}
2 -> {1=1, 2=2}
3 -> {1=1, 2=2, 3=3}
4 -> {2=2, 3=3, 4=4}
5 -> {2=2, 3=3, 5=5}
6 -> {3=3, 5=5, 6=6}
7 -> {3=3, 5=5, 7=7}
8 -> {3=3, 5=5, 8=8}
9 -> {3=3, 5=5, 9=9}
10 -> {3=3, 5=5, 10=10}
cache size: 3