Мой идеальный кеш с использованием гуавы
Последние несколько недель я пытался найти свою идеальную реализацию кеша, используя 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(), и насколько велики ваши объекты. Кажется, что не из всех разумных границ делать что-то подобное.
Что касается реализации, похоже, что вы, вероятно, можете наложить свои карты немного по-другому, чтобы получить желаемое поведение, и при этом иметь хорошие свойства параллелизма.
- Создайте свою первую карту со слабыми значениями и поместите на нее вычислительную функцию getFromDatabase().
- Вторая карта - это карта с истекающим сроком действия, также вычисляемая, но эта функция просто получает с первой карты.
Сделайте весь свой доступ через вторую карту.
Другими словами, карта с истекающим сроком действия служит для закрепления недавно использованного подмножества ваших объектов в памяти, в то время как карта со слабой ссылкой является реальным кэшем.
-dg
Я не понимаю полную картину здесь, но две вещи.
Учитывая это утверждение: "эта реализация будет выселять объекты, даже если они сильно достижимы, как только их время истечет. Это может привести к тому, что в среде будут появляться несколько объектов с одинаковым UID, чего я не хочу". - Похоже, вам просто нужно использовать слабый ключ () и НЕ использовать выселение по времени или по размеру.
Или, если вы хотите привлечь к этому "интерна", я бы использовал настоящий
Interners.newWeakInterner
,