[Перевод] Высокопроизводительная библиотека кэширования Java - Caffeine

Java Java EE

оригинал:ууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууу

Добавить Автора

Перепечатано с общедоступного номера: stackgc

1. Введение

В этой статье я представлюCaffeine- ОдинВысокопроизводительная библиотека кэширования Java.

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

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

Кофеин используетсяWindow TinyLfuСтратегия переработки предоставляет.

2. Зависимость

нам надоpom.xmlДобавьте кофеиновую зависимость в:

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.5.5</version>
</dependency>

ты сможешьMaven CentralНайдите последнюю версию кофеина на .

3. Заполнить кеш

Давайте посмотрим на кофеинТри стратегии заполнения кэша: Ручная, синхронная и асинхронная загрузка.

Во-первых, мы пишем класс для типа значения, которое мы хотим хранить в кеше:

class DataObject {
    private final String data;
 
    private static int objectCounter = 0;
    // standard constructors/getters
     
    public static DataObject get(String data) {
        objectCounter++;
        return new DataObject(data);
    }
}

3.1. Заполнение вручную

В этой стратегии мы вручную помещаем значение в кеш, прежде чем извлекать его.

Инициализировать кеш:

Cache<String, DataObject> cache = Caffeine.newBuilder()
  .expireAfterWrite(1, TimeUnit.MINUTES)
  .maximumSize(100)
  .build();

Теперь мы можем использоватьgetIfPresentметод получения значения из кеша. Если указанное значение не существует в кеше, метод вернет null:

String key = "A";
DataObject dataObject = cache.getIfPresent(key);
 
assertNull(dataObject);

мы можем использоватьputМетод ручного заполнения кеша:

cache.put(key, dataObject);
dataObject = cache.getIfPresent(key);
 
assertNotNull(dataObject);

Мы также можем использоватьgetМетод получает значение, которое принимает функцию с ключом в качестве параметра. Если ключ не существует в кеше, эта функция будет использоваться для предоставления значения по умолчанию, которое после вычисления вставляется в кеш:

dataObject = cache
  .get(key, k -> DataObject.get("Data for A"));
 
assertNotNull(dataObject);
assertEquals("Data for A", dataObject.getData());

getМетоды могут выполнять вычисления атомарно. Это означает, что вы выполняете только одно вычисление, даже если несколько потоков одновременно запрашивают значение. Вот почему используйтеgetлучше чемgetIfPresent.

Иногда нам нужно вручную активировать некоторые кэшированные значения.недействителен:

cache.invalidate(key);
dataObject = cache.getIfPresent(key);
 
assertNull(dataObject);

3.2 синхронная нагрузка

Этот способ загрузки кеша использует аналогичную ручную стратегию для функции, используемой для инициализации значения.getметод. Давайте посмотрим, как его использовать.

Во-первых, нам нужно инициализировать кеш:

LoadingCache<String, DataObject> cache = Caffeine.newBuilder()
  .maximumSize(100)
  .expireAfterWrite(1, TimeUnit.MINUTES)
  .build(k -> DataObject.get("Data for " + k));

Теперь мы можем использоватьgetметод получения значения:

DataObject dataObject = cache.get(key);
 
assertNotNull(dataObject);
assertEquals("Data for " + key, dataObject.getData());

Конечно, вы также можете использоватьgetAllметод для получения набора значений:

Map<String, DataObject> dataObjectMap 
  = cache.getAll(Arrays.asList("A", "B", "C"));
 
assertEquals(3, dataObjectMap.size());

от доbuildФункция инициализации метода извлекает значение, что позволяет использовать кеш в качестве основного фасада для доступа к значению.

3.3 Асинхронная загрузка

Эта стратегия делает то же самое, что и раньше, но выполняет операцию асинхронно и возвращаетCompletableFuture:

AsyncLoadingCache<String, DataObject> cache = Caffeine.newBuilder()
  .maximumSize(100)
  .expireAfterWrite(1, TimeUnit.MINUTES)
  .buildAsync(k -> DataObject.get("Data for " + k));

Мы можем использовать таким же образомgetиgetAllметоды, принимая во внимание, что они возвращаютCompletableFuture:

String key = "A";
 
cache.get(key).thenAccept(dataObject -> {
    assertNotNull(dataObject);
    assertEquals("Data for " + key, dataObject.getData());
});
 
cache.getAll(Arrays.asList("A", "B", "C"))
  .thenAccept(dataObjectMap -> assertEquals(3, dataObjectMap.size()));

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

4, переработка стоимости

Кофеин имеетТри стратегии переработки ценности: на основе размера, времени и цитирования.

4.1. Переработка в зависимости от размера

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

Давайте посмотрим, как считать объекты в кеше. При инициализации кеша его размер равен нулю:

LoadingCache<String, DataObject> cache = Caffeine.newBuilder()
  .maximumSize(1)
  .build(k -> DataObject.get("Data for " + k));
 
assertEquals(0, cache.estimatedSize());

Когда мы добавляем значение, размер значительно увеличивается:

cache.get("A");
 
assertEquals(1, cache.estimatedSize());

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

cache.get("B");
cache.cleanUp();
 
assertEquals(1, cache.estimatedSize());

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

Мы также можем передатьweigher Functionчтобы получить размер кеша:

LoadingCache<String, DataObject> cache = Caffeine.newBuilder()
  .maximumWeight(10)
  .weigher((k,v) -> 5)
  .build(k -> DataObject.get("Data for " + k));
 
assertEquals(0, cache.estimatedSize());
 
cache.get("A");
assertEquals(1, cache.estimatedSize());
 
cache.get("B");
assertEquals(2, cache.estimatedSize());

Когда вес превышает 10, значение удаляется из кеша:

cache.get("C");
cache.cleanUp();
 
assertEquals(2, cache.estimatedSize());

4.2. Утилизация по времени

Эта стратегия утилизацииВремя экспирации на основе входа, бывает трех видов:

  • По истечении срока доступа— Срок действия записей истекает с момента последнего чтения или записи.
  • Истекает после записи— Срок действия записей истекает с момента последней записи
  • индивидуальная стратегия— Время истечения рассчитывается только реализацией Expiry

давайте использоватьexpireAfterAccessКонфигурация методаПосле посещения истекСтратегия:

LoadingCache<String, DataObject> cache = Caffeine.newBuilder()
  .expireAfterAccess(5, TimeUnit.MINUTES)
  .build(k -> DataObject.get("Data for " + k));

Чтобы настроитьИстекает после записистратегия, которую мы используемexpireAfterWriteметод:

cache = Caffeine.newBuilder()
  .expireAfterWrite(10, TimeUnit.SECONDS)
  .weakKeys()
  .weakValues()
  .build(k -> DataObject.get("Data for " + k));

Чтобы инициализировать пользовательскую стратегию, нам нужно реализоватьExpiryинтерфейс:

cache = Caffeine.newBuilder().expireAfter(new Expiry<String, DataObject>() {
    @Override
    public long expireAfterCreate(
      String key, DataObject value, long currentTime) {
        return value.getData().length() * 1000;
    }
    @Override
    public long expireAfterUpdate(
      String key, DataObject value, long currentTime, long currentDuration) {
        return currentDuration;
    }
    @Override
    public long expireAfterRead(
      String key, DataObject value, long currentTime, long currentDuration) {
        return currentDuration;
    }
}).build(k -> DataObject.get("Data for " + k));

4.3. Переработка на основе ссылок

Мы можем включить сборку мусора на основе ключа-значения кеша в конфигурации кеша. Для этого мы настраиваем ключ и значение какслабая ссылка, и может только настроитьмягкая ссылкадля сбора мусора.

Когда нет сильной ссылки на объект, используйтеWeakRefenceМусорная коллекция объектов может быть включена.SoftReferenceПозволяет объектам быть глобальными на основе JVMНаименее недавно использованный(Least-Recently-Used) стратегия сборки мусора. Дополнительные сведения о ссылках на Java см.здесь.

мы должны использоватьCaffeine.weakKeys(),Caffeine.weakValues()иCaffeine.softValues()чтобы включить каждую опцию:

LoadingCache<String, DataObject> cache = Caffeine.newBuilder()
  .expireAfterWrite(10, TimeUnit.SECONDS)
  .weakKeys()
  .weakValues()
  .build(k -> DataObject.get("Data for " + k));
 
cache = Caffeine.newBuilder()
  .expireAfterWrite(10, TimeUnit.SECONDS)
  .softValues()
  .build(k -> DataObject.get("Data for " + k));

5. Обновить

Кэш можно настроить на автоматическое обновление записей по истечении заданного периода времени. Давайте посмотрим, как использоватьrefreshAfterWriteметод:

Caffeine.newBuilder()
  .refreshAfterWrite(1, TimeUnit.MINUTES)
  .build(k -> DataObject.get("Data for " + k));

Здесь мы должны понятьexpireAfterиrefreshAfterразница между. Когда запрашивается запись с истекшим сроком действия, выполнение будет заблокировано до тех пор, покаbuild Functionпока не будет рассчитано новое значение.

Однако, если запись может быть сброшена, кеш вернет старое значение иАсинхронно перезагрузить значение.

6. Статистика

Кофеин имеетЗаписи об использовании кеша пути:

LoadingCache<String, DataObject> cache = Caffeine.newBuilder()
  .maximumSize(100)
  .recordStats()
  .build(k -> DataObject.get("Data for " + k));
cache.get("A");
cache.get("A");
 
assertEquals(1, cache.stats().hitCount());
assertEquals(1, cache.stats().missCount());

Мы также можем пройти вrecordStatsпоставщик, создатьStatsCounterреализация. Этот объект будет отправляться каждый раз, когда вносятся изменения, связанные со статистикой.

7. Заключение

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

Исходный код примеров из этой статьи можно найти по адресуGithubнайти на.