Подтвердить что ты не робот

Проектирование Guava LoadCache с истечением переменной записи

Я использую Guava LoadCache в свой проект, чтобы обрабатывать загрузку кеша thread - {safe, friendly}, и он отлично работает. Однако есть ограничение.

Текущий код, определяющий кеш, выглядит следующим образом:

cache = CacheBuilder.newBuilder().maximumSize(100L).build(new CacheLoader<K, V>()
{
    // load() method implemented here
}

Я не указываю время истечения срока действия.

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

Как бы вы справились с этой проблемой?

4b9b3361

Ответ 1

Другим вариантом является ExpiringMap, который поддерживает истечение срока действия переменной:

Map<String, String> map = ExpiringMap.builder().variableExpiration().build();
map.put("foo", "bar", ExpirationPolicy.ACCESSED, 5, TimeUnit.MINUTES);
map.put("baz", "pez", ExpirationPolicy.CREATED, 10, TimeUnit.MINUTES);

Ответ 2

Я предлагаю вам включить время истечения срока действия непосредственно в ваш класс ввода и вручную выселить его из кеша, если он истек сразу после того, как вы извлекли его из кеша:

MyItem item = cache.getIfPresent(key);
if (item != null && item.isExpired()) {
    cache.invalidate(key);
    item = cache.get(key);
    // or use cache.put if you load it externally
}

В качестве альтернативы я могу предложить вам проверить библиотеку EhCache, которая поддерживает политику истечения срока действия элемента.

Ответ 3

LoadingCache предлагает некоторые общепринятые политики истечения срока действия, но если это не подходит для того, что вам нужно, вам нужно перевернуть свои собственные.

Просто добавьте DelayQueue. Всякий раз, когда вы добавляете что-то в кеш, добавьте Delayed в эту очередь с соответствующим временем истечения. Объект Delayed должен иметь ссылку (слабый?) На ключ.

Конечным ингредиентом является то, что вам нужно периодически опросить эту очередь, чтобы узнать, истекло ли что-то и что должно быть выселено. Не обязательно добавлять поток, чтобы сделать это, вы могли бы просто контактировать с любым потоком, обращаясь к LoadingCache. Перед доступом к кешу, например:

private void drainCache() {
  MyDelayed expired;
  while ((expired = delayedQueue.poll()) != null) {
    K key = expired.getReference();
    if (key != null) { // this only in case if you hold the key in a weak reference
      loadingCache.invalidate(key);
    }
  }
}

..
V lookup(K key) {
  drainCache();
  return loadingCache.getUnchecked(key);
}

Ответ 4

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

Однако вы можете дать разные веса для записей. Это не идеально, но вы можете направлять кеш для выселения записей, которые менее важны. См. Weighter, записи с весом 0 не будут выселены путем выселения на основе размера.

Ответ 5

Если записи большие, и вам нужно сохранить память, я не думаю, что там есть хорошее решение, но эти хаки приходят в голову:

  • Используйте PriorityQueue, упорядоченный по истечении времени для ручного удаления записей. Если вы хотите быть уверенным, что никакая истекшая запись не используется, вам нужно объединить это с решением hoaz; очередь только предотвращает использование бесполезных записей в памяти.

  • Вы написали: "некоторые связанные значения могут истекать, а другие - нет", что говорит о том, что задержка истечения одинакова для всех истекающих записей. Это позволит использовать более простой и быстрый Queue (например, ArrayDeque вместо PriorityQueue).

  • В случае, если задержка истечения достаточно велика, вы можете оставить все записи и снова вставить те, которые должны жить вечно в RemovalListener. Это может потерпеть неудачу двумя способами: 1. Вы можете получить пропущенное время. 2. Изъятия и повторные вставки могут стоить много процессорного времени.

Ответ 6

Вы можете использовать библиотеку кофеина, которая вдохновлена гуавой. Вот пример использования из репозитория github

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfterAccess(5, TimeUnit.MINUTES)

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfterCreate(5, TimeUnit.MINUTES)

https://github.com/ben-manes/caffeine