Schlüsselwertspeicher mit Chronikkarte

1. Übersicht

In diesem Tutorial werden wir sehen, wie wir die Chronikkarte zum Speichern von Schlüssel-Wert-Paaren verwenden können. Wir werden auch kurze Beispiele erstellen, um das Verhalten und die Verwendung zu demonstrieren.

2. Was ist eine Chronikkarte?

Nach der Dokumentation ist „Chronicle Map ein superschneller, speicherinterner, nicht blockierender Schlüsselwertspeicher, der für Anwendungen mit geringer Latenz und / oder für mehrere Prozesse entwickelt wurde“.

Kurz gesagt, es handelt sich um einen Off-Heap-Schlüsselwertspeicher. Die Karte benötigt nicht viel RAM, damit sie ordnungsgemäß funktioniert. Es kann basierend auf der verfügbaren Festplattenkapazität wachsen . Darüber hinaus unterstützt es die Replikation der Daten in einem Multi-Master-Server-Setup.

Lassen Sie uns nun sehen, wie wir es einrichten und damit arbeiten können.

3. Maven-Abhängigkeit

Um zu beginnen, müssen wir unserem Projekt die Abhängigkeit von der Chronikkarte hinzufügen:

 net.openhft chronicle-map 3.17.2 

4. Arten von Chronikkarten

Wir können eine Karte auf zwei Arten erstellen: entweder als In-Memory-Karte oder als persistierte Karte.

Lassen Sie uns beide im Detail sehen.

4.1. In-Memory-Map

Eine speicherinterne Chronikkarte ist ein Kartenspeicher, der im physischen Speicher des Servers erstellt wird. Dies bedeutet, dass nur innerhalb des JVM-Prozesses darauf zugegriffen werden kann, in dem der Kartenspeicher erstellt wird .

Sehen wir uns ein kurzes Beispiel an:

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

Der Einfachheit halber erstellen wir eine Karte, auf der 50 Länder-IDs und deren Namen gespeichert sind. Wie wir im Code-Snippet sehen können, ist die Erstellung mit Ausnahme der Konfiguration von averValue () ziemlich einfach . Dadurch wird die Karte angewiesen, die durchschnittliche Anzahl von Bytes zu konfigurieren, die von Karteneintragswerten benötigt werden.

Mit anderen Worten, beim Erstellen der Karte bestimmt die Chronikkarte die durchschnittliche Anzahl von Bytes, die von der serialisierten Form von Werten benötigt werden. Dazu wird der angegebene Durchschnittswert mithilfe der konfigurierten Wert-Marshaller serialisiert. Anschließend wird die festgelegte Anzahl von Bytes für den Wert jedes Karteneintrags zugewiesen.

Eine Sache, die wir bei der In-Memory-Map beachten müssen, ist, dass auf die Daten nur zugegriffen werden kann, wenn der JVM-Prozess aktiv ist. Die Bibliothek löscht die Daten, wenn der Prozess beendet wird.

4.2. Beharrte Karte

Im Gegensatz zu einer In-Memory-Map speichert die Implementierung eine persistente Map auf der Festplatte . Lassen Sie uns nun sehen, wie wir eine dauerhafte Karte erstellen können:

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

Dadurch wird eine Datei mit dem Namen country-details.dat in dem angegebenen Ordner erstellt. Wenn diese Datei bereits im angegebenen Pfad verfügbar ist, öffnet die Builder-Implementierung von diesem JVM-Prozess aus einen Link zum vorhandenen Datenspeicher.

Wir können die persistierte Karte in Fällen verwenden, in denen wir dies wünschen:

  • über den Schöpferprozess hinaus überleben; Zum Beispiel zur Unterstützung der erneuten Bereitstellung heißer Anwendungen
  • Machen Sie es global auf einem Server. Zum Beispiel, um den gleichzeitigen Zugriff auf mehrere Prozesse zu unterstützen
  • fungieren als Datenspeicher, den wir auf der Festplatte speichern

5. Größenkonfiguration

Es ist obligatorisch, den Durchschnittswert und den Durchschnittsschlüssel beim Erstellen einer Chronikkarte zu konfigurieren, außer in dem Fall, in dem unser Schlüssel- / Werttyp entweder ein Boxed-Primitive oder eine Werteschnittstelle ist. In unserem Beispiel konfigurieren wir den durchschnittlichen Schlüssel nicht, da der Schlüsseltyp LongValue eine Werteschnittstelle ist.

Lassen Sie uns nun sehen, welche Optionen zum Konfigurieren der durchschnittlichen Anzahl von Schlüssel- / Wertbytes zur Verfügung stehen:

  • durchschnittlicher Wert () - Der Wert, aus dem die durchschnittliche Anzahl von Bytes bestimmt wird, die für den Wert eines Karteneintrags zugewiesen werden sollen
  • averValueSize () - Die durchschnittliche Anzahl von Bytes, die für den Wert eines Karteneintrags zugewiesen werden sollen
  • constantValueSizeBySample () - Die Anzahl der Bytes, die für den Wert eines Karteneintrags zugewiesen werden sollen, wenn die Größe des Werts immer gleich ist
  • 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

Nachdem wir mit der Arbeit an unseren Karten fertig sind, rufen wir die Methode close () für unsere Kartenobjekte auf, um den Off-Heap-Speicher und die damit verbundenen Ressourcen freizugeben:

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

Hierbei ist zu beachten, dass alle Kartenvorgänge abgeschlossen sein müssen, bevor die Karte geschlossen wird. Andernfalls kann die JVM unerwartet abstürzen.

9. Fazit

In diesem Tutorial haben wir gelernt, wie Sie mithilfe einer Chronikkarte Schlüssel-Wert-Paare speichern und abrufen. Obwohl die Community-Version mit den meisten Kernfunktionen verfügbar ist, verfügt die kommerzielle Version über einige erweiterte Funktionen wie Datenreplikation über mehrere Server und Remote-Aufrufe.

Alle Beispiele, die wir hier besprochen haben, finden Sie im Github-Projekt.