Похоже, что кофеин по размеру не работает - PullRequest
0 голосов
/ 23 апреля 2020

Я использую кофеиновый кеш.

Я хочу ограничить размер, но он не работает должным образом.

тест 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

Может кто-нибудь, пожалуйста, помогите мне понять это и как заставить его работать должным образом?


Благодаря Бену Мэнсу я добавил .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, так как я несколько раз его использовал?

1 Ответ

1 голос
/ 23 апреля 2020

По умолчанию кэш будет выполнять некоторые операции асинхронно, например, выселение и уведомление слушателя удаления. Это сводит к минимуму задержки запроса, поскольку вспомогательная работа не является необходимой для самого запроса, а предоставляемые пользователем обратные вызовы могут быть дорогими.

Собственные работы по обслуживанию кэша очень дешевы, поэтому вы можете безопасно запустить их на поток звонящего по желанию с помощью Caffeine.executor(Runnable::run). Это накажет вызывающего абонента дополнительным удалением записи, но не заблокирует выполнение других операций. Это связано с тем, что кэш-память внутренне использует несколько блокировок и рабочих буферов, поэтому он может планировать работу, когда блокировка занята, а не потоками блоков.

Что касается размера, то это потому, что запись исключена ранее чтобы быть извлеченным, чтобы он не наращивал частоту. getIfPresent не увеличивает частоту, если запись отсутствует, тогда как get(key, /* loading function */) - потому что штрафуется за загрузку значения в промах. Политика выселения использует в своих решениях как актуальность, так и частоту, поэтому она может высвобождать недавние поступления как можно раньше, как «чудеса одного удара», то есть загрязнение кэша.

Если мы возьмем ваш код как есть и выведем состояние кэша мы видим это,

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
...