Магазин ключових значень з хронічною картою

1. Огляд

У цьому підручнику ми побачимо, як ми можемо використовувати Хронічну карту для зберігання пар ключ-значення. Ми також створимо короткі приклади, щоб продемонструвати його поведінку та використання.

2. Що таке хронічна карта?

Відповідно до документації, «Карта хроніки - це супершвидке сховище пам’яті, що не блокується, ключ-значення, розроблене для додатків із низькою затримкою та / або багатопроцесорності».

У двох словах, це сховище ключ-значення. Карта не вимагає великої кількості оперативної пам'яті, щоб вона могла нормально функціонувати. Він може зростати залежно від доступної ємності диска . Крім того, він підтримує реплікацію даних у налаштуваннях мульти-майстер-сервера.

Давайте тепер подивимося, як ми можемо налаштувати та працювати з ним.

3. Залежність Мейвена

Для початку нам потрібно додати залежність хронічної карти до нашого проекту:

 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 ідентифікаторів країн та їх назви. Як ми бачимо у фрагменті коду, створення є досить простим, за винятком конфігурації averageValue () . Це повідомляє карті, щоб налаштувати середню кількість байтів, прийнятих значеннями записів на карті.

Іншими словами, під час створення карти Хронічна карта визначає середню кількість байтів, прийнятих серіалізованою формою значень. Це робиться шляхом серіалізації заданого середнього значення за допомогою налаштованих маршалерів значень. Потім він виділить визначену кількість байтів для значення кожного запису на карті.

Одне, що ми маємо зазначити, коли мова заходить про карту в пам'яті, це те, що дані доступні лише тоді, коли процес 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 у вказаній папці. Якщо цей файл уже доступний у вказаному шляху, тоді реалізація конструктора відкриє посилання на існуючий сховище даних із цього процесу JVM.

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

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

5. Конфігурація розміру

Обов’язковим є налаштування середнього значення та середнього ключа під час створення Хронічної карти, за винятком випадків, коли наш тип ключа / значення є або примітивом, або інтерфейсом значення. У нашому прикладі ми не налаштовуємо середній ключ, оскільки тип ключа LongValue є значенням інтерфейсу.

Тепер давайте подивимося, які варіанти є для налаштування середньої кількості байт ключ / значення:

  • averageValue () - значення, з якого визначається середня кількість байтів, що виділяється для значення запису на карті
  • averageValueSize () - Середня кількість байтів, що виділяється для значення запису на карті
  • constantValueSizeBySample () - Кількість байтів, що виділяється для значення запису на карті, коли розмір значення завжди однаковий
  • averageKey() – The key from which the average number of bytes to be allocated for the key of a map entry is determined
  • averageKeySize() – The average number of bytes to be allocated for the key of a map entry
  • constantKeySizeBySample() – The number of bytes to be allocated for the key of a map entry when the size of the key is always the same

6. Key and Value Types

There are certain standards that we need to follow when creating a Chronicle Map, especially when defining the key and value. The map works best when we create the key and value using the recommended types.

Here are some of the recommended types:

  • Value interfaces
  • Any class implementing Byteable interface from Chronicle Bytes
  • Any class implementing BytesMarshallable interface from Chronicle Bytes; the implementation class should have a public no-arg constructor
  • byte[] and ByteBuffer
  • CharSequence, String, and StringBuilder
  • Integer, Long, and Double
  • Any class implementing java.io.Externalizable; the implementation class should have a public no-arg constructor
  • Any type implementing java.io.Serializable, including boxed primitive types (except those listed above) and array types
  • Any other type, if custom serializers are provided

7. Querying a Chronicle Map

Chronicle Map supports single-key queries as well as multi-key queries.

7.1. Single-Key Queries

Single-key queries are the operations that deal with a single key. ChronicleMap supports all the operations from the Java Map interface and ConcurrentMap interface:

LongValue qatarKey = Values.newHeapInstance(LongValue.class); qatarKey.setValue(1); inMemoryCountryMap.put(qatarKey, "Qatar"); //... CharSequence country = inMemoryCountryMap.get(key);

In addition to the normal get and put operations, ChronicleMap adds a special operation, getUsing(), that reduces the memory footprint while retrieving and processing an entry. Let's see this in action:

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")));

Here we've used the same StringBuilder object for retrieving values of different keys by passing it to the getUsing() method. It basically reuses the same object for retrieving different entries. In our case, the getUsing() method is equivalent to:

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

7.2. Multi-Key Queries

There may be use cases where we need to deal with multiple keys at the same time. For this, we can use the queryContext() functionality. The queryContext() method will create a context for working with a map entry.

Let's first create a multimap and add some values to it:

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);
     
    

To work with multiple entries, we have to lock those entries to prevent inconsistency that may occur due to a concurrent update:

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. Closing the Chronicle Map

Тепер, коли ми закінчили роботу з нашими картами, давайте зателефонуємо методу close () на наших об’єктах карти, щоб звільнити пам’ять без кучі та пов’язані з нею ресурси:

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

Тут слід пам’ятати одне, що всі операції з картою повинні бути завершені перед закриттям карти. В іншому випадку JVM може несподівано зірватися.

9. Висновок

У цьому підручнику ми дізналися, як використовувати хронічну карту для зберігання та отримання пар ключ-значення. Незважаючи на те, що версія спільноти доступна з більшістю основних функціональних можливостей, комерційна версія має деякі розширені функції, такі як реплікація даних на декількох серверах та віддалені дзвінки.

Усі приклади, які ми обговорили тут, можна знайти в проекті Github.