мой идеальный кеш с использованием гуавы

Время от времени в течение последних нескольких недель я пытался найти свою идеальную реализацию кеша, используя MapMaker. См. мои предыдущие два вопроса здесь и здесь, чтобы следовать моему мыслительному процессу.

Принимая во внимание то, что я узнал, моей следующей попыткой будет отказаться от мягких значений в пользу maxSize и expireAfterAccess:

ConcurrentMap<String, MyObject> cache = new MapMaker()
        .maximumSize(MAXIMUM_SIZE)
        .expireAfterAccess(MINUTES_TO_EXPIRY, TimeUnit.MINUTES)
        .makeComputingMap(loadFunction);

где

Function<String, MyObject> loadFunction = new Function<String, MyObject>() {
   @Override
   public MyObject apply(String uidKey) {
      return getFromDataBase(uidKey);
   }
};

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

Итак, насколько я могу судить, единственный ответ - иметь дополнительную карту, которая функционирует как внутренняя карта, которую я могу проверить, чтобы увидеть, находится ли объект данных все еще в памяти:

ConcurrentMap<String, MyObject> interner = new MapMaker()
        .weakValues()
        .makeMap();

и функция нагрузки будет пересмотрена:

Function<String, MyObject> loadFunction = new Function<String, MyObject>() {
   @Override
   public MyObject apply(String uidKey) {
      MyObject dataObject = interner.get(uidKey);
      if (dataObject == null) {
         dataObject = getFromDataBase(uidKey);
         interner.put(uidKey, dataObject);
      }
      return dataObject;
   }
};

Однако использование двух карт вместо одной для кеша кажется неэффективным. Есть ли более сложный способ приблизиться к этому? В общем, правильно ли я это делаю, или мне следует переосмыслить свою стратегию кэширования?


person Paul Bellora    schedule 21.07.2011    source источник
comment
Немного поздно, но вам может быть интересно узнать стоимость памяти для каждого из различных вариантов создания кеша: code-o-matic.blogspot.com/2012/02/   -  person Dimitris Andreou    schedule 08.02.2012
comment
@DimitrisAndreou - Очень интересно, спасибо за ссылку! Я планирую обновить этот пост для Cache/CacheBuilder, как только поиграю с ними побольше.   -  person Paul Bellora    schedule 09.02.2012


Ответы (2)


Эффективность двух карт полностью зависит от того, насколько дорогой метод getFromDatabase() и насколько велики ваши объекты. Это не кажется выходом за все разумные пределы, чтобы сделать что-то подобное.

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

  1. Создайте свою первую карту со слабыми значениями и поместите на нее вычислительную функцию getFromDatabase().
  2. Вторая карта — истекающая, тоже вычислительная, но эта функция просто достается от первой карты.

Делайте все свои доступы через вторую карту.

Другими словами, карта с истекающим сроком действия закрепляет в памяти самое последнее использовавшееся подмножество ваших объектов, в то время как карта со слабыми ссылками является настоящим кешем.

-dg

person Darren Gilroy    schedule 25.07.2011
comment
+1 Спасибо, вы правы, добавленная логика loadFunction делает то, что внутренняя карта может делать как вторая вычислительная карта. Я думаю, что это в основном косметическая разница, поскольку доступ к внутренней карте осуществляется только через карту с истекающим сроком действия (уже обрабатывающую одновременные запросы), но более элегантную. Ваш ответ, кажется, поддерживает то, что я делаю - я посмотрю, есть ли другие предложения, прежде чем принять - person Paul Bellora; 26.07.2011

Я не понимаю полной картины здесь, но две вещи.

  1. Учитывая это утверждение: «эта реализация будет вытеснять объекты, даже если они сильно доступны, как только их время истекло. Это может привести к тому, что несколько объектов с одним и тем же UID будут плавать в среде, чего я не хочу». -- похоже, вам просто нужно использовать weakKeys() и НЕ использовать выселение по времени или по размеру.

  2. Или, если вы хотите привлечь к этому «интерната», я бы использовал настоящего Interners.newWeakInterner.

person Kevin Bourrillion    schedule 25.07.2011
comment
Спасибо за вклад - я думаю, я не совсем понимаю, как использовать weakKeys(), особенно если ключ является строкой или UUID и т. д. Нужен ли мне тогда класс-оболочка, который будет действовать как ключ? Кроме того, я не понимаю стратегии использования слабых ключей и отказа от выселения на основе времени и размера. Я определенно хочу, чтобы кешированный объект был надежно доступен в течение как минимум x минут после доступа (или нет?). Моя проблема заключается в том, что происходит после этого, если объект все еще находится в тисках длительного процесса. Возможно, я что-то упускаю из-за слабых ключей. - person Paul Bellora; 26.07.2011
comment
О интернере - я рассматривал возможность использования настоящего интернера, однако он поддерживает только метод intern() и не имеет возможности проверить, действительно ли там находится объект или нет. Слабая карта дает мне возможность узнать, есть ли у меня объект в памяти или его нужно загрузить. Может быть, я использую термин слишком свободно? - person Paul Bellora; 26.07.2011
comment
Привет, Кевин, я принимаю ответ Даррена, но был бы признателен за разъяснения по weakKeys(), если у вас будет такая возможность. Благодарность! - person Paul Bellora; 27.07.2011