クロニクルマップを使用したキーバリューストア

 1. 概要

このチュートリアルでは、https://github.com/OpenHFT/Chronicle-Map/blob/master/docs/CM_Tutorial.adoc [Chronicle Map]を使用してキーと値のペアを保存する方法を確認します。 また、その動作と使用法を示すために短い例を作成します。

2. クロニクルマップとは

ドキュメントに続いて、*「クロニクルマップは、超低遅延、および/またはマルチプロセスアプリケーション向けに設計された、超高速、メモリ内、ノンブロッキング、キーバリューストアです。」*
要するに、これはオフヒープのキーと値のストアです。 マップは、適切に機能するために大量のRAMを必要としません。 *使用可能なディスク容量に基づいて拡張できます*。 さらに、マルチマスターサーバー設定でのデータの複製をサポートします。
それでは、どのようにセットアップして操作できるかを見てみましょう。

3. Mavenの依存関係

始めるには、プロジェクトにhttps://search.maven.org/search?q=g:net.openhft%20AND%20a:chronicle-map[chronicle-mapdependency]を追加する必要があります。
<dependency>
    <groupId>net.openhft</groupId>
    <artifactId>chronicle-map</artifactId>
    <version>3.17.2</version>
</dependency>

4. クロニクルマップの種類

マップは、インメモリマップとして、または永続マップとしての2つの方法で作成できます。
これらの両方を詳細に見てみましょう。

* 4.1。 インメモリマップ*

インメモリクロニクルマップは、サーバーの物理メモリ内に作成されるマップストアです。 これは、*マップストアが作成されたJVMプロセス内でのみアクセスできることを意味します*。
簡単な例を見てみましょう。
ChronicleMap<LongValue, CharSequence> inMemoryCountryMap = ChronicleMap
  .of(LongValue.class, CharSequence.class)
  .name("country-map")
  .entries(50)
  .averageValue("America")
  .create();
簡単にするために、50の国IDとその名前を格納するマップを作成しています。 コードスニペットでわかるように、_averageValue()_構成を除いて、作成は非常に簡単です。 これにより、マップエントリ値が使用する平均バイト数を設定するようにマップに指示します。
言い換えると、*マップを作成するとき、クロニクルマップは値のシリアル化された形式で使用される平均バイト数を決定します。 これは、構成された値マーシャラーを使用して、指定された平均値をシリアル化することによりこれを行います。 次に、各マップエントリの値に決定されたバイト数を割り当てます。*
インメモリマップに関して注意しなければならないことの1つは、JVMプロセスが動作しているときにのみデータにアクセスできるということです。 ライブラリは、プロセスが終了するとデータをクリアします。

* 4.2。 永続マップ*

インメモリマップとは異なり、*実装は永続化されたマップをディスクに保存します*。 永続マップを作成する方法を見てみましょう。
ChronicleMap<LongValue, CharSequence> 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_はhttps://github.com/OpenHFT/Chronicle-Values[value interface]であるため、平均キーを構成していません。
ここで、キー/値バイトの平均数を設定するためのオプションを見てみましょう。
  • averageValue() –平均バイト数の元となる値
    マップエントリの値に割り当てられることが決定されます

  • averageValueSize() –割り当てられる平均バイト数
    マップエントリの値

  • constantValueSizeBySample() –割り当てられるバイト数
    値のサイズが常に同じ場合のマップエントリの値

  • averageKey() –平均バイト数を取得するキー
    マップエントリのキーに割り当てられたものが決定されます

  • averageKeySize() –割り当てられる平均バイト数
    マップエントリのキー

  • constantKeySizeBySample() –割り当てられるバイト数
    キーのサイズが常に同じ場合のマップエントリのキー

6. キーと値のタイプ

Chronicle Mapを作成するとき、特にキーと値を定義するときに従う必要がある特定の標準があります。 推奨タイプを使用してキーと値を作成すると、マップが最適に機能します。
推奨されるタイプの一部を次に示します。
  • _Value_インターフェイス

  • からの_Byteable_インターフェイスを実装するクラス
    Chronicle Bytes

  • Chronicleの_BytesMarshallable_インターフェイスを実装するクラス
    バイト;実装クラスには、引数のないパブリックコンストラクタが必要です。

  • byte [] _および_ByteBuffer

  • CharSequence _、 String_、および_StringBuilder_

  • Integer _、 Long_、および_Double_

  • _java.io.Externalizable_を実装するクラス。実装
    クラスには、引数のないパブリックコンストラクタが必要です。

  • _java.io.Serializable_を実装する任意のタイプ(ボックス化を含む)
    プリミティブ型(上記のリストを除く)および配列型

  • カスタムシリアライザーが提供されている場合、その他のタイプ

7. クロニクルマップのクエリ

Chronicle Mapは、単一キークエリとマルチキークエリをサポートしています。

* 7.1。 単一キークエリ*

単一キークエリは、単一キーを処理する操作です。 _ChronicleMap_は、Java _Map_インターフェースおよび_ConcurrentMap_インターフェースからのすべての操作をサポートします。
LongValue qatarKey = Values.newHeapInstance(LongValue.class);
qatarKey.setValue(1);
inMemoryCountryMap.put(qatarKey, "Qatar");

//...

CharSequence country = inMemoryCountryMap.get(key);
通常のgetおよびput操作に加えて、* _ ChronicleMap_は特別な操作_getUsing()、_を追加し、エントリの取得および処理中にメモリフットプリントを削減します*。 これを実際に見てみましょう。
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")));
ここでは、_getUsing()_メソッドに渡すことにより、異なるキーの値を取得するために同じ_StringBuilder_オブジェクトを使用しました。 基本的に、異なるエントリを取得するために同じオブジェクトを再利用します。 この場合、_getUsing()_メソッドは次と同等です。
country.setLength(0);
country.append(persistedCountryMap.get(key));

* 7.2。 マルチキークエリ*

複数のキーを同時に処理する必要がある場合があります。 このために、_queryContext()_機能を使用できます。 * _queryContext()_メソッドは、マップエントリを操作するためのコンテキストを作成します。*
最初にマルチマップを作成し、それにいくつかの値を追加しましょう。
Set<Integer> averageValue = IntStream.of(1, 2).boxed().collect(Collectors.toSet());
ChronicleMap<Integer, Set<Integer>> multiMap = ChronicleMap
  .of(Integer.class, (Class<Set<Integer>>) (Class) Set.class)
  .name("multi-map")
  .entries(50)
  .averageValue(averageValue)
  .create();

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

Set<Integer> set2 = new HashSet<>();
set2.add(3);
multiMap.put(2, set2);
*複数のエントリを処理するには、それらのエントリをロックして、同時更新が原因で発生する可能性のある不整合を防止する必要があります。*
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> fistContext = multiMap.queryContext(1)) {
    try (ExternalMapQueryContext<Integer, Set<Integer>, ?> secondContext = multiMap.queryContext(2)) {
        fistContext.updateLock().lock();
        secondContext.updateLock().lock();

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

        MapEntry<Integer, Set<Integer>> secondEntry = secondContext.entry();
        Set<Integer> 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();
*ここで留意すべきことの1つは、マップを閉じる前にすべてのマップ操作を完了する必要があるということです。 そうしないと、JVMが予期せずクラッシュする可能性があります。*

9. 結論

このチュートリアルでは、クロニクルマップを使用してキーと値のペアを保存および取得する方法を学びました。 コミュニティバージョンはほとんどのコア機能で利用できますが、商用バージョンには複数のサーバー間でのデータレプリケーションやリモートコールなどの高度な機能があります。
ここで説明したすべての例は、https://github.com/eugenp/tutorials/tree/master/libraries-2 [Githubプロジェクト]で見つけることができます。