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

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

Принимая во внимание то, что я узнал, моя следующая попытка будет отбросить мягкие значения в пользу MaximumSize и 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;
   }
};

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

2 ответа

Решение

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

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

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

Сделайте весь свой доступ через вторую карту.

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

-dg

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

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

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

Другие вопросы по тегам