Рубрики
Без рубрики

Хранилище ключевых значений с картой хроник

Узнайте, как хранить и извлекать пары ключ-значение в Java с помощью карты хроники.

Автор оригинала: Tino Mulanchira Thomas.

1. Обзор

В этом уроке мы увидим, как мы можем использовать карту Chronicle для хранения пар ключ-значение. Мы также создадим короткие примеры, чтобы продемонстрировать его поведение и использование.

2. Что такое Летописная карта?

Следуя документации, “Chronicle Map-это сверхбыстрое, неблокирующееся хранилище ключей и значений в памяти, предназначенное для приложений с низкой задержкой и/или многопроцессорных приложений”.

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

Давайте теперь посмотрим, как мы можем настроить и работать с ним.

3. Зависимость Maven

Для начала нам нужно добавить зависимость chronicle-map в наш проект:


    net.openhft
    chronicle-map
    3.17.2

4. Виды Летописной карты

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

Давайте рассмотрим их оба в деталях.

4.1. Карта в памяти

Карта хроники в памяти-это хранилище карт, созданное в физической памяти сервера. Это означает, что он доступен только в процессе JVM, в котором создается хранилище карт .

Давайте рассмотрим краткий пример:

ChronicleMap inMemoryCountryMap = ChronicleMap
  .of(LongValue.class, CharSequence.class)
  .name("country-map")
  .entries(50)
  .averageValue("America")
  .create();

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

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

Одна вещь, которую мы должны отметить, когда речь заходит о карте в памяти, заключается в том, что данные доступны только тогда, когда процесс JVM активен. Библиотека очистит данные, когда процесс завершится.

4.2. Сохраненная карта

В отличие от карты в памяти, реализация сохранит сохраненную карту на диск . Теперь давайте посмотрим, как мы можем создать сохраняемую карту:

ChronicleMap persistedCountryMap = ChronicleMap
  .of(LongValue.class, CharSequence.class)
  .name("country-map")
  .entries(50)
  .averageValue("America")
  .createPersistedTo(new File(System.getProperty("user.home") + "/country-details.dat"));

Это создаст файл с именем country-details.dat в указанной папке. Если этот файл уже доступен по указанному пути, реализация builder откроет ссылку на существующее хранилище данных из этого процесса JVM.

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

  • выжить за пределами процесса создания; например, для поддержки горячего перераспределения приложений
  • сделайте его глобальным на сервере; например, для поддержки одновременного доступа к нескольким процессам
  • действуйте как хранилище данных, которое мы сохраним на диске

5. Конфигурация Размера

Необходимо настроить среднее значение и средний ключ при создании карты хроники, за исключением случаев, когда наш тип ключа/значения является либо коробочным примитивом, либо интерфейсом значений. В нашем примере мы не настраиваем средний ключ, так как тип ключа Long Value является интерфейсом value .

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

  • averageValue() – Значение, из которого определяется среднее количество байтов, выделяемых для значения записи карты
  • averageValueSize() – Среднее количество байтов, выделяемых для значения записи карты
  • constantValueSizeBySample() – Количество байтов, выделяемых для значения записи карты, когда размер значения всегда одинаков
  • averageKey() – Ключ, из которого определяется среднее количество байтов, выделяемых для ключа записи карты
  • averageKeySize() – Среднее количество байтов, выделяемых для ключа записи карты
  • constantKeySizeBySample() – Количество байтов, выделяемых для ключа записи карты, когда размер ключа всегда одинаков

6. Типы ключей и значений

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

Вот некоторые из рекомендуемых типов:

  • Значение интерфейсы
  • Любой класс, реализующий Byteable интерфейс из Байтов хроники
  • Любой класс, реализующий BytesMarshallable интерфейс из байтов хроники; класс реализации должен иметь открытый конструктор no-arg
  • байт[] и ByteBuffer
  • CharSequence , String и StringBuilder
  • Целое число , Длинное и Двойное
  • Любой класс, реализующий java.io.Externalizable ; класс реализации должен иметь открытый конструктор no-arg
  • Любой тип, реализующий java.io.Serializable , включая коробочные примитивные типы (кроме перечисленных выше) и типы массивов
  • Любой другой тип, если предусмотрены пользовательские сериализаторы

7. Запрос карты хроники

Карта хроники поддерживает запросы с одним ключом, а также запросы с несколькими ключами.

7.1. Запросы с одним ключом

Запросы с одним ключом-это операции, которые имеют дело с одним ключом. Chronicle Map поддерживает все операции из Java Map интерфейса и ConcurrentMap интерфейса:

LongValue qatarKey = Values.newHeapInstance(LongValue.class);
qatarKey.setValue(1);
inMemoryCountryMap.put(qatarKey, "Qatar");

//...

CharSequence country = inMemoryCountryMap.get(key);

В дополнение к обычным операциям get и put ChronicleMap добавляет специальную операцию get Using (), которая уменьшает объем памяти при извлечении и обработке записи . Давайте посмотрим на это в действии:

LongValue key = Values.newHeapInstance(LongValue.class);
StringBuilder country = new StringBuilder();
key.setValue(1);
persistedCountryMap.getUsing(key, country);
assertThat(country.toString(), is(equalTo("Romania")));

key.setValue(2);
persistedCountryMap.getUsing(key, country);
assertThat(country.toString(), is(equalTo("India")));

Здесь мы использовали один и тот же объект StringBuilder для извлечения значений различных ключей, передавая его в метод get Using () . Он в основном повторно использует один и тот же объект для извлечения разных записей. В нашем случае метод get Using() эквивалентен:

country.setLength(0);
country.append(persistedCountryMap.get(key));

7.2. Запросы с несколькими ключами

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

Давайте сначала создадим мультимап и добавим в него некоторые значения:

Set averageValue = IntStream.of(1, 2).boxed().collect(Collectors.toSet());
ChronicleMap> multiMap = ChronicleMap
  .of(Integer.class, (Class>) (Class) Set.class)
  .name("multi-map")
  .entries(50)
  .averageValue(averageValue)
  .create();

Set set1 = new HashSet<>();
set1.add(1);
set1.add(2);
multiMap.put(1, set1);

Set set2 = new HashSet<>();
set2.add(3);
multiMap.put(2, set2);

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

try (ExternalMapQueryContext, ?> fistContext = multiMap.queryContext(1)) {
    try (ExternalMapQueryContext, ?> secondContext = multiMap.queryContext(2)) {
        fistContext.updateLock().lock();
        secondContext.updateLock().lock();

        MapEntry> firstEntry = fistContext.entry();
        Set firstSet = firstEntry.value().get();
        firstSet.remove(2);

        MapEntry> secondEntry = secondContext.entry();
        Set secondSet = secondEntry.value().get();
        secondSet.add(4);

        firstEntry.doReplaceValue(fistContext.wrapValueAsData(firstSet));
        secondEntry.doReplaceValue(secondContext.wrapValueAsData(secondSet));
    }
} finally {
    assertThat(multiMap.get(1).size(), is(equalTo(1)));
    assertThat(multiMap.get(2).size(), is(equalTo(2)));
}

8. Закрытие карты Хроники

Теперь, когда мы закончили работу с нашими картами, давайте вызовем метод close() на наших объектах карты, чтобы освободить память вне кучи и связанные с ней ресурсы:

persistedCountryMap.close();
inMemoryCountryMap.close();
multiMap.close();

Здесь следует иметь в виду, что все операции с картой должны быть завершены до закрытия карты. В противном случае JVM может неожиданно выйти из строя.

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

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

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