1. 概要

このチュートリアルでは、 cache2k —軽量で高性能なインメモリJavaキャッシングライブラリを見ていきます。

2. cache2kについて

cache2kライブラリは、キャッシュされた値への非ブロッキングおよび待機なしのアクセスにより、高速アクセス時間を提供します。 また、Spring Framework、Scala Cache、Datanucleus、およびHibernateとの統合もサポートしています。

ライブラリには、スレッドセーフなアトミック操作、ブロッキング付きのキャッシュローダー リードスルー、自動有効期限[ X178X]、更新先、イベントリスナー、およびJSR107APIのJCache実装のサポート。 このチュートリアルでは、これらの機能のいくつかについて説明します。

cache2kは、InfispanHazelcastのような分散キャッシュソリューションではないことに注意してください。

3. Mavenの依存関係

cache2kを使用するには、最初にcache2k-base-bom依存関係pom.xmlに追加する必要があります。

<dependency>
    <groupId>org.cache2k</groupId>
    <artifactId>cache2k-base-bom</artifactId>
    <version>1.2.3.Final</version>
    <type>pom</type>
</dependency>

4. 単純なcache2kの例

それでは、簡単な例を使用して、Javaアプリケーションでcache2kを使用する方法を見てみましょう。

オンラインショッピングサイトの例を考えてみましょう。 このWebサイトで、すべてのスポーツ製品が20%割引され、他の製品が10%割引されているとします。 ここでの目標は、割引をキャッシュして、毎回計算しないようにすることです。

したがって、最初に、 ProductHelper クラスを作成し、単純なキャッシュ実装を作成します。

public class ProductHelper {

    private Cache<String, Integer> cachedDiscounts;
    private int cacheMissCount = 0;

    public ProductHelper() {
        cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
          .name("discount")
          .eternal(true)
          .entryCapacity(100)
          .build();
    }

    public Integer getDiscount(String productType) {
        Integer discount = cachedDiscounts.get(productType);
        if (Objects.isNull(discount)) {
            cacheMissCount++;
            discount = "Sports".equalsIgnoreCase(productType) ? 20 : 10;
            cachedDiscounts.put(productType, discount);
        }
        return discount;
    }

    // Getters and setters

}

ご覧のとおり、 cacheMissCount 変数を使用して、キャッシュで割引が見つからなかった回数をカウントしました。 したがって、 getDiscount メソッドがキャッシュを使用して割引を取得する場合、cacheMissCountは変更されません。

次に、テストケースを作成し、実装を検証します。

@Test
public void whenInvokedGetDiscountTwice_thenGetItFromCache() {
    ProductHelper productHelper = new ProductHelper();
    assertTrue(productHelper.getCacheMissCount() == 0);
    
    assertTrue(productHelper.getDiscount("Sports") == 20);
    assertTrue(productHelper.getDiscount("Sports") == 20);
    
    assertTrue(productHelper.getCacheMissCount() == 1);
}

最後に、使用した構成を簡単に見てみましょう。

1つ目はnameメソッドで、はキャッシュの一意の名前を設定します。 キャッシュ名はオプションであり、指定しない場合に生成されます。

次に、eternalをtrueに設定して、キャッシュされた値が時間とともに期限切れにならないことを示します。 したがって、この場合、キャッシュから要素を明示的に削除することを選択できます。 それ以外の場合、キャッシュが容量に達すると、要素は自動的に削除されます。

また、 entryCapacity メソッドを使用して、キャッシュが保持するエントリの最大数指定しました。 キャッシュが最大サイズに達すると、キャッシュ排除アルゴリズムは1つ以上のエントリを削除して、指定された容量を維持します。

Cache2kBuilderクラスで利用可能な他の構成をさらに詳しく調べることができます。

5. cache2k機能

それでは、例を拡張して、cache2kの機能のいくつかを調べてみましょう。

5.1. キャッシュの有効期限の構成

これまでのところ、すべてのスポーツ製品に固定割引を許可しています。 しかし、私たちのウェブサイトは現在、割引が一定期間のみ利用可能であることを望んでいます。

この新しい要件に対応するために、expireAfterWriteメソッドを使用してキャッシュの有効期限を構成します。

cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
  // other configurations
  .expireAfterWrite(10, TimeUnit.MILLISECONDS)
  .build();

次に、キャッシュの有効期限を確認するためのテストケースを作成しましょう。

@Test
public void whenInvokedGetDiscountAfterExpiration_thenDiscountCalculatedAgain() 
  throws InterruptedException {
    ProductHelper productHelper = new ProductHelper();
    assertTrue(productHelper.getCacheMissCount() == 0);
    assertTrue(productHelper.getDiscount("Sports") == 20);
    assertTrue(productHelper.getCacheMissCount() == 1);

    Thread.sleep(20);

    assertTrue(productHelper.getDiscount("Sports") == 20);
    assertTrue(productHelper.getCacheMissCount() == 2);
}

私たちのテストケースでは、設定された期間が経過した後、割引を再度取得しようとしました。 前の例とは異なり、cacheMissCountがインクリメントされていることがわかります。 これは、キャッシュ内のアイテムの有効期限が切れており、割引が再度計算されるためです。

高度なキャッシュ有効期限構成の場合、ExpiryPolicyを構成することもできます。

5.2. キャッシュのロードまたはリードスルー

この例では、キャッシュアサイドパターンを使用してキャッシュをロードしました。 これは、getDiscountメソッドでオンデマンドのキャッシュに割引を計算して追加したことを意味します。

または、単にリードスルー操作にcache2kサポートを使用することもできます。 この操作では、キャッシュは、ローダーを使用して、欠落している値を自動的にロードします。 これは、キャッシュの読み込みとも呼ばれます。

次に、例をさらに拡張して、キャッシュを自動的に計算してロードします。

cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
  // other configurations
  .loader((key) -> {
      cacheMissCount++;
      return "Sports".equalsIgnoreCase(key) ? 20 : 10;
  })
  .build();

また、getDiscountから割引の計算と更新のロジックを削除します。

public Integer getDiscount(String productType) {
    return cachedDiscounts.get(productType);
}

その後、ローダーが期待どおりに機能していることを確認するためのテストケースを作成しましょう。

@Test
public void whenInvokedGetDiscount_thenPopulateCacheUsingLoader() {
    ProductHelper productHelper = new ProductHelper();
    assertTrue(productHelper.getCacheMissCount() == 0);

    assertTrue(productHelper.getDiscount("Sports") == 20);
    assertTrue(productHelper.getCacheMissCount() == 1);

    assertTrue(productHelper.getDiscount("Electronics") == 10);
    assertTrue(productHelper.getCacheMissCount() == 2);
}

5.3. イベントリスナー

キャッシュ要素の挿入、更新、削除、有効期限など、さまざまなキャッシュ操作のイベントリスナーを構成することもできます。

キャッシュに追加されたすべてのエントリをログに記録するとします。 それでは、キャッシュビルダーにイベントリスナー構成を追加しましょう。

.addListener(new CacheEntryCreatedListener<String, Integer>() {
    @Override
    public void onEntryCreated(Cache<String, Integer> cache, CacheEntry<String, Integer> entry) {
        LOGGER.info("Entry created: [{}, {}].", entry.getKey(), entry.getValue());
    }
})

これで、作成したテストケースを実行して、ログを確認できます。

Entry created: [Sports, 20].

イベントリスナーは、有効期限イベントを除いて同期的に実行されることに注意することが重要です。 非同期リスナーが必要な場合は、addAsyncListenerメソッドを使用できます。

5.4. 不可分操作

Cache クラスには、アトミック操作をサポートする多くのメソッドがあります。 これらのメソッドは、単一のエントリでの操作専用です。

このようなメソッドには、 containsAndRemove putIfAbsent removeIfEquals replaceIfEqualsがあります。 peekAndReplace 、およびpeekAndPut

6. 結論

このチュートリアルでは、cache2kライブラリとその便利な機能のいくつかを調べました。 cache2kユーザーガイドを参照して、ライブラリをさらに詳しく調べることができます。

いつものように、このチュートリアルの完全なコードは、GitHubから入手できます。